提升Android ListView效能的幾個技巧

來源:互聯網
上載者:User

提升Android ListView效能的幾個技巧

  ListView如何運作的?

  ListView是設計應用於對可擴充性和高效能要求的地方。實際上,這就意味著ListView有以下2個要求:

  儘可能少的建立View;

  只是繪製和布局在螢幕上可見的子View。

  理解第一點很簡單:通過布局xml檔案在建立View並顯示是很昂貴耗時耗資源的操作。儘管布局檔案已經編譯打包成了二進位形式以便於更高效的文法解析,但是建立View仍然需要通過一個特殊的XML樹,並執行個體化所有需要響應的View。

  ListView通過回收一些不可見的Views,通常在Android源碼中稱為“ScrapView(廢棄的View)”來解決這個問題。這及意味著開發人員只需要簡單的更新每行的內容而不需要針對每個單獨的行的布局來建立View。

  為了實現第二點,在我們滑動螢幕時,ListView通過使用View回收器來增加低於或者高於噹噹前視窗的Views,併當前活動的Views移動到一個可回收池中。這樣的話,ListView只需要在記憶體中保持足夠多的Views去填充分配空間中的布局和一些額外的可回收Views,即使當你的Adapter有上百個items的適合。它會使用不同的方法去填充行之間的空間,從頂部或者底部等等,具體取決於視窗是如何變化的。

  下面這個圖很直觀的展示了當你按下ListView的時候發生了什麼:

  ListView

  通過上述介紹,相比我們已經熟悉了ListView的這種機制,讓我們繼續前往技巧部分。正如上述介紹的,在滑動時,ListView通過動態建立和回收很多View,實現了儘可能地讓Adapter的getView()輕量。所有的技巧都是通過多種方法讓getView()更快。

  View的回收

  當ListView每次需要在螢幕上顯示新的一行的時候,會從其Adapter中調用getView()的方法。眾所周知,getView()方法有3個參數:行的位置, convertView以及父ViewGroup。

  參數convertView說穿來就是之前講述的ScrapView。當ListView要求更新一行的布局時,convertView是一個非空值。因此,當convertView值非空時,你僅僅需要更新內容即可,而不需要重新一個新行的布局。getView()在Adapter中一般是如下的形式:

  public View getView(int position, View convertView, ViewGroup parent) {

  if (convertView == null) {

  convertView = mInflater.inflate(R.layout.your_layout, null);

  }

  TextView text = (TextView) convertView.findViewById(R.id.text);

  text.setText("Position " + position);

  return convertView;

  }

  View Holder如何寫的模板

  Android很常見的一個操作就是在布局檔案中找到一個內部的View。通常是使用一個findViewById()的View方法來實現的。這個findViewById()方法在View樹中,根據一個View ID,會遞迴的被調用來找到其子樹。雖然在靜態UI布局中使用findViewById()是完全正常的。但是,在滑動時,ListView調用其Adapter中的getView()是非常頻繁的。findViewById()可能會影響ListView滑動時的效能,尤其是你的行布局是很複雜的時候。

  尋找一個充氣布局內的內部觀點是在Android上最常用的操作之一。這通常是通過一個名為findViewById(查看方法完成)。此方法將遞 歸經過視圖樹尋找一個孩子用給定的ID碼。靜態UI布局使用findViewById()是完全正常,但正如你所看到的,ListView中滾動時調用 適配器的getView()非常頻繁。 findViewById()可能perceivably擊中ListViews,尤其是滾動的效能,如果你行的布局是不平凡的。

  View Holder的模式就是減少在Adapter中getView()方法中調用findViewById()次數。實際上,View Holder是一個輕量級的內部類,用於直接引用到所有內部views。在建立View之後,你可以在每行的View儲存為一個標籤。通過這種方法,只需要在初次建立布局的時候調用findViewById()。下面是一個使用上述方法的View Holder模板的程式碼範例:

  public View getView(int position, View convertView, ViewGroup parent) {

  ViewHolder holder;

  if (convertView == null) {

  convertView = mInflater.inflate(R.layout.your_layout, null);

  holder = new ViewHolder();

  holder.text = (TextView) convertView.findViewById(R.id.text);

  convertView.setTag(holder);

  } else {

  holder = convertView.getTag();

  }

  holder.text.setText("Position " + position);

  return convertView;

  }

  private static class ViewHolder {

  public TextView text;

  }

  非同步載入

  很多時候,Android應用在ListView每行中顯示一些多媒體內容,比片等。在Adapter中的getView()使用應用內建的圖片資源還是不會出什麼問題的,因為可以儲存在Android的快取中。但當你想多態的顯示來自本地磁碟或網路的內容時,例如縮圖,簡曆圖片等。在這種情況下,你可能不希望直接在Adapter中的getView()載入它們,因為IO進程會阻塞UI線程。如果這樣做的話,ListView就看起來非常卡頓。

  在一個單獨的線程,如果想要啟動並執行所有行的IO操作或任何高負載CPU限制的非同步作業。其中的技巧就是要做到符合ListView的回收行為。例如,如果在Adapter中的getView()中,使用AsyncTask的載入去載入資料圖片,在AsyncTask完成之前,你正在載入的圖片View就有可能被回收用於其他地方。所以,一旦非同步作業完成的同時,需要一種機制來知道如果相應的View有沒有被回收。

  一個簡單的方法來實現這一目標是通過附加一些標識該行與它相關的View的資訊。然後,當非同步作業完成的適合,檢查目標行的View和標識的View是否一致。實現這一目標的方法很多。下面是實現這種方法的一個很簡單的樣本:

  public View getView(int position, View convertView,

  ViewGroup parent) {

  ViewHolder holder;

  ...

  holder.position = position;

  new ThumbnailTask(position, holder)

  .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null);

  return convertView;

  }

  private static class ThumbnailTask extends AsyncTask {

  private int mPosition;

  private ViewHolder mHolder;

  public ThumbnailTask(int position, ViewHolder holder) {

  mPosition = position;

  mHolder = holder;

  }

  @Override

  protected Cursor doInBackground(Void... arg0) {

  // Download bitmap here

  }

  @Override

  protected void onPostExecute(Bitmap bitmap) {

  if (mHolder.position == mPosition) {

  mHolder.thumbnail.setImageBitmap(bitmap);

  }

  }

  }

  private static class ViewHolder {

  public ImageView thumbnail;

  public int position;

  }

  人機互動知識

  做到在每一行非同步載入很多資源,是一個高效能的ListView的必經之路。但是,在滑動螢幕時,如果你一味的在每一個getView()調用裡面都去啟動一個非同步操作,造成的結果就是你會浪費大量資源。因為行被頻繁回收,造成大部分返回的結果會被丟棄。

  考慮到實際的人機互動情況,在ListView適配器中,在每一行中都不應該去觸發任何非同步作業。也就是說,在ListView中有fling(快速滑動)操作時,啟動任何非同步作業都沒有任何意義。一旦滾動停止或即將停止,才是開始真正顯示每行的內容的時候。

  我不會發布一個程式碼範例貼在這裡,因為其中涉及到的代碼太多。Romain Guy寫了一個很經典的應用:Shelves app,其中有一個很好的的樣本。當GridView停止滑動時不做其他事情時,它就開始觸發從而去非同步載入書的封面資源。即使在滑動時,你也可以展示緩衝中的內容,通過使用memory cache來平衡互動。這真是個好主意!

  以上

  我強烈推薦你看下Romain Guy和Adam Powell的關於ListView的討論,裡面涵蓋了很多這篇文章的東西。你可以看看Pattrn,可以看到這裡面的幾個技巧是如何在應用中運用的。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.