1. 程式人生 > >Android中ListView的幾種常見的優化方法

Android中ListView的幾種常見的優化方法

Android中的ListView應該算是佈局中幾種最常用的元件之一了,使用也十分方便,下面將介紹ListView幾種比較常見的優化方法:

首先我們給出一個沒有任何優化的Listview的Adapter類,我們這裡都繼承自BaseAdapter,這裡我們使用一個包含100個字串的List集合來作為ListView的專案所要顯示的內容,每一個條目都是一個自定義的元件,這個元件中只包含一個textview:


Activity:

  1. package com.alexchen.listviewoptimize;  
  2. import java.util.ArrayList;  
  3. import java.util.List;  
  4. import android.app.Activity;  
  5. import android.os.Bundle;  
  6. import android.view.Menu;  
  7. import android.view.MenuItem;  
  8. import android.view.View;  
  9. import android.view.ViewGroup;  
  10. import android.widget.BaseAdapter;  
  11. import android.widget.ListView;  
  12. import android.widget.TextView;  
  13. publicclass MainActivity 
    extends Activity {  
  14.     private ListView lv_demo;  
  15.     private List<String> list;  
  16.     @Override
  17.     protectedvoid onCreate(Bundle savedInstanceState) {  
  18.         super.onCreate(savedInstanceState);  
  19.         setContentView(R.layout.activity_main);  
  20.         lv_demo = (ListView) findViewById(R.id.lv_demo);  
  21.         //list為要載入的條目文字的集合,這裡總共是100條
  22.         list = new ArrayList<String>();  
  23.         for (int i = 0; i < 100; i++) {  
  24.             list.add("條目" + i);  
  25.         }  
  26.         lv_demo.setAdapter(new MyAdapter());  
  27.     }  
  28.     privateclass MyAdapter extends BaseAdapter {  
  29.         @Override
  30.         publicint getCount() {  
  31.             return list.size();  
  32.         }  
  33.         @Override
  34.         public View getView(int position, View convertView, ViewGroup parent) {  
  35.             //listview_item裡只有一個textview
  36.             View view = View.inflate(MainActivity.this, R.layout.listview_item,  
  37.                     null);  
  38.             //使用每一次都findviewById的方法來獲得listview_item內部的元件
  39.             TextView tv_item = (TextView) view.findViewById(R.id.tv_item);  
  40.             tv_item.setText(list.get(position));  
  41.             return view;  
  42.         }  
  43.         @Override
  44.         public Object getItem(int position) {  
  45.             returnnull;  
  46.         }  
  47.         @Override
  48.         publiclong getItemId(int position) {  
  49.             return0;  
  50.         }  
  51.     }  
  52. }  

優化一:

也是最普通的優化,就在MyAdapter類中的getView方法中,我們注意到,上面的寫法每次需要一個View物件時,都是去重新inflate一個View出來返回去,沒有實現View物件的複用,而實際上對於ListView而言,只需要保留能夠顯示的最大個數的view即可,其他新的view可以通過複用的方式使用消失的條目的view,而getView方法裡也提供了一個引數:convertView,這個就代表著可以複用的view物件,當然這個物件也可能為空,當它為空的時候,表示該條目view第一次建立,所以我們需要inflate一個view出來,所以在這裡,我們使用下面這種方式來重寫getView方法:

  1. @Override
  2.     public View getView(int position, View convertView, ViewGroup parent) {  
  3.         View view;  
  4.         // 判斷convertView的狀態,來達到複用效果
  5.         if (null == convertView) {  
  6.             //如果convertView為空,則表示第一次顯示該條目,需要建立一個view
  7.             view = View.inflate(MainActivity.this, R.layout.listview_item,  
  8.                     null);  
  9.         } else {  
  10.             //否則表示可以複用convertView
  11.             view = convertView;  
  12.         }  
  13.         // listview_item裡只有一個textview
  14.         TextView tv_item = (TextView) view.findViewById(R.id.tv_item);  
  15.         tv_item.setText(list.get(position));  
  16.         return view;  
  17.     }  

優化二:

上面是對view物件的複用做的優化,我們經過上面的優化之後,我們不需要每一個view都重新生成了。下面我們來解決下一個每一次都需要做的工作,那就是view中元件的查詢:

TextView tv_item = (TextView) view.findViewById(R.id.tv_item);

實際上,findViewById是到xml檔案中去查詢對應的id,可以想象如果元件多的話也是挺費事的,如果我們可以讓view內的元件也隨著view的複用而複用,那該是多美好的一件事啊。。實際上谷歌也推薦了一種優化方法來做應對,那就是重新建一個內部靜態類,裡面的成員變數跟view中所包含的元件個數型別相同,我們這裡的view只包含了一個TextView,所以我們的這個靜態類如下:

  1. privatestaticclass ViewHolder {  
  2.     private TextView tvHolder;  
  3. }  

那麼這個viewHolder類我們要如何使用才可以達到複用效果呢?基本思路就是在convertView為null的時候,我們不僅重新inflate出來一個view,並且還需要進行findviewbyId的查詢工作,但是同時我們還需要獲取一個ViewHolder類的物件,並將findviewById的結果賦值給ViewHolder中對應的成員變數。最後將holder物件與該view物件“綁”在一塊。

當convertView不為null時,我們讓view=converView,同時取出這個view對應的holder物件,就獲得了這個view物件中的TextView元件,它就是holder中的成員變數,這樣在複用的時候,我們就不需要再去findViewById了,只需要在最開始的時候進行數次查詢工作就可以了。這裡的關鍵在於如何將view與holder物件進行繫結,那麼就需要用到兩個方法:setTag和getTag方法了:

  1. @Override
  2.     public View getView(int position, View convertView, ViewGroup parent) {  
  3.         View view;  
  4.         ViewHolder holder;  
  5.         // 判斷convertView的狀態,來達到複用效果
  6.         if (null == convertView) {  
  7.             // 如果convertView為空,則表示第一次顯示該條目,需要建立一個view
  8.             view = View.inflate(MainActivity.this, R.layout.listview_item,  
  9.                     null);  
  10.             //新建一個viewholder物件
  11.             holder = new ViewHolder();  
  12.             //將findviewbyID的結果賦值給holder對應的成員變數
  13.             holder.tvHolder = (TextView) view.findViewById(R.id.tv_item);  
  14.             // 將holder與view進行繫結
  15.             view.setTag(holder);  
  16.         } else {  
  17.             // 否則表示可以複用convertView
  18.             view = convertView;  
  19.             holder = (ViewHolder) view.getTag();  
  20.         }  
  21.         // 直接操作holder中的成員變數即可,不需要每次都findViewById
  22.         holder.tvHolder.setText(list.get(position));  
  23.         return view;  
  24.     }  

經過上面的做法,可能大家感覺不太到優化的效果,根據Google的文件,實際優化效果在百分之5左右。

優化三:

上面的兩個例子中ListView都是顯示的本地的List集合中的內容,List的長度也只有100個,我們可以毫不費力一次性載入完這100個數據;但是實際應用中,我們往往會需要使用Listview來顯示網路上的內容,比如說我們拿使用ListView顯示新聞為例:

其一:假如網路情況很好,我們使用的手機也許能夠一下子載入完所有新聞資料,然後顯示在ListView中,使用者可能感覺還好,假如說在網路不太順暢的情況下,使用者載入完所有網路的資料,可能這個list是1000條新聞,那麼使用者可能需要面對一個空白的Activity好幾分鐘,這個顯然是不合適的

其二:我們知道Android虛擬機器給每個應用分配的執行時記憶體是一定的,一般效能不太好的機器只有16M,好一點的可能也就是64M的樣子,假如說我們現在要瀏覽的新聞總數為一萬條,即便是網路很好的情況下,我們可以很快的載入完畢,但是多數情況下也會出現記憶體溢位從而導致應用崩潰的情況。

那麼為了解決上面的兩個問題,我們需要進行分批載入,比如說1000條新聞的List集合,我們一次載入20條,等到使用者翻頁到底部的時候,我們再新增下面的20條到List中,再使用Adapter重新整理ListView,這樣使用者一次只需要等待20條資料的傳輸時間,不需要一次等待好幾分鐘把資料都載入完再在ListView上顯示。其次這樣也可以緩解很多條新聞一次載入進行產生OOM應用崩潰的情況。

實際上,分批載入也不能完全解決問題,因為雖然我們在分批中一次只增加20條資料到List集合中,然後再重新整理到ListView中去,假如有10萬條資料,如果我們順利讀到最後這個List集合中還是會累積海量條數的資料,還是可能會造成OOM的情況,這時候我們就需要用到分頁,比如說我們將這10萬條資料分為1000頁,每一頁100條資料,每一頁載入時都覆蓋掉上一頁中List集合中的內容,然後每一頁內再使用分批載入,這樣使用者的體驗就會相對好一些。