Android性能調優(yōu)篇之內存溢出

開篇廢話

上一篇我們了解了Android里面相關的內存泄露以及相應的處理方案秋茫,這一篇胡诗,接著上一篇的內存泄露的內容燕锥,講一下Android當中的內存溢出子刮。

內存溢出與內存泄露客叉,很多開發(fā)人員都容易產生混淆,有可能是因為這兩個概念有點關系话告,又因為名稱上也不太好區(qū)分吧。不過卵慰,我們依然要清楚沙郭,內存溢出(Out Of Memory Error) 與 內存泄露 (Memory Leak)還是有質的區(qū)別的。都我們的App多次出現(xiàn)內存泄露裳朋,可能就會導致內存溢出病线。

但是,我們的App出現(xiàn)內存溢出鲤嫡,不一定就是因為內存泄露送挑,因為本身Android系統(tǒng)分配給每一個的App的空間就是那么一點。

另外暖眼,內存泄露也不一定就會出現(xiàn)內存溢出惕耕,因為還是泄露的速度比較慢,系統(tǒng)將進程殺死了诫肠,也就不會內存溢出咯司澎,不過,發(fā)現(xiàn)內存泄露栋豫,我們還是要第一時間解決掉這個bug挤安。


技術詳情

講述邏輯如下:

1.什么是內存溢出

2.有些內存里面容易混淆的概念

3.如何解決內存溢出

1.什么是內存溢出

內存溢出,OOM(Out Of Memory),表示當前占用的內存加上我們申請的內存資源超過了Dalvik虛擬機的最大內存限制就會拋出的Out Of Memory異常丧鸯。大部分的OOM的問題蛤铜,都會與Bitmap的加載有關系

2.內存里面容易混淆的一些概念

主要有三個概念:

1.內存溢出

2.內存抖動

3.內存泄露

其中第一個內存溢出,就是剛剛講的OOM,第三個內存泄露丛肢,可以查看我的上一篇文章围肥。

關于第二個內存抖動,出現(xiàn)的情況是蜂怎,短時間內虐先,大量的對象被創(chuàng)建,然后又馬上被釋放派敷,瞬間產生的對象會嚴重占用內存區(qū)域蛹批,這個區(qū)域就是我們之前接觸的那個年輕代區(qū)域撰洗,到達這個區(qū)域的閾值時就會觸發(fā)minor gc,當出現(xiàn)頻繁的minor gc的時候,就會出現(xiàn)內存抖動腐芍,我們能夠通過我們的Android Studio的Memory Monitor能夠非常直觀的看到內存抖動

內存抖動

出現(xiàn)內存抖動的現(xiàn)象差导,可根據(jù)當前app處理的實際業(yè)務結合Memory Monitor中的現(xiàn)象來進行判斷,然后有針對性的進行優(yōu)化猪勇。

它們三者的重要等級分別:內存溢出 > 內存泄露 > 內存抖動

內存溢出對我們的App來說设褐,影響是非常大的,整得不好泣刹,就有可能導致程序閃退助析,無響應等現(xiàn)象,因此椅您,我們一定要優(yōu)先解決OOM的問題外冀。

3.如何解決內存溢出

如何解決OOM,這個問題范圍比較大掀泳,我這邊大概從兩個方面去講述:

1.關于Bitmap的OOM

2.除了Bitmap之外的OOM

3.1 關于Bitmap的OOM

關于Bitmap的OOM我們有幾點需要注意的雪隧。

3.1.1 ImageView等控件圖片的顯示

意思就是加載合適屬性的圖片,當我們有些場景是可以顯示縮略圖的時候员舵,就不要調用網(wǎng)絡請求加載大圖脑沿,例如在ListView中,我們在上下滑動的時候马僻,就不要去調用網(wǎng)絡請求庄拇,當監(jiān)聽到滑動結束的時候,才去加載大圖韭邓,以免上下滑動的時候產生卡頓現(xiàn)象丛忆。

3.1.2 及時釋放內存

我們知道,在Android系統(tǒng)中仍秤,本身就有自己的垃圾回收機制熄诡,系統(tǒng)會不定期進行垃圾回收的。但是诗力,這個只是針對Java那一塊的內存,但是我們需要知道Bitmap實例化的時候凰浮,是通過JNI的方式,所以還有一部分的內存是C那一塊的苇本,我們的GC沒有辦法回收袜茧,所以,我們在不用的時候瓣窄,還是需要調用recycle()方法笛厦,源碼里面,recycle()方法其實就是調用的JNI的函數(shù)俺夕,然后釋放C那一塊的內存裳凸。

3.1.3 把圖片進行壓縮

我們在實際開發(fā)過程當中贱鄙,可能因為業(yè)務需要,需要加載一張很大的圖片姨谷,大到直接可以超過系統(tǒng)分配給我們App的內存大小逗宁,這樣,就會直接導致內存溢出梦湘,那么瞎颗,這個時候,我們就應當控制圖片的大小捌议,那么就應該將bitmap進行壓縮了哼拔。

下面大概講一下對一張圖片進行壓縮的一個過程。

第一步:計算實際采樣率

/**
 * 計算壓縮比例值
 * @param options             解析圖片的配置信息
 * @param reqWidth            所需圖片壓縮尺寸最小寬度
 * @param reqHeight           所需圖片壓縮尺寸最小高度
 * @return
 */
public static int calculateInSampleSize(BitmapFactory.Options options,
         int reqWidth, int reqHeight) {
   //保存圖片原寬高值
   final int height = options. outHeight;
   final int width = options. outWidth;

   //初始化壓縮比例為1
   int inSampleSize = 1;

   //當圖片寬高值任何一個大于所需壓縮圖片寬高值時,進入循環(huán)計算系統(tǒng)
   if (height > reqHeight || width > reqWidth) {

         final int halfHeight = height / 2;
         final int halfWidth = width / 2;

         //壓縮比例值每次循環(huán)兩倍增加,
         //直到原圖寬高值的一半除以壓縮值后都~大于所需寬高值為止
         while ((halfHeight / inSampleSize) >= reqHeight
                    && (halfWidth / inSampleSize) >= reqWidth) {
              inSampleSize *= 2;
        }
   }
   return inSampleSize;
}

第二步:根據(jù)得到的采樣率對圖片進行解析

/**
 * 獲取壓縮后的圖片
 * @param res
 * @param resId
 * @param reqWidth            所需圖片壓縮尺寸最小寬度
 * @param reqHeight           所需圖片壓縮尺寸最小高度
 * @return
 */
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
    int reqWidth, int reqHeight) {

    //首先不加載圖片,僅獲取圖片尺寸
    final BitmapFactory.Options options = new BitmapFactory.Options();

    //當inJustDecodeBounds設為true時,不會加載圖片僅獲取圖片尺寸信息
    options.inJustDecodeBounds = true;

    //此時僅會將圖片信息會保存至options對象內,decode方法不會返回bitmap對象
    BitmapFactory.decodeResource(res, resId, options);

    //計算壓縮比例,如inSampleSize=4時,圖片會壓縮成原圖的1/4
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    //當inJustDecodeBounds設為false時,BitmapFactory.decode...就會返回圖片對象了
    options. inJustDecodeBounds = false;

    //利用計算的比例值獲取壓縮后的圖片對象
    return BitmapFactory.decodeResource(res, resId, options);
}

3.1.4 使用Bitmap的高級屬性inBitmap

Bitmap的inBitmap高級屬性主要是值復用內存塊瓣颅,不需要在重新給新的bitmap對象申請一塊新的內存倦逐,避免了一次內存的分配和回收,從而提供了我們程序運行的效率弄捕。

不過這個屬性還是有一些坑的,對于適配Android3.0以上 导帝。而且守谓,這個功能,google一直在優(yōu)化當中您单,在Android4.4以前斋荞,只能復用相同大小的bitmap內存,而4.4之后虐秦,則只要比之前的內存小平酿,就可以了。以下貼出inBitmap的簡單使用方法:

第一步:首先判斷當前圖片是否能夠使用inBitmap

/**
 * 判斷是否能夠使用inBigmap
 * @param candidate         比較標準
 * @param targetOptions     判斷目標對象屬性
 * @return
 */
public static boolean canUseForInBitmap(
    Bitmap candidate, BitmapFactory.Options targetOptions) {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        // From Android 4.4 (KitKat) onward we can re-use
        // if the byte size of the new bitmap is smaller than
        // the reusable bitmap candidate
        // allocation byte count.
        int width = targetOptions.outWidth / targetOptions.inSampleSize;
        int height =
            targetOptions.outHeight / targetOptions.inSampleSize;
        int byteCount = width * height
            * getBytesPerPixel(candidate.getConfig());
        return byteCount <= candidate.getAllocationByteCount();
    }

    // On earlier versions,
    // the dimensions must match exactly and the inSampleSize must be 1
    return candidate.getWidth() == targetOptions.outWidth
        && candidate.getHeight() == targetOptions.outHeight
        && targetOptions.inSampleSize == 1;
}

第二步:從緩存里面拿出bitmap,將此Bitmap賦值給inBitmap悦陋。

/**
 * 將Bitmap賦值給inBitmap
 * @param options             圖片的配置信息
 * @param cache               圖片緩存
 */
private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) {

    //inBitmap only works with mutable bitmaps, so force the decoder to
    //return mutable bitmaps.
    options.inMutable = true;

    if (cache != null) {
        // Try to find a bitmap to use for inBitmap.
        Bitmap inBitmap = cache.getBitmapFromReusableSet(options);

        if (inBitmap != null) {
            // If a suitable bitmap has been found,
            // set it as the value of inBitmap.
            options.inBitmap = inBitmap;
        }
    }
}

第三步:調用剛剛圖片壓縮時候的decode方法蜈彼,把options參數(shù)傳入

3.1.5 捕獲異常

很多時候,當內存確實很吃緊的時候俺驶,難免還是會出現(xiàn)OOM幸逆,所以,根據(jù)經(jīng)驗之談暮现,我們在開發(fā)過程中还绘,實例化Bitmap的時候,最好還是添加try catch栖袋,進行異常捕獲拍顷。

需要注意,平常的Exception異常是捕獲不到OOM Erro的塘幅,因為OOM是一個錯誤昔案,我們編碼的時候需要捕獲錯誤尿贫,具體給出以下示例代碼:

public static Bitmap createBitmap(int width, int height, Bitmap.Config config) {  
    Bitmap bitmap = null;  
    try {  
        bitmap = Bitmap.createBitmap(width, height, config);  
    } catch (OutOfMemoryError e) {  
        while(bitmap == null) {  
            System.gc();  
            System.runFinalization();  
            bitmap = createBitmap(width, height, config);  
        }  
    }  
} 

3.2 除了Bitmap之外的OOM

3.2.1 listview

這個listview確實提到了好多次,畢竟我們實際開發(fā)當中爱沟,用它來呈現(xiàn)一些數(shù)據(jù)確實的頻率也蠻高帅霜,還是需要講述一下。

使用listview的時候呼伸,一定要記得復用convertView

同時身冀,在listview當中,如果需要顯示大圖的控件括享,記得使用LRU(最近最少使用搂根,三級緩存)機制進行緩存圖片

3.2.2 onDraw方法當中,盡量避免對象的創(chuàng)建

如果在onDraw方法中創(chuàng)建對象铃辖,會觸發(fā)頻繁的GC,也就是之前提到的內存抖動剩愧,當內存抖動積累到一定的程度,也會出現(xiàn)內存溢出娇斩。

3.2.3 使用多進程仁卷,一定要小心小心再小心

我們有的時候需要將一些服務,或者主件放到另外一個進程去運行犬第,例如一些定位锦积,推送等,這樣確實可以分擔主進程的內存壓力歉嗓。

但是丰介,多進程中的一些通信真心沒有那么簡單。很多機制可能失效鉴分,從而影響業(yè)務的基本功能哮幢。可能會出現(xiàn)一些莫名其妙的crash.

所以志珍,如果我們的App實際業(yè)務沒有達到一定程度橙垢,真心不要使用多進程。


干貨總結

此篇文章根據(jù)OOM是什么伦糯,了解一些容易混淆的概念钢悲,然后熟悉一些OOM的解決方案這個邏輯,再結合實際開發(fā)可能遇到的問題舔株,講述了內存溢出的相關知識莺琳。其實,大篇幅都是在講述Bitmap的處理方案载慈,因為惭等,我們這個Bitmap確實在實際開發(fā)當中引發(fā)OOM的概率還是相當大的。

希望通過以上的講述办铡,我們能夠對于OOM有一個清晰的了解辞做,從而根據(jù)我們實際開發(fā)當中自己的業(yè)務琳要,進行OOM的優(yōu)化。

其實有的時候秤茅,我們在解決OOM的時候需要有一個權衡稚补,因為如果考慮到了OOM的情況而頻繁觸發(fā)GC,可能會導致UI卡頓的現(xiàn)象,跟嚴重的可能出現(xiàn)ANR的問題框喳,需要我們在實際開發(fā)過程中具體場景具體分析课幕。

好了,內存的泄露的知識就先更新到這了五垮,如果覺得本篇文章對大家有益乍惊,請給予一個贊和喜歡,這樣我才更有動力一直更新下去放仗,如果想和我一起探討的润绎,可以關注一波。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末诞挨,一起剝皮案震驚了整個濱河市莉撇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌惶傻,老刑警劉巖棍郎,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異达罗,居然都是意外死亡坝撑,警方通過查閱死者的電腦和手機静秆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門粮揉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抚笔,你說我怎么就攤上這事扶认。” “怎么了殊橙?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵辐宾,是天一觀的道長。 經(jīng)常有香客問我膨蛮,道長叠纹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任敞葛,我火速辦了婚禮誉察,結果婚禮上,老公的妹妹穿的比我還像新娘惹谐。我一直安慰自己持偏,他們只是感情好驼卖,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鸿秆,像睡著了一般酌畜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卿叽,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天桥胞,我揣著相機與錄音,去河邊找鬼附帽。 笑死埠戳,一個胖子當著我的面吹牛,可吹牛的內容都是我干的蕉扮。 我是一名探鬼主播整胃,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼喳钟!你這毒婦竟也來了屁使?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤奔则,失蹤者是張志新(化名)和其女友劉穎蛮寂,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體易茬,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡酬蹋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了抽莱。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片范抓。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖食铐,靈堂內的尸體忽然破棺而出匕垫,到底是詐尸還是另有隱情,我是刑警寧澤虐呻,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布象泵,位于F島的核電站,受9級特大地震影響斟叼,放射性物質發(fā)生泄漏偶惠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一朗涩、第九天 我趴在偏房一處隱蔽的房頂上張望忽孽。 院中可真熱鬧,春花似錦、人聲如沸扒腕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瘾腰。三九已至皆的,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蹋盆,已是汗流浹背费薄。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留栖雾,地道東北人楞抡。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像析藕,于是被迫代替她去往敵國和親召廷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內容