Android Bitmap 的優化

1. 概述

Android 中的圖片是以 Bitmap 方式存在的,繪製的時候也是 Bitmap,直接影響到app運行時的內存。通過 ImageView 來顯示圖片,很多時候 ImageView 並沒有原始圖片的尺寸那麼大,這個時候把整個圖片加載進來後再設置給 ImageView,顯然是沒有必要的,因爲 ImageView 根本沒辦法顯示原始圖片。可以將圖片縮小後再加載進來,這樣圖片既能在 ImageView 顯示出來,又能降低內存佔用從而在一定程度上避免OOM,提高了 Bitmap 加載時的性能。在Android,Bitmap 所佔用的內存計算公式是:Bitmap所佔用的內存 = 圖片長度 x 圖片寬度 x 一個像素點佔用的字節數。只要通過改變這3個參數,任意減少一個的值,就達到了壓縮的效果。

2. 單個像素點佔用的字節數

單個像素的字節大小由 Bitmap 的一個可配置的參數 Config 來決定,Config 是枚舉類,定義了 Android中 支持的 Bitmap 配置:

Config 佔用字節大小(byte) 說明
ALPHA_8 1 每個像素存儲爲單個半透明(alpha)通道。
RGB_565 2 簡易RGB色調
ARGB_4444 4 已廢棄
ARGB_8888 4 默認圖片配置
RGBA_F16 8 Android 8.0 新增
HARDWARE Special Android 8.0 新增 (Bitmap直接存儲在graphic memory)

這個是理論結論,在實際使用過程中,如果設置成了其他標準。在很多情況下,是不生效的。Android系統會強行轉成使用ARGB_8888標準。

如下所示是不同 Config 下,同一張圖片的佔用的內存大小:

        if (type.equals("alpha_8")) {
            mConfig = Bitmap.Config.ALPHA_8;
        } else if (type.equals("8888")) {
            mConfig = Bitmap.Config.ARGB_8888;
        } else if (type.equals("565")) {
            mConfig = Bitmap.Config.RGB_565;
        } else if (type.equals("f16")) {
            mConfig = Bitmap.Config.RGBA_F16;
        } else if (type.equals("hardware")) {
            mConfig = Bitmap.Config.HARDWARE;
        }
        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inPreferredConfig = mConfig;
        try {
            mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg, opts);
        } catch (OutOfMemoryError error) {
            mBitmap = null;
        }
        Log.e("zzw", "config:  " + type);
        Log.e("zzw", "width:  " + mBitmap.getWidth() + "  height: " + mBitmap.getHeight());
        Log.e("zzw", "size:  " + mBitmap.getByteCount());
        mImg.setImageBitmap(mBitmap);

log 打印: 

2019-11-07 05:15:16.930 19676-19676/cn.zzw.bitmapdemo E/zzw: config:  alpha_8
2019-11-07 05:15:16.930 19676-19676/cn.zzw.bitmapdemo E/zzw: width:  1313  height: 738
2019-11-07 05:15:16.930 19676-19676/cn.zzw.bitmapdemo E/zzw: size:  3875976
2019-11-07 05:15:27.835 19676-19676/cn.zzw.bitmapdemo E/zzw: config:  8888
2019-11-07 05:15:27.835 19676-19676/cn.zzw.bitmapdemo E/zzw: width:  1313  height: 738
2019-11-07 05:15:27.835 19676-19676/cn.zzw.bitmapdemo E/zzw: size:  3875976
2019-11-07 05:15:34.442 19676-19676/cn.zzw.bitmapdemo E/zzw: config:  565
2019-11-07 05:15:34.442 19676-19676/cn.zzw.bitmapdemo E/zzw: width:  1313  height: 738
2019-11-07 05:15:34.442 19676-19676/cn.zzw.bitmapdemo E/zzw: size:  1937988
2019-11-07 05:15:39.743 19676-19676/cn.zzw.bitmapdemo E/zzw: config:  f16
2019-11-07 05:15:39.744 19676-19676/cn.zzw.bitmapdemo E/zzw: width:  1313  height: 738
2019-11-07 05:15:39.744 19676-19676/cn.zzw.bitmapdemo E/zzw: size:  7751952
2019-11-07 05:15:44.425 19676-19676/cn.zzw.bitmapdemo E/zzw: config:  hardware
2019-11-07 05:15:44.425 19676-19676/cn.zzw.bitmapdemo E/zzw: width:  1313  height: 738
2019-11-07 05:15:44.425 19676-19676/cn.zzw.bitmapdemo E/zzw: size:  3875976

3. Bitmap 壓縮

3.1 採樣率壓縮

採樣率壓縮其原理其實也是縮放 bitamp 的尺寸,通過調節其 inSampleSize 參數,比如調節爲2,寬高會爲原來的1/2,內存變回原來的1/4。

            BitmapFactory.Options opts = new BitmapFactory.Options();
            opts.inSampleSize = 2;
            mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg, opts);

 應用場景: 先獲取 Bitmap 的寬高,以及要顯示的大小,通過比較來決定 inSampleSize 的具體大小。在獲取 Bitmap 寬高的時候,設置 inJustDecodeBounds=true,這樣創建的 Bitmap 不佔用內存的。

3.2 質量壓縮

在保持像素的前提下改變圖片的位深及透明度等,來達到壓縮圖片的目的。bitmap 圖片的大小不會改變。

        BitmapFactory.Options opts = new BitmapFactory.Options();
        Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.timg, opts);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        bm.compress(Bitmap.CompressFormat.JPEG, 50, bos);
        byte[] bytes = bos.toByteArray();
        mBitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);

 應用場景:用於對圖片進行壓縮,用於分享圖片以及保存爲文件用於上傳到服務端。

3.3 縮放法壓縮

Android 中使用 Matrix 對圖像進行縮放、旋轉、平移、斜切等變換的。

            Matrix matrix = new Matrix();
            matrix.setScale(0.5f, 0.5f);
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg);
            mBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
            bitmap.recycle();
            bitmap = null;

應用場景:跟 3.1 中類似,3.1 中寬高同時進行縮放,這裏可以進行分佈對寬高進行縮放。

4. Bitmap 的複用

BitmapFactory.Options.inBitmap 字段,設置此字段之後解碼方法會嘗試複用一張存在的 Bitmap。Bitmap 的內存被複用,避免了內存的回收及申請過程,性能表現更佳。

            BitmapFactory.Options opts1 = new BitmapFactory.Options();
            opts1.inMutable=true;
            mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.na,opts1);
            BitmapFactory.Options opts2 = new BitmapFactory.Options();
            opts2.inBitmap=mBitmap;
            opts2.inSampleSize = 1;
            Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.timg,opts2);
            mImg.setImageBitmap(bitmap2);

用了 inBitmap 這個屬性,加載兩張,這兩張圖片會指向同一塊內存,而不用開闢兩塊內存空間。

inBitmap 的限制:

3.0-4.3:複用的圖片大小必須相同,編碼必須相同;4.4以上:複用的空間大於等於即可,編碼不必相同,不支持WebP。

圖片複用,這個屬性必須設置爲true:options.inMutable = true;

5. LruCache 的使用

關於 LruCache 的使用,參考此篇:LruCache 源碼解析

 

附上文章中的例子:

https://download.csdn.net/download/zzw0221/11967383

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