Bitmap優化詳談

楊充發表於2018-11-26

目錄介紹

  • 01.如何計算Bitmap佔用記憶體
    • 1.1 如何計算佔用記憶體
    • 1.2 上面方法計算記憶體對嗎
    • 1.3 一個畫素佔用多大記憶體
  • 02.Bitmap常見四種顏色格式
    • 2.1 什麼是bitmap
    • 2.2 Android常見是那種
    • 2.3 常見四種顏色格式介紹
    • 2.4 Bitmap到底有幾種顏色格式
  • 03.Bitmap壓縮技術
    • 3.1 質量壓縮
    • 3.2 取樣率壓縮
    • 3.3 縮放法壓縮
  • 04.Bitmap回收問題
    • 4.1 recycle()方法
    • 4.2 快取原理
    • 4.3 Bitmap的複用
  • 05.Bitmap常見操作
    • 5.1 Bitmap的壓縮方式
    • 5.2 Bitmap如何複用
    • 5.3 Bitmap使用API獲取記憶體
    • 5.4 該部落格對應測試專案地址

好訊息

  • 部落格筆記大彙總【16年3月到至今】,包括Java基礎及深入知識點,Android技術部落格,Python學習筆記等等,還包括平時開發中遇到的bug彙總,當然也在工作之餘收集了大量的面試題,長期更新維護並且修正,持續完善……開源的檔案是markdown格式的!同時也開源了生活部落格,從12年起,積累共計50篇[近30萬字],轉載請註明出處,謝謝!
  • 連結地址:github.com/yangchong21…
  • 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起於忽微,量變引起質變!
  • 輪播圖封裝庫:github.com/yangchong21…
  • 輕量級版本更新彈窗:github.com/yangchong21…
  • 通知欄封裝庫:github.com/yangchong21…

01.如何計算Bitmap佔用記憶體

1.1 如何計算佔用記憶體

  • 如果圖片要顯示下Android裝置上,ImageView最終是要載入Bitmap物件的,就要考慮單個Bitmap物件的記憶體佔用了,如何計算一張圖片的載入到記憶體的佔用呢?其實就是所有畫素的記憶體佔用總和:
  • bitmap記憶體大小 = 圖片長度 x 圖片寬度 x 單位畫素佔用的位元組數
  • 起決定因素就是最後那個引數了,Bitmap'常見有2種編碼方式:ARGB_8888和RGB_565,ARGB_8888每個畫素點4個byte,RGB_565是2個byte,一般都採用ARGB_8888這種。那麼常見的1080*1920的圖片記憶體佔用就是:1920 x 1080 x 4 = 7.9M

1.2 上面方法計算記憶體對嗎

  • 我看到好多部落格都是這樣計算的,但是這樣算對嗎?有沒有哥們試驗過這種方法正確性?我覺得看部落格要對博主表示懷疑,論證別人寫的是否正確。更多詳細可以看我的GitHub:github.com/yangchong21…
    • 說出我的結論:上面1.1這種說法也對,但是不全對,沒有說明場景,同時也忽略了一個影響項:Density。接下來看看原始碼。
    • inDensity預設為圖片所在資料夾對應的密度;inTargetDensity為當前系統密度。
    • 載入一張本地資源圖片,那麼它佔用的記憶體 = width * height * nTargetDensity/inDensity * nTargetDensity/inDensity * 一個畫素所佔的記憶體。
    @Nullable
    public static Bitmap decodeResourceStream(@Nullable Resources res, @Nullable TypedValue value,
            @Nullable InputStream is, @Nullable Rect pad, @Nullable Options opts) {
        validate(opts);
        if (opts == null) {
            opts = new Options();
        }
    
        if (opts.inDensity == 0 && value != null) {
            final int density = value.density;
            if (density == TypedValue.DENSITY_DEFAULT) {
                opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
            } else if (density != TypedValue.DENSITY_NONE) {
                opts.inDensity = density;
            }
        }
        
        if (opts.inTargetDensity == 0 && res != null) {
            opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
        }
        
        return decodeStream(is, pad, opts);
    }
    複製程式碼
  • 正確說法,這個注意呢?計算公式如下所示
    • 對資原始檔:width * height * nTargetDensity/inDensity * nTargetDensity/inDensity * 一個畫素所佔的記憶體;
    • 別的:width * height * 一個畫素所佔的記憶體;

1.3 一個畫素佔用多大記憶體

  • Bitmap.Config用來描述圖片的畫素是怎麼被儲存的?
    • ARGB_8888: 每個畫素4位元組. 共32位,預設設定。
    • Alpha_8: 只儲存透明度,共8位,1位元組。
    • ARGB_4444: 共16位,2位元組。
    • RGB_565:共16位,2位元組,只儲存RGB值。

02.Bitmap常見四種顏色格式

2.1 什麼是bitmap

  • 點陣圖檔案(Bitmap),副檔名可以是.bmp或者.dib。點陣圖是Windows標準格式圖形檔案,它將影象定義為由點(畫素)組成,每個點可以由多種色彩表示,包括2、4、8、16、24和32位色彩。點陣圖檔案是非壓縮格式的,需要佔用較大儲存空間。

2.2 Android常見是那種

  • 在Gesture類中
    • image
  • 在Notification類中
    • image
  • 在fw原始碼中bitmap圖片一般是以ARGB_8888(ARGB分別代表的是透明度,紅色,綠色,藍色,每個值分別用8bit來記錄,也就是一個畫素會佔用4byte,共32bit)來進行儲存的。

2.3 常見四種顏色格式介紹

  • 四種顏色格式如下所示
    • image
  • 說明
    • 在實際應用中而言,建議使用ARGB_8888以及RGB_565。 如果你不需要透明度,選擇RGB_565,可以減少一半的記憶體佔用。
    • ARGB_8888:ARGB分別代表的是透明度,紅色,綠色,藍色,每個值分別用8bit來記錄,也就是一個畫素會佔用4byte,共32bit.
    • ARGB_4444:ARGB的是每個值分別用4bit來記錄,一個畫素會佔用2byte,共16bit.
    • RGB_565:R=5bit,G=6bit,B=5bit,不存在透明度,每個畫素會佔用2byte,共16bit
    • ALPHA_8:該畫素只儲存透明度,會佔用1byte,共8bit.

2.4 Bitmap到底有幾種顏色格式

  • 上面我說到了常見的四種,言下之意應該不止四種,那到底有幾種呢?檢視原始碼可知,具體有6種型別。檢視Bitmap原始碼之Config配置。
    • image
  • 配置Config.HARDWARE為啥異常,看下面原始碼提示
    • image

03.Bitmap壓縮技術

3.1 質量壓縮

  • 質量壓縮方法:在保持畫素的前提下改變圖片的位深及透明度等,來達到壓縮圖片的目的,這樣適合去傳遞二進位制的圖片資料,比如分享圖片,要傳入二進位制資料過去,限制500kb之內。
    • 1、bitmap圖片的大小不會改變
    • 2、bytes.length是隨著quality變小而變小的。
    /**
     * 第一種:質量壓縮法
     * @param image     目標原圖
     * @param maxSize   最大的圖片大小
     * @return          bitmap,注意可以測試以下壓縮前後bitmap的大小值
     */
    public static Bitmap compressImage(Bitmap image , long maxSize) {
        int byteCount = image.getByteCount();
        Log.i("yc壓縮圖片","壓縮前大小"+byteCount);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // 把ByteArrayInputStream資料生成圖片
        Bitmap bitmap = null;
        // 質量壓縮方法,options的值是0-100,這裡100表示原來圖片的質量,不壓縮,把壓縮後的資料存放到baos中
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
        int options = 90;
        // 迴圈判斷如果壓縮後圖片是否大於maxSize,大於繼續壓縮
        while (baos.toByteArray().length  > maxSize) {
            // 重置baos即清空baos
            baos.reset();
            // 這裡壓縮options%,把壓縮後的資料存放到baos中
            image.compress(Bitmap.CompressFormat.JPEG, options, baos);
            // 每次都減少10,當為1的時候停止,options<10的時候,遞減1
            if(options == 1){
                break;
            }else if (options <= 10) {
                options -= 1;
            } else {
                options -= 10;
            }
        }
        byte[] bytes = baos.toByteArray();
        if (bytes.length != 0) {
            // 把壓縮後的資料baos存放到bytes中
            bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
            int byteCount1 = bitmap.getByteCount();
            Log.i("yc壓縮圖片","壓縮後大小"+byteCount1);
        }
        return bitmap;
    }
    
    
    /**
     * 第一種:質量壓縮法
     *
     * @param src           源圖片
     * @param maxByteSize   允許最大值位元組數
     * @param recycle       是否回收
     * @return              質量壓縮壓縮過的圖片
     */
    public static Bitmap compressByQuality(final Bitmap src, final long maxByteSize, final boolean recycle) {
        if (src == null || src.getWidth() == 0 || src.getHeight() == 0 || maxByteSize <= 0) {
            return null;
        }
        Log.i("yc壓縮圖片","壓縮前大小"+src.getByteCount());
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        src.compress(Bitmap.CompressFormat.JPEG, 100, baos);
        byte[] bytes;
        if (baos.size() <= maxByteSize) {// 最好質量的不大於最大位元組,則返回最佳質量
            bytes = baos.toByteArray();
        } else {
            baos.reset();
            src.compress(Bitmap.CompressFormat.JPEG, 0, baos);
            if (baos.size() >= maxByteSize) { // 最差質量不小於最大位元組,則返回最差質量
                bytes = baos.toByteArray();
            } else {
                // 二分法尋找最佳質量
                int st = 0;
                int end = 100;
                int mid = 0;
                while (st < end) {
                    mid = (st + end) / 2;
                    baos.reset();
                    src.compress(Bitmap.CompressFormat.JPEG, mid, baos);
                    int len = baos.size();
                    if (len == maxByteSize) {
                        break;
                    } else if (len > maxByteSize) {
                        end = mid - 1;
                    } else {
                        st = mid + 1;
                    }
                }
                if (end == mid - 1) {
                    baos.reset();
                    src.compress(Bitmap.CompressFormat.JPEG, st, baos);
                }
                bytes = baos.toByteArray();
            }
        }
        if (recycle && !src.isRecycled()){
            src.recycle();
        }
        Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
        Log.i("yc壓縮圖片","壓縮後大小"+bitmap.getByteCount());
        return bitmap;
    }
    
    
    /**
     * 第一種:質量壓縮法
     *
     * @param src     源圖片
     * @param quality 質量
     * @param recycle 是否回收
     * @return 質量壓縮後的圖片
     */
    public static Bitmap compressByQuality(final Bitmap src, @IntRange(from = 0, to = 100) final int quality, final boolean recycle) {
        if (src == null || src.getWidth() == 0 || src.getHeight() == 0) {
            return null;
        }
        Log.i("yc壓縮圖片","壓縮前大小"+src.getByteCount());
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        src.compress(Bitmap.CompressFormat.JPEG, quality, baos);
        byte[] bytes = baos.toByteArray();
        if (recycle && !src.isRecycled()) {
            src.recycle();
        }
        Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
        Log.i("yc壓縮圖片","壓縮後大小"+bitmap.getByteCount());
        return bitmap;
    }
    複製程式碼

3.2 取樣率壓縮

  • 什麼是取樣率壓縮?
    • 設定inSampleSize的值(int型別)後,假如設為n,則寬和高都為原來的1/n,寬高都減少,記憶體降低。上面的程式碼沒用過options.inJustDecodeBounds = true;因為我是固定來取樣的資料,為什麼這個壓縮方法叫取樣率壓縮?是因為配合inJustDecodeBounds,先獲取圖片的寬、高(這個過程就是取樣)。然後通過獲取的寬高,動態的設定inSampleSize的值。當inJustDecodeBounds設定為true的時候, BitmapFactory通過decodeResource或者decodeFile解碼圖片時,將會返回空(null)的Bitmap物件,這樣可以避免Bitmap的記憶體分配, 但是它可以返回Bitmap的寬度、高度以及MimeType。
    /**
     * 第二種:按取樣大小壓縮
     *
     * @param src        源圖片
     * @param sampleSize 取樣率大小
     * @param recycle    是否回收
     * @return 按取樣率壓縮後的圖片
     */
    public static Bitmap compressBySampleSize(final Bitmap src, final int sampleSize, final boolean recycle) {
        if (src == null || src.getWidth() == 0 || src.getHeight() == 0) {
            return null;
        }
        Log.i("yc壓縮圖片","壓縮前大小"+src.getByteCount());
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = sampleSize;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        src.compress(Bitmap.CompressFormat.JPEG, 100, baos);
        byte[] bytes = baos.toByteArray();
        if (recycle && !src.isRecycled()) {
            src.recycle();
        }
        Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
        Log.i("yc壓縮圖片","壓縮後大小"+bitmap.getByteCount());
        return bitmap;
    }
    
    
    /**
     * 第二種:按取樣大小壓縮
     *
     * @param src       源圖片
     * @param maxWidth  最大寬度
     * @param maxHeight 最大高度
     * @param recycle   是否回收
     * @return 按取樣率壓縮後的圖片
     */
    public static Bitmap compressBySampleSize(final Bitmap src, final int maxWidth, final int maxHeight, final boolean recycle) {
        if (src == null || src.getWidth() == 0 || src.getHeight() == 0) {
            return null;
        }
        Log.i("yc壓縮圖片","壓縮前大小"+src.getByteCount());
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        src.compress(Bitmap.CompressFormat.JPEG, 100, baos);
        byte[] bytes = baos.toByteArray();
        BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
        options.inJustDecodeBounds = false;
        if (recycle && !src.isRecycled()) {
            src.recycle();
        }
        Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
        Log.i("yc壓縮圖片","壓縮後大小"+bitmap.getByteCount());
        return bitmap;
    }
    
    /**
     * 計算獲取縮放比例inSampleSize
     */
    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;
        }
        final float totalPixels = width * height;
        final float totalReqPixelsCap = reqWidth * reqHeight * 2;
        while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
            inSampleSize++;
        }
        return inSampleSize;
    }
    複製程式碼

3.3 縮放法壓縮

  • Android中使用Matrix對影象進行縮放、旋轉、平移、斜切等變換的。
    • Matrix提供了一些方法來控制圖片變換:Matrix呼叫一系列set,pre,post方法時,可視為將這些方法插入到一個佇列。當然,按照佇列中從頭至尾的順序呼叫執行。其中pre表示在隊頭插入一個方法,post表示在隊尾插入一個方法。而set表示把當前佇列清空,並且總是位於佇列的最中間位置。當執行了一次set後:pre方法總是插入到set前部的佇列的最前面,post方法總是插入到set後部的佇列的最後面
    setTranslate(float dx,float dy):控制Matrix進行位移。
    setSkew(float kx,float ky):控制Matrix進行傾斜,kx、ky為X、Y方向上的比例。
    setSkew(float kx,float ky,float px,float py):控制Matrix以px、py為軸心進行傾斜,kx、ky為X、Y方向上的傾斜比例。
    setRotate(float degrees):控制Matrix進行depress角度的旋轉,軸心為(0,0)。
    setRotate(float degrees,float px,float py):控制Matrix進行depress角度的旋轉,軸心為(px,py)。
    setScale(float sx,float sy):設定Matrix進行縮放,sx、sy為X、Y方向上的縮放比例。
    setScale(float sx,float sy,float px,float py):設定Matrix以(px,py)為軸心進行縮放,sx、sy為X、Y方向上的縮放比例。
    複製程式碼
    • 縮放法壓縮工具類程式碼
    /**
     * 第三種:按縮放壓縮
     *
     * @param src                   源圖片
     * @param newWidth              新寬度
     * @param newHeight             新高度
     * @param recycle               是否回收
     * @return                      縮放壓縮後的圖片
     */
    public static Bitmap compressByScale(final Bitmap src, final int newWidth, final int newHeight, final boolean recycle) {
        return scale(src, newWidth, newHeight, recycle);
    }
    
    public static Bitmap compressByScale(final Bitmap src, final float scaleWidth, final float scaleHeight, final boolean recycle) {
        return scale(src, scaleWidth, scaleHeight, recycle);
    }
    
    /**
     * 縮放圖片
     *
     * @param src                   源圖片
     * @param scaleWidth            縮放寬度倍數
     * @param scaleHeight           縮放高度倍數
     * @param recycle               是否回收
     * @return                      縮放後的圖片
     */
    private static Bitmap scale(final Bitmap src, final float scaleWidth, final float scaleHeight, final boolean recycle) {
        if (src == null || src.getWidth() == 0 || src.getHeight() == 0) {
            return null;
        }
        Matrix matrix = new Matrix();
        matrix.setScale(scaleWidth, scaleHeight);
        Bitmap ret = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
        if (recycle && !src.isRecycled()) {
            src.recycle();
        }
        return ret;
    }
    複製程式碼

04.Bitmap回收問題

4.1 recycle()方法

  • 如何呼叫這個recycle()方法
    if (bitmap != null && !bitmap.isRecycled()) {
        bitmap.recycle();
        bitmap = null;
    }
    複製程式碼
  • 思考以下,為何呼叫recycle()需要做非空判斷?這裡可以引出bitmap系統回收功能。小楊我如果分析不對,歡迎反饋。
    • 首先看看原始碼……順便翻一下該方法的註釋!我是用有道翻譯的,大意如下:釋放與此點陣圖關聯的本機物件,並清除對畫素資料的引用。這將不會同步釋放畫素資料;如果沒有其他引用,它只允許垃圾收集。點陣圖被標記為“死”,這意味著如果呼叫getPixels()或setPixels(),它將丟擲異常,並且不會繪製任何東西。此操作不能反轉,因此只有在確定沒有進一步使用點陣圖的情況下才應呼叫該操作。這是一個高階呼叫,通常不需要呼叫,因為當沒有對此點陣圖的引用時,普通GC程式將釋放此記憶體。
    public void recycle() {
        if (!mRecycled && mNativePtr != 0) {
            if (nativeRecycle(mNativePtr)) {
                // return value indicates whether native pixel object was actually recycled.
                // false indicates that it is still in use at the native level and these
                // objects should not be collected now. They will be collected later when the
                // Bitmap itself is collected.
                mNinePatchChunk = null;
            }
            mRecycled = true;
        }
    }
    複製程式碼
  • 通常不需要呼叫?這是為啥?
    • 在Android3.0以後Bitmap是存放在堆中的,只要回收堆記憶體即可。官方建議我們3.0以後使用recycle()方法進行回收,該方法可以不主動呼叫,因為垃圾回收器會自動收集不可用的Bitmap物件進行回收。
    • 那麼何是進行回收呢?這裡面涉及到bitmap的快取演算法,還有GC回收垃圾機制。關於GC回收機制可以看我這篇部落格:blog.csdn.net/m0_37700275…
    • 大概就是移除最少使用的快取和使用最久的快取,先說出結論,下來接著分析!

4.2 快取原理

  • LruCache原理
    • LruCache是個泛型類,內部採用LinkedHashMap來實現快取機制,它提供get方法和put方法來獲取快取和新增快取,其最重要的方法trimToSize是用來移除最少使用的快取和使用最久的快取,並新增最新的快取到佇列中。

4.3 Bitmap的複用

  • Android3.0之後,並沒有強調Bitmap.recycle();而是強調Bitmap的複用。
    • 使用LruCache對Bitmap進行快取,當再次使用到這個Bitmap的時候直接獲取,而不用重走編碼流程。
    • Android3.0(API 11之後)引入了BitmapFactory.Options.inBitmap欄位,設定此欄位之後解碼方法會嘗試複用一張存在的Bitmap。這意味著Bitmap的記憶體被複用,避免了記憶體的回收及申請過程,顯然效能表現更佳。
    • 使用這個欄位有幾點限制:
      • 宣告可被複用的Bitmap必須設定inMutable為true;
      • Android4.4(API 19)之前只有格式為jpg、png,同等寬高(要求苛刻),inSampleSize為1的Bitmap才可以複用;
      • Android4.4(API 19)之前被複用的Bitmap的inPreferredConfig會覆蓋待分配記憶體的Bitmap設定的inPreferredConfig;
      • Android4.4(API 19)之後被複用的Bitmap的記憶體必須大於需要申請記憶體的Bitmap的記憶體;
      • Android4.4(API 19)之前待載入Bitmap的Options.inSampleSize必須明確指定為1。

05.Bitmap常見操作

5.1 Bitmap的壓縮方式

  • 常見壓縮方法Api
    • Bitmap.compress(),質量壓縮,不會對記憶體產生影響;
    • BitmapFactory.Options.inSampleSize,記憶體壓縮;
  • Bitmap.compress()
    • 質量壓縮,不會對記憶體產生影響
    • 它是在保持畫素的前提下改變圖片的位深及透明度等,來達到壓縮圖片的目的,不會減少圖片的畫素。進過它壓縮的圖片檔案大小會變小,但是解碼成bitmap後佔得記憶體是不變的。
  • BitmapFactory.Options.inSampleSize
    • 記憶體壓縮
    • 解碼圖片時,設定BitmapFactory.Options類的inJustDecodeBounds屬性為true,可以在Bitmap不被載入到記憶體的前提下,獲取Bitmap的原始寬高。而設定BitmapFactory.Options的inSampleSize屬性可以真實的壓縮Bitmap佔用的記憶體,載入更小記憶體的Bitmap。
    • 設定inSampleSize之後,Bitmap的寬、高都會縮小inSampleSize倍。例如:一張寬高為2048x1536的圖片,設定inSampleSize為4之後,實際載入到記憶體中的圖片寬高是512x384。佔有的記憶體就是0.75M而不是12M,足足節省了15倍。
    • 備註:inSampleSize值的大小不是隨便設、或者越大越好,需要根據實際情況來設定。inSampleSize比1小的話會被當做1,任何inSampleSize的值會被取接近2的冪值。

5.2 Bitmap如何複用

  • Bitmap複用的實驗,程式碼如下所示,然後看列印的日誌資訊
    • 從記憶體地址的列印可以看出,兩個物件其實是一個物件,Bitmap複用成功;
    • bitmapReuse佔用的記憶體(4346880)正好是bitmap佔用記憶體(1228800)的四分之一;
    • getByteCount()獲取到的是當前圖片應當所佔記憶體大小,getAllocationByteCount()獲取到的是被複用Bitmap真實佔用記憶體大小。雖然bitmapReuse的記憶體只有4346880,但是因為是複用的bitmap的記憶體,因而其真實佔用的記憶體大小是被複用的bitmap的記憶體大小(1228800)。這也是getAllocationByteCount()可能比getByteCount()大的原因。
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    private void initBitmap() {
        BitmapFactory.Options options = new BitmapFactory.Options();
        // 圖片複用,這個屬性必須設定;
        options.inMutable = true;
        // 手動設定縮放比例,使其取整數,方便計算、觀察資料;
        options.inDensity = 320;
        options.inTargetDensity = 320;
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bg_autumn_tree_min, options);
        // 物件記憶體地址;
        Log.i("ycBitmap", "bitmap = " + bitmap);
        Log.i("ycBitmap", "ByteCount = " + bitmap.getByteCount() + ":::bitmap:AllocationByteCount = " + bitmap.getAllocationByteCount());
        // 使用inBitmap屬性,這個屬性必須設定;
        options.inBitmap = bitmap; options.inDensity = 320;
        // 設定縮放寬高為原始寬高一半;
        options.inTargetDensity = 160;
        options.inMutable = true;
        Bitmap bitmapReuse = BitmapFactory.decodeResource(getResources(), R.drawable.bg_kites_min, options);
        // 複用物件的記憶體地址;
        Log.i("ycBitmap", "bitmapReuse = " + bitmapReuse);
        Log.i("ycBitmap", "bitmap:ByteCount = " + bitmap.getByteCount() + ":::bitmap:AllocationByteCount = " + bitmap.getAllocationByteCount());
        Log.i("ycBitmap", "bitmapReuse:ByteCount = " + bitmapReuse.getByteCount() + ":::bitmapReuse:AllocationByteCount = " + bitmapReuse.getAllocationByteCount());
    
        //11-26 18:24:07.971 15470-15470/com.yc.cn.ycbanner I/ycBitmap: bitmap = android.graphics.Bitmap@9739bff
        //11-26 18:24:07.972 15470-15470/com.yc.cn.ycbanner I/ycBitmap: bitmap:ByteCount = 4346880:::bitmap:AllocationByteCount = 4346880
        //11-26 18:24:07.994 15470-15470/com.yc.cn.ycbanner I/ycBitmap: bitmapReuse = android.graphics.Bitmap@9739bff
        //11-26 18:24:07.994 15470-15470/com.yc.cn.ycbanner I/ycBitmap: bitmap:ByteCount = 1228800:::bitmap:AllocationByteCount = 4346880
        //11-26 18:24:07.994 15470-15470/com.yc.cn.ycbanner I/ycBitmap: bitmapReuse:ByteCount = 1228800:::bitmapReuse:AllocationByteCount = 4346880
    }
    複製程式碼

5.3 Bitmap使用API獲取記憶體

  • getByteCount()
    • getByteCount()方法是在API12加入的,代表儲存Bitmap的色素需要的最少記憶體。API19開始getAllocationByteCount()方法代替了getByteCount()。
  • getAllocationByteCount()
    • API19之後,Bitmap加了一個Api:getAllocationByteCount();代表在記憶體中為Bitmap分配的記憶體大小。
    public final int getAllocationByteCount() {
        if (mRecycled) {
            Log.w(TAG, "Called getAllocationByteCount() on a recycle()'d bitmap! "
                    + "This is undefined behavior!");
            return 0;
        }
        return nativeGetAllocationByteCount(mNativePtr);
    }
    複製程式碼
  • 思考: getByteCount()與getAllocationByteCount()的區別?
    • 一般情況下兩者是相等的;
    • 通過複用Bitmap來解碼圖片,如果被複用的Bitmap的記憶體比待分配記憶體的Bitmap大,那麼getByteCount()表示新解碼圖片佔用記憶體的大小(並非實際記憶體大小,實際大小是複用的那個Bitmap的大小),getAllocationByteCount()表示被複用Bitmap真實佔用的記憶體大小(即mBuffer的長度)。
  • 在複用Bitmap的情況下,getAllocationByteCount()可能會比getByteCount()大。

5.4 該部落格對應測試專案地址

關於其他內容介紹

01.關於部落格彙總連結

02.關於我的部落格

相關文章