內存泄露--contentView緩存使用與ListView優化

引發Android內存泄露有不少種緣由,下面羅列了一些問題,之後會一一解決java

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

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

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

四、Bitmap對象不使用時沒有recycle掉(這裏還有其餘解決方案)數據結構

 

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

由於若是不使用緩存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的方式稍微好一點點,不過性能相差很少

相關文章
相關標籤/搜索