Bitmap導致的內存溢出

原本計劃是按照章節順序學習《Android開發藝術探索》這本書的,Android性能優化這部分也是本書的最後一章。但是週末的時候,友盟線下反饋的公司項目的一個錯誤讓我不得不提前學習這一塊的知識。先看看線下反饋的錯誤吧:

這裏寫圖片描述

java.lang.OutOfMemoryError:應用程序內存溢出,俗稱OOM,是指應用程序在申請內存時,沒有足夠的內存空間供其使用而出現的問題。Android中常見的導致內存溢出的場景有以下幾種:

1.靜態變量導致的內存溢出
2.單例模式導致的內存溢出
3.大量位圖的加載導致的內存溢出

這裏我們根據反饋上來崩潰日誌,可以很清楚的看到我們這次的內存溢出是由第三種原因導致的。那麼就從這個問題入手,找到解決Android中因Bitmap導致內存溢出的辦法。

一.檢測內存溢出

新版的Android Studio給我們提供了內存分析的可視化界面,但是精確的檢測並找到內存泄漏的原因,我們還需要第三方的工具。

推薦簡書上這篇文章,說的很詳細,這裏就不重複了。對照這篇文章一步一步進行即可:

使用新版Android Studio檢測內存泄露和性能

二.分析具體原因
項目中應用其實很常見,在客戶端選擇圖片以後,上傳到七牛,然後再把七牛返回的url存儲到服務器上。可問題就出現在圖片上傳到七牛的這一步,我們先看一下剛開始的代碼怎麼寫的:


    public static byte[] getSmallBitmap(String filePath) {
        Bitmap bitmap;
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(filePath, options);
        //獲取縮放比例
        options.inSampleSize = calculateInSampleSize(options, 1000, 1000);
        options.inJustDecodeBounds = false;
        bitmap = BitmapFactory.decodeFile(filePath, options);
        //bitmap轉bytes
        byte[] bytes = Bitmap2Bytes(bitmap);
        if (null != bitmap && !bitmap.isRecycled()) {
            bitmap.recycle();
        }
        return bytes;
    }

代碼其實就是將Bitmap轉化爲byte數組,然後將這個byte數組傳到七牛上,之前圖片處理一直是這樣做的,也沒有出現任何問題。爲什麼這次卻報了OOM呢,中場暫停一下,先來了解一下圖片相關的知識:

以我紅米2的測試機爲例,一張圖庫裏的圖片分辨率是1080*1920px,它的文件大小爲192KB,此時這張圖片是以文件的形式存在於硬盤上。那麼我們如果以Bitmap的形式將這張圖片加載到應用程序中,佔用的內存是多少呢:

1080*1920*4=8294400B=7.9M

圖片(BitMap)佔用的內存=圖片長度 * 圖片寬度*單位像素佔用的字節數
前兩個分別代表長度與寬度(像素單位),單位像素佔用字節數其大小由BitmapFactory.Options的inPreferredConfig變量決定。
inPreferredConfig爲Bitmap.Config類型,是個枚舉類型,對應如下:

這裏寫圖片描述

默認值爲ARGB_8888。一張質量並不高的圖片以Bitmap的形式加載到內存中佔用的內存就快8M,當一次性加載大量位圖的時候,肯定會遠遠超過應用程序所分配的內存空間,從而導致OOM。對於配置不高,系統版本過低,應用程序內存緊張的手機來說,出現這種情況的概率會大大增加。所以我們有必要對圖片進行必要的壓縮,減小內存,避免OOM。

三.優化過程:

圖片的壓縮分爲兩種:質量壓縮與尺寸壓縮,區別是質量壓縮並不會改變圖片的尺寸,而尺寸壓縮則會改變圖片的尺寸。那麼它們分別應用在哪些地方呢,還是以剛纔那張圖片爲例子。

當我把以文件形式存在在硬盤上的圖片,以Bitmap的形式加載到內存中的時候,我就必須進行尺寸壓縮,因爲質量壓縮並不會改變Bitmap所佔內存的大小,而尺寸壓縮由於是減小了圖片的像素,所以它直接對bitmap產生了影響,從而使所佔內存減小。代碼具體實現過程:

    public static byte[] getSmallBitmap(String filePath) {
        Bitmap bitmap;
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(filePath, options);
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        options.inSampleSize = calculateInSampleSize(options, 1000, 1000);
        options.inJustDecodeBounds = false;

        try {
            bitmap = BitmapFactory.decodeFile(filePath, options);
        } catch (Exception e) {
            options.inSampleSize = calculateInSampleSize(options, 500, 500);
            options.inJustDecodeBounds = false;
            bitmap = BitmapFactory.decodeFile(filePath, options);
        }
        byte[] bytes = Bitmap2Bytes(bitmap != null ? bitmap : null);
        if (null != bitmap && !bitmap.isRecycled()) {
            bitmap.recycle();
        }
        return bytes;
    }


   private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {
            final int heightRatio = Math.round((float) height / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);
            inSampleSize = heightRatio > widthRatio ? heightRatio : widthRatio;
        }
        return inSampleSize;
    }

代碼分析:

Bitmap的實例化是通過BitmapFactory提供的接口生成的,利用BitmapFactory可以從一個指定文件中,利用decodeFile()解出Bitmap,也可以定義的圖片資源中,利用decodeResource()解出Bitmap。它的主要方法及配置選項如下:

這裏寫圖片描述

實例化一個BitmapFactory.Options,並配置它的相關屬性:

options.inJustDecodeBounds = true,表示解析圖片的時候,只解析長度和寬度,不載入圖片,這樣就節省內存開支。
options.inPreferredConfig = Bitmap.Config.RGB_565,前文提到的表格一目瞭然,這樣會節省一半的內存。
options.inSampleSize = calculateInSampleSize(options, 1000, 1000),計算縮放的比例,inSampleSize只能是2的整數次冪,如果不是的話,向下取得最大的2的整數次冪,比如比例爲7,向下尋找2的整數次冪,就是4。如果縮放比例是4的話,7.9M的那張圖片最後佔用的內存會是7.9/16=0.49M,完全不用擔心OOM的發生。
options.inJustDecodeBounds = false,計算好壓縮比例後,去加載解析原圖。
bitmap = BitmapFactory.decodeFile(filePath, options),解析文件得到Bitmap。
與之前的代碼相比,優化之後我改變了options.inPreferredConfig的值,並且在轉換得到Bitmap的時候加上了try/catch,出現異常的話進一步擴大縮放比例,減小內存,防止OOM。

以上是尺寸壓縮的相關辦法,那麼質量壓縮又用在哪裏呢:

    private static byte[] Bitmap2Bytes(Bitmap bm) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bm.compress(Bitmap.CompressFormat.JPEG, 75, baos);
        return baos.toByteArray();
    }

當圖片從Bitmap的形式轉化爲二進制的形式(文件形式)時, 我們可以適當使用質量壓縮,加快傳遞速率。還是之前那張圖片,192KB的圖片經過以上方法的質量壓縮以後,大小爲144KB左右(雖然圖片大小變小,但是轉換爲Bitmap的時候,這張經過質量壓縮後的圖片所佔內存還是不會變的,仍然爲7.9M)。隨着手機硬件配置的提升,手機圖片的質量也越來越高,所以質量壓縮還是很必要的。

以上就是從這次Bitmap導致的內存溢出學習總結到的一些知識,希望能對你有所幫助。下一篇再見~~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章