Android ListView內存優化

文章摘自:http://www.eoeandroid.com/thread-211498-1-1.html

原文:http://www.cnblogs.com/loulijun/archive/2012/04/10/2437888.html

引起android內存泄漏的原因很多,下面羅列一些原因:

1.使用BaseAdapter自定義ListView的適配器的時候沒有使用convertView對ListView的View對象進行優化(衍生出了listView的優化與內存的問題)

2.使用SQLite數據庫保存數據的時候,使用完畢之後沒有關閉遊標指針,浪費了大量的內存在Curse對象

3.Activty生命週期對象大於Activity的生命週期<Activity>Appliaction>即Activity關閉之後,沒有銷燬Activity中創建的對象

4.加載圖片的時候,使用的Bitmap對象沒有釋放,Bitmap是將圖片讀取到內存中,所以一般佔用比較大的內存,使用不當的話,很容易造成ANR(Application Not Response)


首先我們對第一個內存的問題進行討論:使用convertView對ListView進行優化

因爲如果不使用緩存convertView的話,調用getView時每次都會重新創建View,這樣之前的View可能還沒有銷燬,加之不斷的新建View勢必會造成內存泄露。
使用getView時有3方案:(1)沒有使用convertView,(2)使用convertView,(3)使用convertView+靜態類ViewHolder


使用測試程序,對上面的三種情況進行計時計算,在ListView的項item比較少的時候,將看不出來明顯的時間差異,所以測試的時候,將item加載的比較多的時候,來看看測試不同方式所使用的時間的差距 。選擇的Item的項爲0-2000項<從第0項一直下拉到第2000項>計算總共的時耗,同時顯示GC釋放的內存的大小

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

在Dalvik中,爲一個應用程序(Application)分配的內存一般是32M,但有時候也得根據機型的不同,分配稍微有些差異,JAVA使用的堆內存(Heap Memory)和Native使用的(external 即虛擬機中通過JNI調用本地的Native類中malloc分配的內存如Bitmap,java.nio.ByteBuffers)不過兩者不同共享,也就是說當Native的內存不夠用的時候,而Java內存夠用的時候,不能夠向Java申請內存的,他們倆屬於不用的內存模塊,必須向虛擬機申請內存,當虛擬機的內存出現不夠的情況下就會出現OOM(out of memory)

free 7K表示已經釋放了7K的內存

18%free  11153K/13511K:表示Java使用的堆內存(對象存在於此),18% free表示當前剩餘18&的內存(heap memory ) 11153K表示當前已經用了的堆內存,13511K表示總共堆內存的大小

external 1632K/1672K 1632K表示已用的external memory,總共1672K external memory(注意,這些可能只存在Android3.0之前)

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

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

(沒有使用convertView)

沒有使用任何的處理,不建議這樣寫,如果數據量比較少的情況下還算可行,但如果列表項的數據量很大的情況下,回調方法getView在每次滑動的時候都會調用,產生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的時候,堆內存已經申請了9067K,已用內存7245K 明顯比剛開始的時候要大了,耗時:167633055ns = 0.167633055秒

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


當又創建1000個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爲兩千的時候,已用內存只有3068K,堆內存只用了6215K,而且external memory是0K,用時:378326396ns = 0.378326396秒,性能差距如此之大,都有點不敢相信。也不知道這種方式對不對,如有不妥的地方,還希望大牛能給出正確回答

<3>使用ConverView+ViewHolder靜態類

通過ConvertIView+ViewHolder類來實現,ViewHolder就是一個靜態類,使用靜態類的好處關鍵在於緩存了顯示數據的視圖(View),加快了View的響應速度,當我們判斷ConvertView==null的時候,如果爲空,就會根據設計好的List加載Item佈局(XML)

靜態類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的方式稍微好一點點,不過性能相差不多



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

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