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,這說明什麼,大家就自個體會吧。
今天就先扯這些了,以後有機會再接著扯,還是那句話,給大神們茶餘飯後取樂,給後來者拋磚引玉,對與錯大家自個判定,只是不要取笑鄙人就好~