Bitmap優(yōu)化詳談

目錄介紹

  • 01.如何計算Bitmap占用內(nèi)存
    • 1.1 如何計算占用內(nèi)存
    • 1.2 上面方法計算內(nèi)存對嗎
    • 1.3 一個像素占用多大內(nèi)存
  • 02.Bitmap常見四種顏色格式
    • 2.1 什么是bitmap
    • 2.2 Android常見是那種
    • 2.3 常見四種顏色格式介紹
    • 2.4 Bitmap到底有幾種顏色格式
  • 03.Bitmap壓縮技術(shù)
    • 3.1 質(zhì)量壓縮
    • 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獲取內(nèi)存
    • 5.4 該博客對應測試項目地址

好消息

  • 博客筆記大匯總【16年3月到至今】肴掷,包括Java基礎及深入知識點岩臣,Android技術(shù)博客掸犬,Python學習筆記等等统阿,還包括平時開發(fā)中遇到的bug匯總,當然也在工作之余收集了大量的面試題误墓,長期更新維護并且修正,持續(xù)完善……開源的文件是markdown格式的!同時也開源了生活博客有缆,從12年起,積累共計50篇[近30萬字]温亲,轉(zhuǎn)載請注明出處棚壁,謝謝!
  • 鏈接地址:https://github.com/yangchong211/YCBlogs
  • 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起于忽微凌受,量變引起質(zhì)變!
  • 輪播圖封裝庫:https://github.com/yangchong211/YCBanner
  • 輕量級版本更新彈窗:https://github.com/yangchong211/YCUpdateApp
  • 通知欄封裝庫:https://github.com/yangchong211/YCNotification

01.如何計算Bitmap占用內(nèi)存

1.1 如何計算占用內(nèi)存

  • 如果圖片要顯示下Android設備上,ImageView最終是要加載Bitmap對象的蚣驼,就要考慮單個Bitmap對象的內(nèi)存占用了魄幕,如何計算一張圖片的加載到內(nèi)存的占用呢相艇?其實就是所有像素的內(nèi)存占用總和:
  • bitmap內(nèi)存大小 = 圖片長度 x 圖片寬度 x 單位像素占用的字節(jié)數(shù)
  • 起決定因素就是最后那個參數(shù)了,Bitmap'常見有2種編碼方式:ARGB_8888和RGB_565纯陨,ARGB_8888每個像素點4個byte坛芽,RGB_565是2個byte,一般都采用ARGB_8888這種翼抠。那么常見的1080*1920的圖片內(nèi)存占用就是:1920 x 1080 x 4 = 7.9M

1.2 上面方法計算內(nèi)存對嗎

  • 我看到好多博客都是這樣計算的咙轩,但是這樣算對嗎?有沒有哥們試驗過這種方法正確性阴颖?我覺得看博客要對博主表示懷疑活喊,論證別人寫的是否正確。更多詳細可以看我的GitHub:https://github.com/yangchong211
    • 說出我的結(jié)論:上面1.1這種說法也對量愧,但是不全對钾菊,沒有說明場景,同時也忽略了一個影響項:Density偎肃。接下來看看源代碼煞烫。
    • inDensity默認為圖片所在文件夾對應的密度;inTargetDensity為當前系統(tǒng)密度累颂。
    • 加載一張本地資源圖片滞详,那么它占用的內(nèi)存 = width * height * nTargetDensity/inDensity * nTargetDensity/inDensity * 一個像素所占的內(nèi)存。
    @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 * 一個像素所占的內(nèi)存蒲犬;
    • 別的:width * height * 一個像素所占的內(nèi)存;

1.3 一個像素占用多大內(nèi)存

  • Bitmap.Config用來描述圖片的像素是怎么被存儲的稀火?
    • ARGB_8888: 每個像素4字節(jié). 共32位暖哨,默認設置。
    • Alpha_8: 只保存透明度凰狞,共8位篇裁,1字節(jié)。
    • ARGB_4444: 共16位赡若,2字節(jié)达布。
    • RGB_565:共16位,2字節(jié)逾冬,只存儲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,可以減少一半的內(nèi)存占用默伍。
    • 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壓縮技術(shù)

3.1 質(zhì)量壓縮

  • 質(zhì)量壓縮方法:在保持像素的前提下改變圖片的位深及透明度等捕捂,來達到壓縮圖片的目的瑟枫,這樣適合去傳遞二進制的圖片數(shù)據(jù),比如分享圖片指攒,要傳入二進制數(shù)據(jù)過去慷妙,限制500kb之內(nèi)。
    • 1允悦、bitmap圖片的大小不會改變
    • 2膝擂、bytes.length是隨著quality變小而變小的。
    /**
     * 第一種:質(zhì)量壓縮法
     * @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數(shù)據(jù)生成圖片
        Bitmap bitmap = null;
        // 質(zhì)量壓縮方法架馋,options的值是0-100,這里100表示原來圖片的質(zhì)量全闷,不壓縮叉寂,把壓縮后的數(shù)據(jù)存放到baos中
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
        int options = 90;
        // 循環(huán)判斷如果壓縮后圖片是否大于maxSize,大于繼續(xù)壓縮
        while (baos.toByteArray().length  > maxSize) {
            // 重置baos即清空baos
            baos.reset();
            // 這里壓縮options%,把壓縮后的數(shù)據(jù)存放到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) {
            // 把壓縮后的數(shù)據(jù)baos存放到bytes中
            bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
            int byteCount1 = bitmap.getByteCount();
            Log.i("yc壓縮圖片","壓縮后大小"+byteCount1);
        }
        return bitmap;
    }
    
    
    /**
     * 第一種:質(zhì)量壓縮法
     *
     * @param src           源圖片
     * @param maxByteSize   允許最大值字節(jié)數(shù)
     * @param recycle       是否回收
     * @return              質(zhì)量壓縮壓縮過的圖片
     */
    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) {// 最好質(zhì)量的不大于最大字節(jié)局服,則返回最佳質(zhì)量
            bytes = baos.toByteArray();
        } else {
            baos.reset();
            src.compress(Bitmap.CompressFormat.JPEG, 0, baos);
            if (baos.size() >= maxByteSize) { // 最差質(zhì)量不小于最大字節(jié)钓瞭,則返回最差質(zhì)量
                bytes = baos.toByteArray();
            } else {
                // 二分法尋找最佳質(zhì)量
                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;
    }
    
    
    /**
     * 第一種:質(zhì)量壓縮法
     *
     * @param src     源圖片
     * @param quality 質(zhì)量
     * @param recycle 是否回收
     * @return 質(zhì)量壓縮后的圖片
     */
    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,寬高都減少搏讶,內(nèi)存降低佳鳖。上面的代碼沒用過options.inJustDecodeBounds = true;因為我是固定來取樣的數(shù)據(jù)霍殴,為什么這個壓縮方法叫采樣率壓縮媒惕?是因為配合inJustDecodeBounds,先獲取圖片的寬来庭、高(這個過程就是取樣)妒蔚。然后通過獲取的寬高,動態(tài)的設置inSampleSize的值月弛。當inJustDecodeBounds設置為true的時候肴盏, BitmapFactory通過decodeResource或者decodeFile解碼圖片時,將會返回空(null)的Bitmap對象帽衙,這樣可以避免Bitmap的內(nèi)存分配菜皂, 但是它可以返回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對圖像進行縮放恍飘、旋轉(zhuǎn)榨崩、平移、斜切等變換的章母。
    • Matrix提供了一些方法來控制圖片變換:Matrix調(diào)用一系列set,pre,post方法時,可視為將這些方法插入到一個隊列母蛛。當然,按照隊列中從頭至尾的順序調(diào)用執(zhí)行。其中pre表示在隊頭插入一個方法,post表示在隊尾插入一個方法乳怎。而set表示把當前隊列清空,并且總是位于隊列的最中間位置彩郊。當執(zhí)行了一次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角度的旋轉(zhuǎn)廉油,軸心為(0,0)惠险。
    setRotate(float degrees,float px,float py):控制Matrix進行depress角度的旋轉(zhuǎn),軸心為(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            縮放寬度倍數(shù)
     * @param scaleHeight           縮放高度倍數(shù)
     * @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()方法

  • 如何調(diào)用這個recycle()方法
    if (bitmap != null && !bitmap.isRecycled()) {
        bitmap.recycle();
        bitmap = null;
    }
    
  • 思考以下睡陪,為何調(diào)用recycle()需要做非空判斷寺渗?這里可以引出bitmap系統(tǒng)回收功能。小楊我如果分析不對兰迫,歡迎反饋信殊。
    • 首先看看源碼……順便翻一下該方法的注釋!我是用有道翻譯的汁果,大意如下:釋放與此位圖關(guān)聯(lián)的本機對象涡拘,并清除對像素數(shù)據(jù)的引用。這將不會同步釋放像素數(shù)據(jù)据德;如果沒有其他引用鳄乏,它只允許垃圾收集府蔗。位圖被標記為“死”,這意味著如果調(diào)用getPixels()或setPixels()汞窗,它將拋出異常姓赤,并且不會繪制任何東西。此操作不能反轉(zhuǎn)仲吏,因此只有在確定沒有進一步使用位圖的情況下才應調(diào)用該操作不铆。這是一個高級調(diào)用,通常不需要調(diào)用裹唆,因為當沒有對此位圖的引用時誓斥,普通GC進程將釋放此內(nèi)存。
    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;
        }
    }
    
  • 通常不需要調(diào)用许帐?這是為啥劳坑?
    • 在Android3.0以后Bitmap是存放在堆中的,只要回收堆內(nèi)存即可成畦。官方建議我們3.0以后使用recycle()方法進行回收距芬,該方法可以不主動調(diào)用,因為垃圾回收器會自動收集不可用的Bitmap對象進行回收循帐。
    • 那么何是進行回收呢框仔?這里面涉及到bitmap的緩存算法,還有GC回收垃圾機制拄养。關(guān)于GC回收機制可以看我這篇博客:https://blog.csdn.net/m0_37700275/article/details/83651039
    • 大概就是移除最少使用的緩存和使用最久的緩存离斩,先說出結(jié)論,下來接著分析瘪匿!

4.2 緩存原理

  • LruCache原理
    • LruCache是個泛型類跛梗,內(nèi)部采用LinkedHashMap來實現(xiàn)緩存機制,它提供get方法和put方法來獲取緩存和添加緩存棋弥,其最重要的方法trimToSize是用來移除最少使用的緩存和使用最久的緩存核偿,并添加最新的緩存到隊列中。

4.3 Bitmap的復用

  • Android3.0之后嘁锯,并沒有強調(diào)Bitmap.recycle()宪祥;而是強調(diào)Bitmap的復用聂薪。
    • 使用LruCache對Bitmap進行緩存家乘,當再次使用到這個Bitmap的時候直接獲取,而不用重走編碼流程藏澳。
    • Android3.0(API 11之后)引入了BitmapFactory.Options.inBitmap字段仁锯,設置此字段之后解碼方法會嘗試復用一張存在的Bitmap。這意味著Bitmap的內(nèi)存被復用翔悠,避免了內(nèi)存的回收及申請過程业崖,顯然性能表現(xiàn)更佳野芒。
    • 使用這個字段有幾點限制:
      • 聲明可被復用的Bitmap必須設置inMutable為true;
      • Android4.4(API 19)之前只有格式為jpg双炕、png狞悲,同等寬高(要求苛刻),inSampleSize為1的Bitmap才可以復用妇斤;
      • Android4.4(API 19)之前被復用的Bitmap的inPreferredConfig會覆蓋待分配內(nèi)存的Bitmap設置的inPreferredConfig摇锋;
      • Android4.4(API 19)之后被復用的Bitmap的內(nèi)存必須大于需要申請內(nèi)存的Bitmap的內(nèi)存;
      • Android4.4(API 19)之前待加載Bitmap的Options.inSampleSize必須明確指定為1站超。

05.Bitmap常見操作

5.1 Bitmap的壓縮方式

  • 常見壓縮方法Api
    • Bitmap.compress()荸恕,質(zhì)量壓縮,不會對內(nèi)存產(chǎn)生影響死相;
    • BitmapFactory.Options.inSampleSize融求,內(nèi)存壓縮;
  • Bitmap.compress()
    • 質(zhì)量壓縮算撮,不會對內(nèi)存產(chǎn)生影響
    • 它是在保持像素的前提下改變圖片的位深及透明度等生宛,來達到壓縮圖片的目的,不會減少圖片的像素肮柜。進過它壓縮的圖片文件大小會變小茅糜,但是解碼成bitmap后占得內(nèi)存是不變的。
  • BitmapFactory.Options.inSampleSize
    • 內(nèi)存壓縮
    • 解碼圖片時素挽,設置BitmapFactory.Options類的inJustDecodeBounds屬性為true蔑赘,可以在Bitmap不被加載到內(nèi)存的前提下,獲取Bitmap的原始寬高预明。而設置BitmapFactory.Options的inSampleSize屬性可以真實的壓縮Bitmap占用的內(nèi)存缩赛,加載更小內(nèi)存的Bitmap。
    • 設置inSampleSize之后撰糠,Bitmap的寬酥馍、高都會縮小inSampleSize倍。例如:一張寬高為2048x1536的圖片阅酪,設置inSampleSize為4之后旨袒,實際加載到內(nèi)存中的圖片寬高是512x384。占有的內(nèi)存就是0.75M而不是12M术辐,足足節(jié)省了15倍砚尽。
    • 備注:inSampleSize值的大小不是隨便設、或者越大越好辉词,需要根據(jù)實際情況來設置必孤。inSampleSize比1小的話會被當做1,任何inSampleSize的值會被取接近2的冪值瑞躺。

5.2 Bitmap如何復用

  • Bitmap復用的實驗敷搪,代碼如下所示兴想,然后看打印的日志信息
    • 從內(nèi)存地址的打印可以看出,兩個對象其實是一個對象赡勘,Bitmap復用成功嫂便;
    • bitmapReuse占用的內(nèi)存(4346880)正好是bitmap占用內(nèi)存(1228800)的四分之一;
    • getByteCount()獲取到的是當前圖片應當所占內(nèi)存大小闸与,getAllocationByteCount()獲取到的是被復用Bitmap真實占用內(nèi)存大小顽悼。雖然bitmapReuse的內(nèi)存只有4346880,但是因為是復用的bitmap的內(nèi)存几迄,因而其真實占用的內(nèi)存大小是被復用的bitmap的內(nèi)存大形盗(1228800)。這也是getAllocationByteCount()可能比getByteCount()大的原因映胁。
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    private void initBitmap() {
        BitmapFactory.Options options = new BitmapFactory.Options();
        // 圖片復用木羹,這個屬性必須設置;
        options.inMutable = true;
        // 手動設置縮放比例解孙,使其取整數(shù)坑填,方便計算、觀察數(shù)據(jù)弛姜;
        options.inDensity = 320;
        options.inTargetDensity = 320;
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bg_autumn_tree_min, options);
        // 對象內(nèi)存地址脐瑰;
        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);
        // 復用對象的內(nèi)存地址;
        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獲取內(nèi)存

  • getByteCount()
    • getByteCount()方法是在API12加入的荠商,代表存儲Bitmap的色素需要的最少內(nèi)存寂恬。API19開始getAllocationByteCount()方法代替了getByteCount()。
  • getAllocationByteCount()
    • API19之后莱没,Bitmap加了一個Api:getAllocationByteCount()初肉;代表在內(nèi)存中為Bitmap分配的內(nèi)存大小。
    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()的區(qū)別饰躲?
    • 一般情況下兩者是相等的牙咏;
    • 通過復用Bitmap來解碼圖片,如果被復用的Bitmap的內(nèi)存比待分配內(nèi)存的Bitmap大,那么getByteCount()表示新解碼圖片占用內(nèi)存的大朽诹选(并非實際內(nèi)存大小,實際大小是復用的那個Bitmap的大型),getAllocationByteCount()表示被復用Bitmap真實占用的內(nèi)存大薪鼓ⅰ(即mBuffer的長度)盯拱。
  • 在復用Bitmap的情況下,getAllocationByteCount()可能會比getByteCount()大例嘱。

5.4 該博客對應測試項目地址

關(guān)于其他內(nèi)容介紹

01.關(guān)于博客匯總鏈接

02.關(guān)于我的博客

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拼卵,一起剝皮案震驚了整個濱河市奢浑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌腋腮,老刑警劉巖雀彼,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異即寡,居然都是意外死亡徊哑,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門聪富,熙熙樓的掌柜王于貴愁眉苦臉地迎上來莺丑,“玉大人,你說我怎么就攤上這事墩蔓∩颐В” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵奸披,是天一觀的道長昏名。 經(jīng)常有香客問我,道長阵面,這世上最難降的妖魔是什么轻局? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮样刷,結(jié)果婚禮上嗽交,老公的妹妹穿的比我還像新娘。我一直安慰自己颂斜,他們只是感情好夫壁,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著沃疮,像睡著了一般盒让。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上司蔬,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天邑茄,我揣著相機與錄音,去河邊找鬼俊啼。 笑死肺缕,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播同木,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼浮梢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了彤路?” 一聲冷哼從身側(cè)響起秕硝,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎洲尊,沒想到半個月后远豺,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡坞嘀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年躯护,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丽涩。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡棺滞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出内狸,到底是詐尸還是另有隱情检眯,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布昆淡,位于F島的核電站锰瘸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏昂灵。R本人自食惡果不足惜避凝,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望眨补。 院中可真熱鬧管削,春花似錦、人聲如沸撑螺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽甘晤。三九已至含潘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間线婚,已是汗流浹背遏弱。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留塞弊,地道東北人漱逸。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓泪姨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親饰抒。 傳聞我的和親對象是個殘疾皇子肮砾,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容