ListView幾種寫法性能對比及優化(轉載)

ListView中性能優化

1、構造Adapter時沒有使用緩存convertView(衍生出ListView優化問題)

2、查詢數據庫遊標沒有關閉

3、Activity中生命週期對象大於Activity生命週期(關於Application Context與Activity Context)

4、Bitmap對象不使用時沒有recycle掉(這裏還有其他解決方案)


今天說的是第一種:如何使用緩存來優化ListView

因爲如果不使用緩存convertView的話,調用getView時每次都會重新創建View,這樣之前的View可能還沒有銷燬,加之不斷的新建View勢必會造成內存泄露。

使用getView時有3方案:(1)沒有使用convertView,(2)使用convertView,(3)使用convertView+靜態類ViewHolder

 

我做了一個測試,代碼在下面,創建2000個View,從0拉到最後,計算總共耗,同時顯示GC釋放內存的大小,三種測試的結果如下:

注:這裏先說下 GC_EXTERNAL_ALLOC freed 7K, 18% free 11153K/13511K, external 1632K/1672K, paused 89ms 的意思

  在Dalvik中,爲一個程序分配的內存要根據機型的不同而不同,一般爲32M,而虛擬機會把這些內存分別分配給,JAVA使用的堆內存(heap)和Nativie使用的內存(external)(即虛擬機中通過JNI調用本地Nativie的類中malloc分配的內存,如Bitmap,java.nio.ByteBuffers)。不過兩者不同共享,也就是說Native的內存不夠用了,而JAVA內存夠用時是不能向JAVA申請的,必須向虛擬機申請才行,當虛擬機無法分配的時候就會報OOM的錯誤

freed 7k:表示GC已經釋放了7K的內存

18% free 11153K/13511K:表示JAVA使用的堆內存(對象存在於此),18% free表示當前剩餘18%的堆內存(heap memory),11153K表示當前已用的堆內存,13511K表示堆內存總共大小(網上有些文章這部分弄錯了,很多轉載都是同一個)

external 1632K/1672K:1632K表示已用external memory,總共1672K external memory(注意:這個可能只存在於Android 3.0之前)

paused 89ms:這裏其實包括了兩部分,一個是在調用GC之前暫停的時間,一個是調用GC後基本完成時暫停的時間

詳細可參考:http://stackoverflow.com/questions/4550757/android-logs-gc-external-alloc-gc-for-malloc

 

(1)沒有使用convertView

  沒有任何處理,不建議這樣寫。如果數據量少可以,但是如果列表項數據量很大的時候,會每次都重新創建View,設置資源,嚴重影響性能,所以從一開始就不要用這種方式

複製代碼
@Override
        public View getView(int position, View convertView, ViewGroup parent) {
            //Get a View that displays the data at the specified position in the data set.
            //開始計時,性能測試用nanoTime會更精確,因爲它是納秒級的
            long startTime = System.nanoTime();
            View item = mInflater.inflate(R.layout.list_item, null);
            ImageView img = (ImageView)item.findViewById(R.id.img);
            TextView title = (TextView)item.findViewById(R.id.title);
            TextView info = (TextView)item.findViewById(R.id.info);
            img.setImageResource(R.drawable.ic_launcher);
            title.setText("loulijun");
            info.setText("www.cnblogs.com/loulijun");
            
            //停止計時
            long endTime = System.nanoTime();
            //耗時
            long spendTime = (endTime - startTime);
            
            sumTime += spendTime;
            Log.d("GoogleIO", "position at:"+position+"--sumTime:"+String.valueOf(sumTime));
            return item;
        }
複製代碼

測試結果:

目前VM只爲他們分配了5767K+518k的內存,而內存峯值是32M

剛開始時,而且heap memory只申請了5767K,已用內存3353K,注意數據大小的變化:耗時:167633055ns = 0.167633055秒

當拉到1000的時候,堆內存總計已經申請了9607K,已用內存7245K,明顯已經比剛開始時要大了 ,耗時:3435241667ns=3.435241667秒

當拉到2000的時候,堆內存總計13511K,已用內存11153K,耗時:6660369835ns = 6.660369835秒

---------------------------我又創建了10000個ListView,測試後直到內存泄露,證明峯值卻是是32M,而不使用convertView導致的內存泄露,當內存泄露時手機會提示force close,並將錯誤寫入/data/anr/traces.txt中,你可以adb pull下來查看具體信息

(2)使用convertView後的測試數據(優化後)

  通過緩存convertView,convertView可以緩存可視範圍內的convertView,當再次向下滑動時又開始更新View,這種利用緩存convertView的方式可以判斷如果緩存中不存在View才創建View,如果已經存在可以利用緩存中的View,這樣會減少很多View的創建,提升了性能

 

複製代碼
@Override
        public View getView(int position, View convertView, ViewGroup parent) {
            //Get a View that displays the data at the specified position in the data set.
            if(convertView == null)
            {
                convertView = mInflater.inflate(R.layout.list_item, null);
            }
            //開始計時,性能測試用nanoTime會更精確,因爲它是納秒級的
            long startTime = System.nanoTime();
            
            ImageView img = (ImageView)convertView.findViewById(R.id.img);
            TextView title = (TextView)convertView.findViewById(R.id.title);
            TextView info = (TextView)convertView.findViewById(R.id.info);
            img.setImageResource(R.drawable.ic_launcher);
            title.setText("loulijun");
            info.setText("www.cnblogs.com/loulijun");
            
            //停止計時
            long endTime = System.nanoTime();
            //耗時
            long spendTime = (endTime - startTime);
            
            sumTime += spendTime;
            Log.d("GoogleIO", "position at:"+position+"--sumTime:"+String.valueOf(sumTime));
            return convertView;
        }
複製代碼

 

測試數據我還是用2000吧,10000太大了(一萬年太久,只爭朝夕)

測試結果:

這次一直拉到最後明顯比剛纔流暢多了,而且GC釋放內存的次數也明顯少了很多,最後用的時間和當前使用的內存也小很多,優化後的確好多了

當position爲1000的時候,附近沒怎麼調用GC,用時:213653551ns=0.213653551秒,額,差距有點大,上面到達1000時用時達到3.43秒之多。

當position爲2000的時候,已用內存只有3068K,堆總共內存6215K,而且external memory是0K,用時:378326396ns = 0.378326396秒,性能差距如此之大,都有點不敢相信。也不知道這種方式對不對,如有不妥的地方,還希望大牛能給出正確回答

(3)使用contentView+靜態類ViewHolder類

  通過convertView+ViewHolder來實現,ViewHolder就是一個靜態類,使用 ViewHolder 的關鍵好處是緩存了顯示數據的視圖(View),加快了 UI 的響應速度。

當我們判斷 convertView == null  的時候,如果爲空,就會根據設計好的List的Item佈局(XML),來爲convertView賦值,並生成一個viewHolder來綁定converView裏面的各個View控件(XML佈局裏面的那些控件)。再用convertView的setTag將viewHolder設置到Tag中,以便系統第二次繪製ListView時從Tag中取出。(看下面代碼中)

如果convertView不爲空的時候,就會直接用convertView的getTag(),來獲得一個ViewHolder。

靜態類ViewHolder

複製代碼
//定義靜態類ViewHolder
    static class ViewHolder
    {
        public ImageView img;
        public TextView title;
        public TextView info;
    }
複製代碼
複製代碼
@Override
        public View getView(int position, View convertView, ViewGroup parent) {
            //Get a View that displays the data at the specified position in the data set.
            
            //開始計時,性能測試用nanoTime會更精確,因爲它是納秒級的
            long startTime = System.nanoTime();
            ViewHolder holder;
            
            if(convertView == null)
            {
                holder = new ViewHolder();
                convertView = mInflater.inflate(R.layout.list_item, null);
                holder.img = (ImageView)convertView.findViewById(R.id.img);
                holder.title = (TextView)convertView.findViewById(R.id.title);
                holder.info = (TextView)convertView.findViewById(R.id.info);
                convertView.setTag(holder);
            }else
            {
                holder = (ViewHolder)convertView.getTag();
                holder.img.setImageResource(R.drawable.ic_launcher);
                holder.title.setText("loulijun");
                holder.info.setText("www.cnblogs.com/loulijun");
            }
                
            //停止計時
            long endTime = System.nanoTime();
            //耗時
            long spendTime = (endTime - startTime);
            
            sumTime += spendTime;
            Log.d("GoogleIO", "position at:"+position+"--sumTime:"+String.valueOf(sumTime));
            return convertView;
        }
複製代碼

到這裏,可能會有人問ViewHolder靜態類結合緩存convertView與直接使用convertView有什麼區別嗎,是否重複了

在這裏,官方給出瞭解釋

提升Adapter的兩種方法

To work efficiently the adapter implemented here uses two techniques:
-It reuses the convertView passed to getView() to avoid inflating View when it is not necessary

(譯:重用緩存convertView傳遞給getView()方法來避免填充不必要的視圖)
-It uses the ViewHolder pattern to avoid calling findViewById() when it is not necessary

(譯:使用ViewHolder模式來避免沒有必要的調用findViewById():因爲太多的findViewById也會影響性能)
ViewHolder類的作用
-The ViewHolder pattern consists in storing a data structure in the tag of the view
returned by getView().This data structures contains references to the views we want to bind data to,
thus avoiding calling to findViewById() every time getView() is invoked

(譯:ViewHolder模式通過getView()方法返回的視圖的標籤(Tag)中存儲一個數據結構,這個數據結構包含了指向我們

要綁定數據的視圖的引用,從而避免每次調用getView()的時候調用findViewById())

 

測試數據:(跟直接使用convertView數據相差不多)

當position爲1000時,用時:199188216ns = 0.199188216秒,堆內存的時候也沒比沒有使用convertView理想的多

當position爲2000時,用時:336669887ns = 0.336669887秒,比直接使用convertView的方式稍微好一點點,不過性能相差不多

文章轉自:http://www.cnblogs.com/loulijun/archive/2012/04/10/2437888.html


發佈了28 篇原創文章 · 獲贊 61 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章