優化ListView

1. 使用後臺線程,讓ListView流暢滾動

有經驗的開發者經常建議:不要在主線程進行耗時的操作。但對於初學者來說,到底什麼是主線程呢?這個問題我琢磨了很久。主線程其實就是UI線程,這個線程負責處理跟UI相關的操作。界面的繪製、界面的加載、控件的調整等操作都是由主線程來執行的。UI線程之所以被稱作主線程,是因爲在絕大多數情況下,我們寫的代碼都會在跑在UI線程裏,比如界面的各個生命週期的回調方法(onCreate(),onResume(), onPause()等)。

由於主線程處理跟UI相關的操作,如果主線程處理耗時的操作(文件處理、網絡請求等),那麼就會導致主線程無法及時處理其他的UI操作請求,這樣我們在界面上就會明顯的感覺到卡頓。這個問題在處理ListView這種需要重複的繪製列表元素的情況下會變得更加嚴峻。因此需要小心處理ListAdapter裏getView()方法裏可能存在的耗時操作。

但使用後臺線程就要接觸多線程了,而多線程操作又是衆所周知的坑多。如果不想自己手動去維護自己的線程池或實現自己的多線程機制,那麼可以使用AsyncTask類來實現後臺處理任務的需求。這是Android提供的一個非常便捷的方法。

  1. // 使用AsyncTask在後臺加載一張加載很慢的圖片
  2. // 這裏的三個泛型參數的意義分別是:
  3. // 1) UI控件、2)進度回調的類型、3)任務執行完畢後的返回類型
  4. new AsyncTask<ViewHolder, Void, Bitmap>() {
  5.  
  6. // ViewHolder技術是實現複雜ListView的一個優化性能的技術,後面會介紹
  7. private ViewHolder v;
  8.  
  9. // doInBackground()方法在後臺線程執行
  10. @Override
  11. protected Bitmap doInBackground(ViewHolder... params) {
  12. v = params[0]; // 這裏保存一份界面的引用
  13.  
  14. // 這裏假定有一個圖片加載器,getImage()方法是它的一個耗時方法
  15. return mFakeImageLoader.getImage();
  16. }
  17.  
  18. // onPostExecute()方法在主線程執行,確保所有耗時的操作在doInBackground()方法做完了
  19. @Override
  20. protected void onPostExecute(Bitmap result) {
  21. super.onPostExecute(result);
  22. if (v.position == position) {
  23. v.progress.setVisibility(View.GONE);
  24. v.icon.setVisibility(View.VISIBLE);
  25. v.icon.setImageBitmap(result);
  26. }
  27. }
  28.  
  29. }.execute(holder);

從Android 3.0(API 11)開始,AsyncTask增加了一個方法:AsyncTask.executeOnExecutor()。這個方法會利用處理器的多核特性,進一步提升性能。這個方法的具體效果取決於具體設備的處理器核心數。

2. 使用ViewHolder技術來優化ListView

前面說到,在ListView這種需要重複的繪製列表元素的情況下,性能問題將變得更加嚴峻。由於內存的原因,ListView只有一個固定數量的View列表來顯示列表的每一項,通過回收不可見的項,重新調整控件來顯示新出現在界面上的項。因此在ListView的滾動過程中,將會頻繁的調用ListAdapter.getView()方法。通過上面的優化建議,我們已經把耗時操作放到後臺線程去執行了,只把必須的UI操作留在主線程。那還能不能再優化呢?答案是肯定的。

如果有經常在網上查找教程,那麼應該對ListView的教程裏的ViewHolder技術不陌生。這個技術的核心是減少主線程裏的執行步驟,以達到優化性能的目的。通過使用ViewHolder,緩存每個View的引用,減少不必要的查找控件操作,以達到優化ListView性能的效果。

首先需要創建一個ViewHolder類來持有View的引用:

  1. static class ViewHolder {
  2. TextView text;
  3. TextView timestamp;
  4. ImageView icon;
  5. ProgressBar progress;
  6. }

然後實例化ViewHolder併爲其賦值,再將ViewHolder存儲在view的tag裏:

  1. ViewHolder holder = new ViewHolder();
  2. holder.text = (TextView) convertView.findViewById(R.id.list_item_text);
  3. holder.timestamp = (TextView) convertView.findViewById(R.id.list_item_timestamp);
  4. holder.icon = (ImageView) convertView.findViewById(R.id.list_item_icon);
  5. holder.progress = (ProgressBar) convertView.findViewById(R.id.list_item_progress);
  6. convertView.setTag(holder);

之後就可以從convertView的tag裏取出ViewHolder來直接訪問對應的控件進行操作了。

  1. ViewHolder holder = (ViewHolder) convertView.getTag();
  2. holder.text.setText(text);
  3. // ...這裏省略對其他控件的操作

發佈了15 篇原創文章 · 獲贊 0 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章