Android ListView最佳化篇

來源:互聯網
上載者:User

標籤:

在我的上一篇部落格《Android ListView基礎篇》中陳列了ListView和adapter的多種結合方式的基本使用,在本篇文章中將具體講述如何通過多種方式處理好ListView的最佳化問題。


在上篇文章的例子中,我們使用了一張圖片和一個文本作為每一行的資料,發現效果已經完全達到了,而且沒出現什麼問題。但如果我們將Item的數量調大,比如調到 1000、10000、100000條資料,這個時候當你開啟ListView的時候,肯定會不禁感慨“什麼鬼,卡機了?!”等了好幾秒鐘,ListView才顯示出來,使用者體驗非常不好,特別是如果是要上市的項目,後果很嚴重!所以針對ListView的最佳化至關重要。


ListView記憶體調用機制的原理ListView消耗記憶體的主要地方就在於每一個ListItem的繪製,之前說過了,ListView的每一項的繪製的地方就在於Adapter的getView()方法中,getView()方法的返回值是一個View,這個View就是每一行的視圖,然後ListView再將其展示出來,那如果我們像之前那種寫法,不做任何修改,結果會是怎樣?

我們通過上一篇的例子做個測試,將行數調到100,在getView中列印一句Log看看:



Log列印結果:



可以看到,初始化ListView時getView運行了9次,而介面上剛好也僅顯示到第9條資料,也就是只有螢幕範圍內顯示的才會調用getView(),另外,可以看到它們的convertView都會null,然後我們再將介面稍微往下拖動,



再看Logcat:



注意到,第九項資料從底部開始進入介面,它的getView也調用了一遍,convertView依然為null,這是因為頂部的第一項資料還未完全脫離螢幕範圍外,也就是第一項的視圖還未進入Android的Recycler中,還不能被重用,我們再繼續往下滑:



Logcat:



發現第10項的convertView不為空白了!這是因為頂部的第一項資料已經完全離開了螢幕,所以Android會將它的convertView“推”進RecycleView中,然後第10行出現的時候,getView方法的convertView參數正是第一項存放在Recycler中的視圖。如:




ConvertView的重用瞭解了ListView的getView原理,我們就可以開始對它進行最佳化,上面提到了已經離開螢幕的convertView會被壓入Recycler中,那我們可以在 每次getView的一開始先判斷convertView是否為空白,不是為空白的話就直接用那個已經存在的convertView來直接進行操作,代碼如下:

@Overridepublic View getView(int position, View convertView, ViewGroup parent) {// TODO Auto-generated method stubLog.d("getView--->", convertView+"--position:"+position);if(convertView==null){convertView = inflater.inflate(R.layout.list_item, null);}TextView text = (TextView)convertView.findViewById(R.id.list_item_text);ImageView image = (ImageView)convertView.findViewById(R.id.list_item_image);text.setText(data.get(position).get("text").toString());image.setImageResource(Integer.parseInt(data.get(position).get("image").toString()));return convertView;}



運行滑動到如:



列印結果:



注意我圈起來的兩個地方,兩個地址一模一樣!所以我們成功重用了Recycle中緩衝的視圖,這樣可以有效最佳化ListView的記憶體消耗(試想一下,100000個視圖我來來回回只用那10個convertView,能不減少記憶體開銷嗎?)



ViewHolder的使用以上只是利用convertView的重用來做到最佳化效果,但是注意到還是有存在問題,每個視圖裡面有一個text和一個image,每次都要通過findViewByID來找到它們,這也是一件龐大的工程...那既然我們可以重用convertView, 那可不可以將這兩個子控制項也緩衝起來呢?
我們可以通過自訂一個 ViewHolder來,來進行子控制項視圖的緩衝,以達到更佳的最佳化效果:

@Overridepublic View getView(int position, View convertView, ViewGroup parent) {// TODO Auto-generated method stubLog.d("getView--->", convertView+"--position:"+position);ViewHolder holder = null;if(convertView==null){convertView = inflater.inflate(R.layout.list_item, null);holder = new ViewHolder();holder.text = (TextView)convertView.findViewById(R.id.list_item_text);holder.image = (ImageView)convertView.findViewById(R.id.list_item_image);convertView.setTag(holder);}else{holder = (ViewHolder)convertView.getTag();}holder.text.setText(data.get(position).get("text").toString());holder.image.setImageResource(Integer.parseInt(data.get(position).get("image").toString()));return convertView;}public static class ViewHolder{public TextView text;public ImageView image;}


程式碼分析:首先自訂了一個ViewHolder類,這裡設定為static這樣ViewHolder無論new多少次都是指向同一個記憶體空間,在ViewHolder類中添加了兩個成員變數,分別對應我們的子控制項。每次getView的時候,同樣先判斷ViewHolder對象是否為空白,如果為空白,就執行個體化一個ViewHolder對象,並將convertView通過findViewById找到的子控制項賦給holder,再將holder通過setTag()方法設定在convertView上,之後重用的時候可以通過convertView的getTag()來獲得。其實ViewHolder相當於我們子控制項的一個封裝類而已,通過這樣實現不用每次都去findViewById尋找子控制項,每次做的事情只是重用之前的視圖和控制項設定一下資料,達到最佳化的目的。



ListView多種子布局的重用方式上面的操作雖然已經對ListView進行了一些最佳化,但是依然存在問題,如果所有的ListItem的布局並不是都一樣(例如類似朋友圈,一些是圖片,一些是文字,一些是小視頻等等),就不能全部都用一樣的ViewHolder或者convertView來處理了,因為重用的布局不一定適合新出現的ListItem,ListView中提供了另外兩個方法: 
getItemViewType(int position)  【根據下標返回當前視圖的類型】
getViewTypeCount()                    【傳回型別的種類數】


代碼如下:

public class ListViewAdapter extends SimpleAdapter{private Context context;private List<Map<String,Object>> data;private LayoutInflater inflater;//注意,這裡定義的這些整型數要小於getViewTypeCount()所返回的那個數字,否則會報錯越界private final int TYPE_1 = 0;private final int TYPE_2 = 1;public ListViewAdapter(Context context,List<Map<String, Object>> data, int resource, String[] from,int[] to) {super(context, data, resource, from, to);// TODO Auto-generated constructor stubthis.context = context;this.data = data;inflater = LayoutInflater.from(context);}//返回資料的大小,即listview的行數@Overridepublic int getCount() {// TODO Auto-generated method stubreturn data.size();}//根據下標獲得某一行的資料@Overridepublic Object getItem(int position) {// TODO Auto-generated method stubreturn data.get(position);}//獲得指定的Item的下標@Overridepublic long getItemId(int position) {// TODO Auto-generated method stubreturn position;}@Overridepublic int getItemViewType(int position) {// TODO Auto-generated method stub//如果當前行是偶數行,傳回型別1if(position%2==0){return TYPE_1;}//如果當前行是奇數行,傳回型別2else{return TYPE_2;}}@Overridepublic int getViewTypeCount() {// TODO Auto-generated method stubreturn 2;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {// TODO Auto-generated method stubLog.d("getView--->", convertView+"--position:"+position);ViewHolder1 holder1 = null;ViewHolder2 holder2 = null;int type = getItemViewType(position);if(convertView==null){switch (type) {case TYPE_1:convertView = inflater.inflate(R.layout.list_item, null);holder1 = new ViewHolder1();holder1.text = (TextView)convertView.findViewById(R.id.list_item_text);holder1.image = (ImageView)convertView.findViewById(R.id.list_item_image);convertView.setTag(holder1);break;case TYPE_2:convertView = inflater.inflate(R.layout.list_item2, null);holder2 = new ViewHolder2();holder2.text = (TextView)convertView.findViewById(R.id.list_item_text2);holder2.detail = (TextView)convertView.findViewById(R.id.list_item_detail2);convertView.setTag(holder2);break;}}else{switch (type) {case TYPE_1:holder1 = (ViewHolder1)convertView.getTag();break;case TYPE_2:holder2 = (ViewHolder2)convertView.getTag();break;}}switch (type) {case TYPE_1:holder1.text.setText(data.get(position).get("text").toString());holder1.image.setImageResource(Integer.parseInt(data.get(position).get("image").toString()));break;case TYPE_2:holder2.text.setText(data.get(position).get("text").toString());holder2.detail.setText(data.get(position).get("text").toString());break;}return convertView;}public static class ViewHolder1{public TextView text;public ImageView image;}public static class ViewHolder2{public TextView text;public TextView detail;}}


程式碼分析:建立另外一個ViewHolder,用於載入和重用另外一種布局,其實就是在原來的基礎上,為每個操作都套上一層switch判斷,然後根據type的類型來分別設定兩種布局。



ListView非同步載入亂序問題出現亂序的原因上面的操作都是屬於同步載入每一行,所以不會出現什麼問題。但如果當我們是網路非同步載入每一行的圖片時,就會出現資料紊亂,前文已經說了,Android為ListView進行的Reycler的處理,減少記憶體開銷,那麼,每當有新的元素進入介面時就會回調getView()方法,而在getView()方法中會開啟非同步請求從網路上擷取圖片,注意網路操作都是比較耗時的,也就是說當我們 快速滑動ListView的時候就很有可能出現這樣一種情況,某一個位置上的元素進入螢幕後開始從網路上請求圖片,但是還沒等圖片下載完成,它就又被移出了螢幕。這種情況下會產生什麼樣的現象呢? 根據ListView的工作原理,被移出螢幕的控制項將會很快被新進入螢幕的元素重新利用起來,而如果在這個時候剛好前面發起的圖片請求有了響應,就會將剛才位置上的圖片顯示到當前位置上,因為雖然它們位置不同,但都是共用的同一個ImageView執行個體,這樣就出現了圖片亂序的情況。但是還沒完,新進入螢幕的元素它也會發起一條網路請求來擷取當前位置的圖片,等到圖片下載完的時候會設定到同樣的ImageView上面,因此就會出現先顯示一張圖片,然後又變成了另外一張圖片的情況。


如何解決亂序問題?由於篇幅問題這裡附上郭神的一篇博文http://blog.csdn.net/guolin_blog/article/details/45586553很詳細地講解了三種方式來解決非同步載入亂序。


總之,以上講述了ListView的多種最佳化方式,但是並不是萬能,也僅僅只是起到了一部分效果,真實開發中還要視情況而定,比如如果是多圖片,首先需要將圖片壓縮,並且不要再getView中做過多的耗時操作!希望本文對大家理解ListView的最佳化有所協助。

Android ListView最佳化篇

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.