Android記憶體那點事兒,Android記憶體事兒

來源:互聯網
上載者:User

Android記憶體那點事兒,Android記憶體事兒

好久沒有寫了,不是忘了,也不是懶,是因為迷茫了~~不知道該學什麼,該寫什麼,該走什麼樣子的路,該做什麼樣子的人。我嘴笨,不知道怎麼把自己會的講給別人,我願意分享,所以我就寫出來,不管是對的,錯的,希望大家能取其精華去其糟粕,不要因為我而誤導諸位。廢話不多說了~~

你的應用記憶體流失了嗎?

要看是不是存在記憶體流失,首先我們要看到記憶體資訊,如何看到記憶體資訊呢?這裡介紹一種方法,開啟Eclipse串連手機,到DDMS中,選擇要分析的應用,點擊Update Heap也就是中1的表徵圖,點擊1表徵圖之後會在要分析的應用進程中出現2的表徵圖,在4的位置就能看到該應用的記憶體資訊。


記憶體資訊看到,那怎麼知道存在記憶體流失呢?由於這裡可以看到分析應用已申請的記憶體大小,也就是3位置的數值,所以可以根據這個值的變動來判斷是不是存在記憶體流失。那怎麼判斷呢?例如我們要判斷進入分析應用的第一個介面是不是存在記憶體流失,我們可以這樣來操作,先退出應用,然後點擊Cause GC,等3位置數值穩定了,記錄下來,然後進入應用,等第一個介面載入完成,再退出應用,再點擊Cause GC,等3位置的數值穩定了,再記錄下來,重複操作多次之後,來看我們記錄的數值,如果成增長趨勢,說明分析應用的第一個介面存在記憶體流失問題,如果水平趨勢,說明不存在記憶體流失問題。

哪裡的記憶體流失了?

要知道哪裡記憶體流失了,這就需要安裝一個叫MAT的工具了,如何安裝就去百度吧,這裡不再扯淡。同樣,進入開啟Eclipse串連手機,進入到DDMS中,選中待分析的應用,然後點擊中A的位置,也就是Dump HPROF file。


等待片刻,就會彈出如下視窗


選擇Leak Suspects Report,然後點擊Finish,就會到如下視窗



點擊A所指表徵圖,就會出現B所指表徵圖,然後在B中選擇Groupby class loader,就可以看到待分析應用的已申請對象資訊,Objects也就是C所在位置,就是申請對象個數,Shallow Heap是對象本身所佔記憶體大小,Retained Heap是對象本身和直接或間接引用到的對象的Shallow Heap之和,也就是還對象被GC之後所能回收到記憶體總和。通過這些數值怎麼判斷那裡記憶體流失了呢?還是接著上面的例子說明,我們進入第一個介面也就是第一個Activity,我們知道當退出Activity的時候就會釋放掉該Activity所佔用的資源,所以對象個數和你啟動Activity次數是沒有關係的,所以我們先記錄Objects個數,然後多次進出分析應用,再產生hprof檔案,再看某個Objects數量只增加不減少,說明該對象出現記憶體流失。當然A對象存在記憶體流失可能是應為B對象對A的引用造成,所以我們就要去找到到底是那個對象沒釋放導致記憶體流失。剩下的事情就靠自個了,一個一個檢查個數一直增長的對象吧。

Context對記憶體的威脅到底有多大?

這裡說的Context不是Application Context,而主要說的是Activity。以前在網上搜記憶體溢出如何處理的時候,總是說讓把Context釋放掉,這樣做了,但還是糊裡糊塗的。今天我們就說說這個Context。我們接著上面的問題開始,當我們監測到很多個物件的個數都在增長,並且這些對象的個數相等,就像C區出現很多個物件個數是2一樣,這時我們就要注意了,是不是有一個Context沒有釋放掉。在Activity中建立的不管是布局,Handler,還是Thread等等,都會儲存一個該Activity對象,而如果這些當中有一個不釋放該Activity,就會導致該Activity中所有對象都無法釋放,如果該Activity中有個大點Bitmap沒有主動釋放,那隱患就無法形容了。鄙人之前就碰到過一次,是Handler沒有釋放掉,導致Context沒有釋放,而Context中較大的Bitmap沒有釋放,導致每次進入該Activity記憶體以將近3M的速度增長,來回進入數次就OOM了。

Bitmap到底佔多大記憶體?


首先,先確定一下Bitmap圖片大小是怎麼計算的。我們來計算一個像素500x500的位元深度為24的Bitmap大小。我們都知道Bitmap的特點就是一個像素佔一個位元深度,例如位元深度為24,也就是一個像素佔24b(位),也就是3B。

大小(B)= 寬度(像素)*高度(像素)*位元深度/8。

大小(KB)= 大小(B)/1024。

所以上面我們提到的圖片大小就是:500*500*24/8/1024=732KB。

而這個bitmap在Android手機中的大小是多少呢?我們寫段代碼來測試一下,當然其它的對象大小也可以這麼來測試大小。

<span style="font-size:18px;">    private void testBitmapSize (Context context) {        BitmapDrawable drawable = (BitmapDrawable) context.getResources().getDrawable(R.drawable.test_bitmap);        Bitmap bitmap = drawable.getBitmap();        android.util.Log.d("my_test","start----------");        System.gc();        bitmap.recycle();        System.gc();        android.util.Log.d("my_test","end----------");    }</span>

我們抓取log如下:

D/my_test (14667): start----------

D/dalvikvm(14667): GC_EXPLICIT freed 9K,4% free 10122K/10503K, paused 1ms+4ms

D/dalvikvm(14667): GC_EXPLICIT freed976K, 13% free 9145K/10503K, paused 2ms+2ms

D/my_test (14667): end----------

根據這個我們可以估算出來這個Bitmap釋放之後,記憶體釋放了大概976KB大小,將近1M的記憶體。如果這個不去釋放,那麼很快就會溢出了。

什麼情況會導致Context不去釋放?

鄙人遇到的Context沒有釋放,是這樣子的,應用中有一個翻滾的標題,每隔一段時間就去翻滾一次,所以我就這麼去寫的:

從代碼中很容易看出來,每個5s,Handler就給自個發個訊息,而這個Handler在activity銷毀時沒有釋放,所以裡面的View所擁有的Context沒有釋放,而Context中有2M多的Bitmap沒有主動釋放,導致每次進入應用都會多一個Context對象,多出將近3M的空間。

Bitmap是否需要釋放?

就像上面說的,Context沒有釋放導致裡面的Bitmap沒有釋放,那如果Context釋放了裡面的Bitmap會釋放嗎答案是會的。鄙人遇到上面的記憶體流失時,在還沒有查到真正原因是,我發下裡面有2M的Bitmap沒有主動釋放掉,所以就在用完的時候主動去釋放了:

<p align="left"><span style="font-size:18px;"><strong><span style="color:#7F0055;">if</span></strong> (bitmap != <strong><span style="color:#7F0055;">null</span></strong> && !bitmap.isRecycled()) {</span></p><p align="left"><span style="font-size:18px;">    bitmap.recycle();</span></p><p align="left"><span style="font-size:18px;">    bitmap = <strong><span style="color:#7F0055;">null</span></strong>;</span></p><p><span style="font-size:18px;">}</span></p>

釋放後每次進入應用,確實少了2M多,但是還有將近1M的空間沒有釋放,我很納悶,就用mat工具去看,發現是第一個Activity產生的Context沒有釋放,接著就查到了,那個Handler訊息,所以就在Activity銷毀之前主動釋放掉Handler訊息:

<span style="font-size:18px;">    public void releaseRes () {        mContext = null;        if (mHandler != null && mHandler.hasMessages(HANDLER_SEARCH_HOT_WORD))            mHandler.removeMessages(HANDLER_SEARCH_HOT_WORD);        mHandler = null;    }</span>

結果我再去觀察應用所佔記憶體,沒有增加,基本保持在一個固定值附近。這是我又突發奇想,如果我的Bitmap不去釋放,會不會泄漏呢,所以我就把釋放Bitmap的地方注釋掉了。我又去驗證,進出應用多次,我發現應用基本上還是保持在那個固定值附近。所以我就認為,Bitmap會隨著Context的釋放而釋放掉的。問題來了,那為什麼還要主動去釋放Bitmap呢?那是因為在你還沒退出應用之前,這個Bitmap所佔的空間是存在的,既然不用了,佔著空間就是浪費,另外,如果某個地方的Bitmap沒有釋放,而這個地方會被重複調用,那後果也是不堪設想的。所以Bitmap不再使用的時候就要去釋放,不是一定,只要你能確保記憶體夠用。

Bitmap在哪些地方可能被忽略釋放?

這個最長出現的就是Bitmap被修改的地方:

<span style="font-size:18px;">        BitmapDrawable drawable = (BitmapDrawable) this.getResources().getDrawable(R.drawable.test_bitmap);        Bitmap bitmap = drawable.getBitmap();                android.util.Log.d("logTest","bitmap.getWidth()="+bitmap.getWidth());        android.util.Log.d("logTest","bitmap.getHeight()="+bitmap.getHeight());                Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, 500, 500);        Bitmap newBitmap2 = Bitmap.createBitmap(bitmap, 0, 0, 400, 400);                android.util.Log.d("logTest","bitmap="+bitmap);        android.util.Log.d("logTest","bitmap.isRecycled()="+bitmap.isRecycled());        android.util.Log.d("logTest","newBitmap="+newBitmap);        android.util.Log.d("logTest","newBitmap.isRecycled()="+newBitmap.isRecycled());        android.util.Log.d("logTest","newBitmap2="+newBitmap2);        android.util.Log.d("logTest","newBitmap2.isRecycled()="+newBitmap2.isRecycled());</span>

Log打出來是這樣子的:

D/logTest (27782):bitmap.getWidth()=500

D/logTest (27782):bitmap.getHeight()=500

D/logTest (27782):bitmap=android.graphics.Bitmap@4143c758

D/logTest (27782):bitmap.isRecycled()=false

D/logTest (27782):newBitmap=android.graphics.Bitmap@4143c758

D/logTest (27782):newBitmap.isRecycled()=false

D/logTest (27782):newBitmap2=android.graphics.Bitmap@4143a110

D/logTest (27782):newBitmap2.isRecycled()=false


很明顯了,當我們做圖片修改的時候,如果傳進入的Bitmap和輸出的Bitmap規格相同,那麼返回的就是原圖,同樣的地址;如果圖片做規格不同了,那麼就會重新建立一個Bitmap,同時原來的Bitmap並沒有釋放掉。這時就容易出問題了,我們會忘記釋放原來的Bitmap,如果多個圖片被重新修改,那麼記憶體流失就嚴重了,比如Listview中有圖片,並且在設定圖片之前我們會對圖片進行處理,那麼如果忘記釋放修改前的圖片,那麼後果就不堪設想。

<span style="font-size:18px;">    if (newBitmap != null && !newBitmap.isRecycled() && newBitmap != bitmap) {newBitmap.recycle();        newBitmap = null;    }</span>
這麼簡單的幾行代碼就能拯救你的記憶體。

能釋放布局中的圖片來節省記憶體嗎?

網上有中方法釋放掉布局中不需要的圖片:

<span style="font-size:18px;">        //設定圖片        BitmapDrawable drawable = (BitmapDrawable) this.getResources().getDrawable(R.drawable.test_bitmap);        mImageView.setBackgroundDrawable(drawable);                        //釋放圖片        BitmapDrawable drawableRel = (BitmapDrawable) mImageView.getBackground();        if (drawableRel != null) {            System.gc();            size = Runtime.getRuntime().totalMemory();            drawableRel.setCallback(null);            drawableRel.getBitmap().recycle();            drawableRel = null;            System.gc();            android.util.Log.d("test_my_log","rel_size="+(Runtime.getRuntime().totalMemory() - size));        }</span>

Log是這樣子的:

D/test_my_log(29891):rel_size=0

D/test_my_log(29891):rel_size=0

D/test_my_log(30292):rel_size=0

D/test_my_log(30292):rel_size=0

D/test_my_log(30292):rel_size=0


釋放記憶體的大小是0,這說明什麼,大家就自個體會吧。

今天就先扯這些了,以後有機會再接著扯,還是那句話,給大神們茶餘飯後取樂,給後來者拋磚引玉,對與錯大家自個判定,只是不要取笑鄙人就好~



聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.