Android中ListView的優化方法

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

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

Activity:

package com.alexchen.listviewoptimize;

import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class MainActivity extends Activity {

private ListView lv_demo;
private List list;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv_demo = (ListView) findViewById(R.id.lv_demo);
//list爲要加載的條目文本的集合,這裏總共是100條
list = new ArrayList();
for (int i = 0; i < 100; i++) {
list.add(“條目” + i);
}

lv_demo.setAdapter(new MyAdapter());

}

private class MyAdapter extends BaseAdapter {

@Override
public int getCount() {
  return list.size();
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
  //listview_item裏只有一個textview
  View view = View.inflate(MainActivity.this, R.layout.listview_item,
      null);
  //使用每一次都findviewById的方法來獲得listview_item內部的組件
  TextView tv_item = (TextView) view.findViewById(R.id.tv_item);
  tv_item.setText(list.get(position));
  return view;
}

@Override
public Object getItem(int position) {
  return null;
}

@Override
public long getItemId(int position) {
  return 0;
}

}
}
優化一:

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

@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
// 判斷convertView的狀態,來達到複用效果
if (null == convertView) {
//如果convertView爲空,則表示第一次顯示該條目,需要創建一個view
view = View.inflate(MainActivity.this, R.layout.listview_item,
null);
} else {
//否則表示可以複用convertView
view = convertView;
}
// listview_item裏只有一個textview
TextView tv_item = (TextView) view.findViewById(R.id.tv_item);
tv_item.setText(list.get(position));
return view;
}
優化二:

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

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

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

private static class ViewHolder {
private TextView tvHolder;
}
那麼這個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方法了:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
ViewHolder holder;
// 判斷convertView的狀態,來達到複用效果
if (null == convertView) {
// 如果convertView爲空,則表示第一次顯示該條目,需要創建一個view
view = View.inflate(MainActivity.this, R.layout.listview_item,
null);
//新建一個viewholder對象
holder = new ViewHolder();
//將findviewbyID的結果賦值給holder對應的成員變量
holder.tvHolder = (TextView) view.findViewById(R.id.tv_item);
// 將holder與view進行綁定
view.setTag(holder);
} else {
// 否則表示可以複用convertView
view = convertView;
holder = (ViewHolder) view.getTag();
}
// 直接操作holder中的成員變量即可,不需要每次都findViewById
holder.tvHolder.setText(list.get(position));
return view;
}
經過上面的做法,可能大家感覺不太到優化的效果,根據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集合中的內容,然後每一頁內再使用分批加載,這樣用戶的體驗就會相對好一些。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章