Android優化系列之ListView優化老生常談
本文內容:adapter,listview的優化,RecycleBi,google大會推薦優化,
實現ListView的過程,Adapter起到了至關重要的作用,不僅僅因為getview()方法。那麼,先從Adapter說起~
Adapter:
它在ListView和資料來源之間起到橋樑的作用,避免listview和資料來源直接接觸,而導致因為資料來源的複雜性使listview顯得臃腫。
Adapter,介面卡,把複雜的資料來源適配給listview,很容易聯想到介面卡模式。
下面是幾種常用的Adapter:
- ArrayAdapter:簡單易用的Adapter,通常用於陣列或list集合的資料來源(多個值包裝成多個列表項)。
- simpleAdapter:並不見得、功能強大的Adapter,可用於list集合的多個物件包裝成多個列表項。
- simpleCursorAdapter:與上相似,但是用於包裝jCursor(資料庫遊標)提供的資料來源。
- BaseAdapter:通常用於被擴充套件。擴充套件BaseAdapter可以對各列表項進行最大限度的定製。
下面是listview的優化:
增加優化一:convertView的使用,主要優化載入佈局問題
1.listivew每次滾動都會呼叫gitview()方法,所以優化gitview是重中之重。
下面是getview()在Adapter類的原始碼,這個沒有實現,要看重點部分已經顏色標記。無非是View convertView的介紹~
/** * Get a View that displays the data at the specified position in the data set. You can either * create a View manually or inflate it from an XML layout file. When the View is inflated, the * parent View (GridView, ListView...) will apply default layout parameters unless you use * {@link android.view.LayoutInflater#inflate(int, android.view.ViewGroup, boolean)} * to specify a root view and to prevent attachment to the root. * * @param position The position of the item within the adapter's data set of the item whose view * we want. * @param convertView The old view to reuse, if possible. Note: You should check that this view * is non-null and of an appropriate type before using. If it is not possible to convert * this view to display the correct data, this method can create a new view. * Heterogeneous lists can specify their number of view types, so that this View is * always of the right type (see {@link #getViewTypeCount()} and * {@link #getItemViewType(int)}). * @param parent The parent that this view will eventually be attached to * @return A View corresponding to the data at the specified position. */ View getView(int position, View convertView, ViewGroup parent);
大家對於 convertView = null優化方法的使用已經瞭然於胸,但是我那個糾結,就知道是快取了listview裡面已經載入好的view(下文會講解)。
核心程式碼如下:
這部分程式碼很簡單,如果沒有快取就載入佈局,如果有快取就直接用convertView物件。所以這樣就不用滑動listview的時候
呼叫getView()方法每次都去載入佈局了(如果改佈局已經載入)。
View view; if(convertView == null){ view = LayoutInfalter.from(getContext()).inflate(resourceID,null) } else{ view = convertView }
表示寶寶一開始對 LayoutInfalter.from(getContext()).inflate(resourceID,null) 一臉矇蔽,然後找到了解釋。
//載入佈局管理器
LayoutInflater inflater = LayoutInflater.from(context);
//將xml佈局轉換為view物件
convertView = inflater.inflate(R.layout.item_myseallist,parent,
false
);
//利用view物件,找到佈局中的元件
convertView.findViewById(R.id.delete);
// 刪除
增加優化二:內部類ViewHolder的使用。
程式碼如下:主要優化getView方法中每次回撥用findviewByID()方法來獲取一次控制元件的程式碼。
新增加內部類ViewHolder,用於對控制元件的實力儲存進行快取。
- convertView為空時,viewHolder會將空間的實力存放在ViewHolder裡,然後用setTag方法講viewHolder物件儲存在view裡。
- convertView不為空時,用getTag方法獲取viewHolder物件.
//getView核心程式碼 ViewHolder viewHolder; if(convertView == null){ viewHolder = new ViewHolder(); viewHolder.fruitImage = (ImageView) view.findViewByID(R.id.fruit_image); view.setTage(viewHolder);//講ViewHolder儲存在View中 }else{ view = convertView; viewHolder = ViewHolder view.getTag();//重獲取viewHolder } viewHolder.fruitImage.setImageResource(fruit.getIMageID); //內部類 class ViewHolder{ ImageView fruitImage; }
可以看到方案一二目的很明確:第一個是優化載入佈局,第二個是優化載入控制元件。
回到我問題~convertView儲存的問題。
有沒有想過ListView載入成千上萬的資料為什麼不出OOM錯誤?
最主要的是因為RecycleBin機制。
- listview的許多view呈現在Ui上,這樣的View對我們來說是可見的,可以稱為OnScreen的view(也為ActionView)。
- view被上滾移除螢幕,這樣的view稱為offScreenView(也稱為ScrapView)。
- 然後ScrapView會被listview刪除,而RecycleView會將這部分儲存。
- 而listview底部需要顯示的view會從RecycleBin裡面取出一個ScrapView。
將其作為convertView引數傳遞過去,
- 從而達到View複用的目的,這樣就不必在Adapter的getView方法中執行LayoutInflater.inflate()方法了(不用載入佈局了有木有)。
在RecycleBin裡面有兩個陣列,看名字就知道了Actionview和ScrapViews.
/** * Views that were on screen at the start of layout. This array is populated at the start of * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews. * Views in mActiveViews represent a contiguous range of Views, with position of the first * view store in mFirstActivePosition. */ private View[] mActiveViews = new View[0]; /** * Unsorted views that can be used by the adapter as a convert view. */ private ArrayList<View>[] mScrapViews;
原理如下:
Google推薦優化方案:
public View getView(int position, View convertView, ViewGroup parent) { 3: Log.d("MyAdapter", "Position:" + position + "---" 4: + String.valueOf(System.currentTimeMillis())); 5: ViewHolder holder; 6: if (convertView == null) { 7: final LayoutInflater inflater = (LayoutInflater) mContext 8: .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 9: convertView = inflater.inflate(R.layout.list_item_icon_text, null); 10: holder = new ViewHolder(); 11: holder.icon = (ImageView) convertView.findViewById(R.id.icon); 12: holder.text = (TextView) convertView.findViewById(R.id.text); 13: convertView.setTag(holder); 14: } else { 15: holder = (ViewHolder) convertView.getTag(); 16: } 17: holder.icon.setImageResource(R.drawable.icon); 18: holder.text.setText(mData[position]); 19: return convertView; 20: } 21: 22: static class ViewHolder { 23: ImageView icon; 24: 25: TextView text;
推薦幾個連結:listview原始碼理解:很長,我是沒看完~http://www.bkjia.com/Androidjc/1037874.html。取其中一張圖,原始碼不是最新版本。
RecycleBin機制:http://www.2cto.com/kf/201604/497754.html。解決了我的疑問。
列舉下真正意義上的優化:http://www.xuanyusong.com/archives/1252。
致敬前輩。如果有錯誤請提出。