ListView原理與優化總結


想要優化ListView就得先了解ListView加載數據原理
列表的顯示需要三個元素
    1. ListVeiw: 用來展示列表的View
    2. 適配器: 用來把數據映射到ListView上
    3. 數據: 具體的將被映射的字符串,圖片,或者基本組件
    根據列表的適配器類型,列表分為三種,ArrayAdapter,SimpleAdapter和SimpleCursorAdapter,其中以ArrayAdapter最為簡單,只能展示一行字。SimpleAdapter有最好的擴充性,可以自定義出各種效果。SimpleCursorAdapter可以認為是SimpleAdapter對數據庫的簡單結合,可以方便的把數據庫的內容以列表的形式展示出來。
    系統要繪制ListView時,他首先用getCount()函數得到要繪制的這個列表的長度,然后開始繪制第一行,怎么繪制呢?調用getView()函數。在這個函數里面首先獲得一個View(這個看實際情況,如果是一個簡單的顯示則是View,如果是一個自定義的里面包含很多控件的時候它其實是一個ViewGroup),然后再實例化並設置各個組件及其數據內容並顯示它。好了,繪制完這一行了。那再繪制下一行,直到繪完為止.
     現在我們再來了解ListView加載數據的原理,有了這方面的了解后再說優化才行,下面先跟大家一起來看下ListView加載數據的基本原理。
ListView的工作原理:ListView針對每個item,要求adapter “返回一個視圖”(getView),也就是說ListView在開始繪制的時候,系統首先調用getCount()函數,根據他的返回值得到ListView的長度,然后根據這個長度,調用getView()一行一行的繪制ListView的每一項。如果你的getCount()返回值是0的話,列表一行都不會顯示,如果返回1,就只顯示一行。返回幾則顯示幾行。如果我們有幾千幾萬甚至更多的item要顯示怎么辦?為每個Item創建一個新的View?不可能!!!實際上Android早已經緩存了這些視圖,大家可以看下下面這個截圖來理解下,這個圖是解釋ListView工作原理的最經典的圖了大家可以收藏下,不懂的時候拿來看看,加深理解,其實Android中有個叫做Recycler的構件,順帶列舉下與Recycler相關的已經由Google做過N多優化過的東東比如:AbsListView.RecyclerListener、ViewDebug.RecyclerTraceType等等,要了解的朋友自己查下,不難理解,下圖是ListView加載數據的工作原理(原理圖看不清楚的點擊后看大圖):


下面簡單說下上圖的原理
     1. 如果你有幾千幾萬甚至更多的選項(item)時,其中只有可見的項目存在內存(內存內存哦,說的優化就是說在內存中的優化!!!)中,其他的在Recycler中
     2. ListView先請求一個type1視圖(getView)然后請求其他可見的項目。convertView在getView中是空(null)的
     3. 當item1滾出屏幕,並且一個新的項目從屏幕低端上來時,ListView再請求一個type1視圖。convertView此時不是空值了,它的值是item1。你只需設定新的數據然后返回convertView,不必重新創建一個視圖

下面來看下示例代碼,結合上面的原理圖一起加深理解,如下:

public class MultipleItemsList extends ListActivity { 

private MyCustomAdapter mAdapter;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAdapter = new MyCustomAdapter();
for (int i = 0; i < 50; i++) {
mAdapter.addItem("item " + i);
}
setListAdapter(mAdapter);
}

private class MyCustomAdapter extends BaseAdapter {

private ArrayList mData = new ArrayList();

private LayoutInflater mInflater;

public MyCustomAdapter() {
mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

public void addItem(final String item) {
mData.add(item);
notifyDataSetChanged();
}

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

@Override
public String getItem(int position) {
return mData.get(position);
}

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

@Override
public View getView(int position, View convertView, ViewGroup parent) {
System.out.println("getView " + position + " " + convertView);
ViewHolder holder = null;
<span style="color:#ff0000;">if (convertView == null) {
convertView = mInflater.inflate(R.layout.item1, null);
holder = new ViewHolder();
holder.textView = (TextView)convertView.findViewById(R.id.text);
convertView.setTag(holder);
} else {
holder = (ViewHolder)convertView.getTag();
}
holder.textView.setText(mData.get(position));
return convertView;
}
}

public <span style="color:#ff0000;">static</span> class ViewHolder {
public TextView textView;
}
}

執行程序,查看日志:

getView被調用9次,convertView對於所有的可見項目是空值(如下):

然后稍微向下滾動List,直到item10出現:

convertView仍然是空值,因為recycler中沒有視圖(item1的邊緣仍然可見,在頂端)再滾動列表,繼續滾動:

convertView不是空值了!item1離開屏幕到Recycler中去了,然后item11被創建,再滾動下:

此時的convertView非空了,在item11離開屏幕之后,它的視圖(…0f8)作為convertView容納item12了,好啦,結合以上原理,下面來看看今天最主要的話題,主角ListView的優化:
    首先,這個地方先記兩個ListView優化的一個小點:
        1. ExpandableListView 與 ListActivity 由官方提供的,里面要使用到的ListView是已經經過優化的ListView,如果大家的需求可以用Google自帶的ListView滿足的的話盡量用官方的,絕對沒錯!
        2.其次,像小馬前面講的,說ListView優化,其實並不是指其它的優化,就是內存是的優化,提到內存…(想到OOM,折騰了我不少時間),很多很多,先來寫下,如果我們的ListView中的選項僅僅是一些簡單的TextView的話,就好辦啦,消耗不了多少的,但如果你的Item是自定義的Item的話,例如你的自定義Item布局ViewGroup中包含:按鈕、圖片、flash、CheckBox、RadioButton等一系列你能想到的控件的話, 你要在getView中單單使用文章開頭提到的ViewHolder是遠遠不夠的,如果數據過多,加載的圖片過多過大,你BitmapFactory.decode的猛多的話,OOM搞死你,這個地方再警告下大家:大家應該都碰到過的問題,自定義的ListView項亂序,我很天真的在getView()中強制清除了下ListView的緩存數據convertView,也就是convertView = null了,雖然當時是解決了這個問題讓其它每次重繪,但是犯了大錯了,如果數據太多的話,出現最最惡心的錯,手機卡死或強制關機,關機啊…O_O,客戶殺了我都有可能,但大家以后別犯這樣的錯了,單單使用清除緩存convertView是解決不了實際問題的。
小記:圖片用完了正確的釋放:

if(!bmp.isRecycle() ){ 
bmp.recycle() //回收圖片所占的內存
system.gc() //提醒系統及時回收
}


下面來列舉下真正意義上的優化吧:
     1. ViewHolder Tag 必不可少,這個不多說!
     2.如果自定義Item中有涉及到圖片等等的,一定要狠狠的處理圖片,圖片占的內存是ListView項中最惡心的,處理圖片的方法大致有以下幾種:
          2.1:不要直接拿個路徑就去循環decodeFile();這是找死….用Option保存圖片大小、不要加載圖片到內存去;
          2.2:拿到的圖片一定要經過邊界壓縮
          2.3:在ListView中取圖片時也不要直接拿個路徑去取圖片,而是以WeakReference(使用WeakReference代替強引用。比如可以使用WeakReference<Context> mContextRef)、SoftReference、WeakHashMap等的來存儲圖片信息,是圖片信息不是圖片哦!
          2.4:在getView中做圖片轉換時,產生的中間變量一定及時釋放,用以下形式:
     3.盡量避免在BaseAdapter中使用static 來定義全局靜態變量,我以為這個沒影響 ,這個影響很大,static是Java中的一個關鍵字,當用它來修飾成員變量時,那么該變量就屬於該類,而不是該類的實例。所以用static修飾的變量,它的生命周期是很長的,如果用它來引用一些資源耗費過多的實例(比如Context的情況最多),這時就要盡量避免使用了..
     4.如果為了滿足需求下必須使用Context的話:Context盡量使用Application Context,因為Application的Context的生命周期比較長,引用它不會出現內存泄露的問題
     5.盡量避免在ListView適配器中使用線程,因為線程產生內存泄露的主要原因在於線程生命周期的不可控制
     6.記下小馬自己的錯誤:之前使用的自定義ListView中適配數據時使用AsyncTask自行開啟線程的,這個比用Thread更危險,因為Thread只有在run函數不 結束時才出現這種內存泄露問題,然而AsyncTask內部的實現機制是運用了線程執行池(ThreadPoolExcutor,要想了解這個類的話大家加下我們的Android開發群五號,因為其它群的存儲空間快滿了,所以只上傳到五群里了,看下小馬上傳的Gallery源碼,你會對線程執行池、軟、弱、強引用有個更深入的認識),這個類產生的Thread對象的生命周期是不確定的,是應用程序無法控制的,因此如果AsyncTask作為Activity的內部類,就更容易出現內存泄露的問題。這個問題的解決辦法小馬當時網上查到了記在txt里了,如下:
          6.1:將線程的內部類,改為靜態內部類。
          6.2:在線程內部采用弱引用保存Context引用
      示例代碼如下:

public abstract class WeakAsyncTask<Params, Progress, Result, WeakTarget> extends AsyncTask<Params, Progress, Result> { 

protected WeakReference<WeakTarget> mTarget;

public WeakAsyncTask(WeakTarget target) {
mTarget = new WeakReference<WeakTarget>(target);
}

@Override
protected final void onPreExecute() {
final WeakTarget target = mTarget.get();
if (target != null) {
this.onPreExecute(target);
}
}

@Override
protected final Result doInBackground(Params... params) {
final WeakTarget target = mTarget.get();
if (target != null) {
return this.doInBackground(target, params);
} else {
return null;
}
}

@Override
protected final void onPostExecute(Result result) {
final WeakTarget target = mTarget.get();
if (target != null) {
this.onPostExecute(target, result);
}
}
protected void onPreExecute(WeakTarget target) {
// No default action
}

protected abstract Result doInBackground(WeakTarget target, Params... params);

protected void onPostExecute(WeakTarget target, Result result) {
// No default action
}
}

本文來自網絡酷_莫名簡單原創博文http://mzh3344258.blog.51cto.com/1823534/889879


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com