android基礎--ListView原理與優化
列表的顯示需要三個元素:
-
ListVeiw: 用來展示列表的View。
-
介面卡 : 用來把資料對映到ListView上
-
資料: 具體的將被對映的字串,圖片,或者基本元件。
根據列表的介面卡型別,列表分為三種,ArrayAdapter,SimpleAdapter和SimpleCursorAdapter,這三種介面卡的使用大家可學習下官網上面的使用或者自行百度谷歌,一堆DEMO!!!其中以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載入資料的工作原理(原理圖看不清楚的點選後看大圖):
下面簡單說下上圖的原理:
- 如果你有幾千幾萬甚至更多的選項(item)時,其中只有可見的專案存在記憶體(記憶體記憶體哦,說的優化就是說在記憶體中的優化!!!)中,其他的在Recycler中
- ListView先請求一個type1檢視(getView)然後請求其他可見的專案。convertView在getView中是空(null)的
- 當item1滾出螢幕,並且一個新的專案從螢幕低端上來時,ListView再請求一個type1檢視。convertView此時不是空值了,它的值是item1。你只需設定新的資料然後返回convertView,不必重新建立一個檢視
結合上面的原理圖一起加深理解,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
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; 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 static 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優化,其實並不是指其它的優化,就是記憶體是的優化。如果我們的ListView中的選項僅僅是一些簡單的TextView的話,就好辦啦,消耗不了多少的,但如果你的Item是自定義的Item的話,例如你的自定義Item佈局ViewGroup中包含:按鈕、圖片、flash、CheckBox、RadioButton等一系列你能想到的控制元件的話, 你要在getView中單單使用文章開頭提到的ViewHolder是遠遠不夠的,如果資料過多,載入的圖片過多過大,你BitmapFactory.decode的猛多的話,OOM搞死你,這個地方再警告下大家,是警告……….也提醒下自己:
碰到的問題,自定義的ListView項亂序問題,我很天真的在getView()中強制清除了下ListView的快取資料convertView,也就是convertView = null了,雖然當時是解決了這個問題讓其它每次重繪,但是犯了大錯了,如果資料太多的話,出現最最噁心的錯,手機卡死或強制關機。
下面是小記:圖片用完了正確的釋放…
1 2 3 4 |
if(!bmp.isRecycle() ){ bmp.recycle() //回收圖片所佔的記憶體 system.gc() //提醒系統及時回收 } |
下面來列舉下真正意義上的優化吧:
- ViewHolder Tag 必不可少,這個不多說!
- 如果自定義Item中有涉及到圖片,圖片佔的記憶體是ListView項中最噁心的,處理圖片的方法大致有以下幾種:
2.1:不要直接拿個路徑就去迴圈decodeFile() 。 用Option儲存圖片大小、不要載入圖片到記憶體去;
2.2: 拿到的圖片一定要經過邊界壓縮
2.3:在ListView中取圖片時也不要直接拿個路徑去取圖片,而是以WeakReference(使用WeakReference代替強引用。比如可以使用WeakReference<Context> mContextRef)、SoftReference、WeakHashMap等的來儲存圖片資訊,是圖片資訊不是圖片哦!
2.4:在getView中做圖片轉換時,產生的中間變數一定及時釋放。 - 儘量避免在BaseAdapter中使用static 來定義全域性靜態變數,static是Java中的一個關鍵字,當用它來修飾成員變數時,那麼該變數就屬於該類,而不是該類的例項。所以用static修飾的變數,它的生命週期是很長的,如果用它來引用一些資源耗費過多的例項(比如Context的情況最多),這時就要儘量避免使用了..
- 如果為了滿足需求下必須使用Context的話:Context儘量使用Application Context,因為Application的Context的生命週期比較長,引用它不會出現記憶體洩露的問題
- 儘量避免在ListView介面卡中使用執行緒,因為執行緒產生記憶體洩露的主要原因在於執行緒生命週期的不可控制
- 錯誤:
之前使用的自定義ListView中適配資料時使用AsyncTask自行開啟執行緒的,這個比用Thread更危險,因為Thread只有在run函式不結束時才出現這種記憶體洩露問題,然而AsyncTask內部的實現機制是運用了執行緒執行池(ThreadPoolExcutor),這個類產生的Thread物件的生命週期是不確定的,是應用程式無法控制的,因此如果AsyncTask作為Activity的內部類,就更容易出現記憶體洩露的問題。解決辦法如下:
6.1:將執行緒的內部類,改為靜態內部類。
6.2:線上程內部採用弱引用儲存Context引用
程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
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); }
/** {@inheritDoc} */ @Override protected final void onPreExecute() { final WeakTarget target = mTarget.get(); if (target != null) { this.onPreExecute(target); } }
/** {@inheritDoc} */ @Override protected final Result doInBackground(Params... params) { final WeakTarget target = mTarget.get(); if (target != null) { return this.doInBackground(target, params); } else { return null; } }
/** {@inheritDoc} */ @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 } } |