Android内存中的大胖小子_Bitmap

Bitmap的内存模型

Android的Bitmap内存管理也是随着版本的迭代在不断的演进

API10之前
  • API10之前的时候也就是Android2.3.3之前,Bitmap的像素数据是保存在Native内存中的,而Bitmap对象是放在堆内存中(Dalvik Heap中)
  • Native内存中的像素数据不会按照预期的方式进行同步回收,有可能导致Native内存升高。
API10 到 API26
  • 也就是从Android3.0到Android8.0的时候,Android把Bitmap的像素数据也放到堆内存中了,这样像素数据也会随着Bitmap的对象的回收而一起回收。
API26之后
  • 也就是从Android8.0之后,Android把像素数据再次放到Native内存中,但是这回做了改进,native层的Bitmap像素数据可以做好和Java层的对象一起快速释放。

Bitmap的像素格式(也就是一个像素占用的内存大小)

ARGB_8888(Android默认使用的像素格式)
  • 颜色信由有Alpha、Red、Green、Blue四部分组成,每个部分占了8位,共32位,占了4个字节
ARGB_4444
  • 颜色信息由Alpha、Red、Green、Blue四部分组成,每个部分占了4位,共16位,占了2个字节
RGB_565
  • 颜色信息由Red、Green、Blue三部分组成,R占了5位、G占了6位、B占了5位,共16位,占了2个字节
ALPHA_8
  • 颜色信息只有透明度,共8位 占了1个字节。

Bitmap占有的内存

getByteCount()

  • 该方法是在API12加入的,代表着Bitmap的像素数据需要最少的内存。
  • 在API19开始getAlloctaionByteCount()方法替代了getByteCount()。

getAllocationByteCount()

  • API19之后加入的,代表着内存中为Bitmap分配的内存大小。

Bitmap的内存回收

  • 在Android3.0之前,需要手动调用Bitmap.recycler()进行Bitmap的回收
  • 在Android3.0之后,不需要手动回收Bitmap了。

Bitmap的内存复用

怎么复用
  • 在Android3.0之后引入了新的字段 BitmapFactory.Options.inBitmap和inMutab

  • 如果字段inMutable设置为true,那么解码的方法会尝试服用一个存在的Bitmap。

  • Bitmap的内存复用意味着减少了一个内存的回收以及申请,这样性能会更好。

    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
        private void reuseBitmap() {
            BitmapFactory.Options options = new BitmapFactory.Options();
           //关键点1
            options.inMutable = true;
            options.inDensity = 320;
            options.inTargetDensity = 320;
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.scal_density, options);
            Log.d(TAG, "reuseBitmap: " + bitmap);
            Log.d(TAG, "reuseBitmap: width = " + bitmap.getWidth() + ",height = " + bitmap.getHeight());
            Log.d(TAG, "reuseBitmap: byte_count = "+bitmap.getByteCount()+",allocation_byte_count ="+bitmap.getAllocationByteCount());
            // 关键点2
            options.inBitmap = bitmap;
            options.inDensity = 320;
            options.inTargetDensity = 160;
            Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.scal_density, options);
            Log.d(TAG, "reuseBitmap: " + bitmap2);
            Log.d(TAG, "reuseBitmap: width = " + bitmap2.getWidth() + ",height = " + bitmap2.getHeight());
            Log.d(TAG, "reuseBitmap: byte_count = "+bitmap2.getByteCount()+",allocation_byte_count ="+bitmap2.getAllocationByteCount());
        }
    
    //运行结果
    
    2020-03-04 20:15:23.955 29831-29831/? D/MainActivity: reuseBitmap: android.graphics.Bitmap@57148f
    2020-03-04 20:15:23.955 29831-29831/? D/MainActivity: reuseBitmap: width = 720,height = 360
    2020-03-04 20:15:23.955 29831-29831/? D/MainActivity: reuseBitmap: byte_count = 1036800,allocation_byte_count =1036800
    2020-03-04 20:15:23.964 29831-29831/? D/MainActivity: reuseBitmap: android.graphics.Bitmap@57148f
    2020-03-04 20:15:23.964 29831-29831/? D/MainActivity: reuseBitmap: width = 360,height = 180
    2020-03-04 20:15:23.964 29831-29831/? D/MainActivity: reuseBitmap: byte_count = 259200,allocation_byte_count =1036800
    
     //同一个对象
      // 复用后的bitmap getAllocationByteCount 和 getByteCount 也是不一样的。
    
复用也是有版本的问题的
  • 在Android4.4之前只有格式为jpg、png,同等宽高,inSampleSize为1的Bitmap才可以复用。
  • 在Android4.4之后开始,被复用的Bitmap内存大于需要新申请内存的Bitmap的内存就可以。

说下getByteCount()和getAllocationByteCount()的区别

  • 两者一般情况下是相等的。
  • 通过复用Bitmap来解码图片的时候,如果被复用的Bitmap的内存比待内存分配的Bitmap要大的时候
    • getByteCode表示新的Bitmap的像素大小也就是占用的内存大小。
    • getAllocationByteCode表示要被复用的Bitmap的真实内存大小

计算Bitmap占用的内存

让人想到的就是: width x height x 一个像素占用的内存大小

BitmapFactory.decodeResource()

  • 使用这个API进行图片的解码,bitmap在内存中占用的大小 = width x scale x height x scale x 一个像素占用的内存大小;
  • 其中 scale(缩放系数) = mTargetDensity/inDensity;
  • 这里面的width和height,可以理解为图片的原始宽和高,当图片放进不同的drawable文件夹中的时候,内存中Bitmap的宽和高 与图片的实际宽和高有可能不一样大了。
  • 正常情况下我们所说的公式 width和height 是Bitmap的,
  • 一张图片在相同app中的不同的资源文件夹下 占用的内存可能不一样大。

BitmapFactory.decodeFile()

  • 该方法从磁盘中加载到内存中不涉及到缩放(inDenisty和inTargetDensity)的影响。

Bitmap的压缩

质量压缩(Bitmap.compress())

  • 它在不改变图片的像素的提前下,改变图片的位深和透明度,来进行压缩图片。
  • 但是经过质量压缩后磁盘中的File会变小,但是解码后的Bitmap在内存中的大小是不变的。
/**
     * 质量压缩
     * @param bitmap
     * @return
     */
    private Bitmap compressBitmap(Bitmap bitmap) {
        ByteArrayOutputStream ous = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, ous);
        int options = 100;
        while (ous.toByteArray().length / 1024 > 100) {
            ous.reset();
            options -= 10;
            bitmap.compress(Bitmap.CompressFormat.JPEG, options, ous);
            
        }
        ByteArrayInputStream bIn = new ByteArrayInputStream(ous.toByteArray());
        Bitmap compressBitmap = BitmapFactory.decodeStream(bIn, null, null);
        return compressBitmap;
    }
质量压缩后的Bitmap,再次保存到本地变大了?
public static boolean saveBitmapToJPG(Bitmap bitmap, File file) {
    if (bitmap == null)
        return false;
    FileOutputStream fos = null;
     if (file.exists()){
         file.delete();
     }
    try {
        fos = new FileOutputStream(file);
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
        fos.flush();
        fos.close();
        return true;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}


  • 原因是压缩后的Bitmap经过了一次编码,导致保存的Bitmap过大
可以这样搞
 compressPic(BitmapFactory.decodeResource(getResources(),R.drawable.scal_density));
        String path = Environment.getExternalStorageDirectory().getPath();
        File file = new File(path, "/test/2.jpg");
        if (!file.exists()) {
            file.getParentFile().mkdirs();
        }

        try {
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            fileOutputStream.write(ous.toByteArray());
            fileOutputStream.flush();
            fileOutputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

采样率压缩(BitmapFactory.Options.inSampleSize)

  • 可以真实压缩解码后的Bitmap在内存中的大小。

  • 首先我们设置inJustDecodeBounds = true 不加载Bitmap到内存中,但是可以获取Bitmap的原始宽高。

  • 根据Bitmap的宽高和想要显示的大小,计算合适的inSampleSize设置黑Options.inSampleSize =

  • 然后将inJustDecodeBounds = false ,意味着可以进行加载Bitmap到内存中了。

      private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
            int outWidth = options.outWidth;
            int outHeight = options.outHeight;
            int inSampleSize = 1;
    
            if (outWidth > reqWidth || outHeight > reqHeight) {
                final int halfHeight = outHeight / 2;
                final int halfWidth = outWidth / 2;
    
                while ((halfWidth / inSampleSize) > reqWidth && (halfHeight / inSampleSize) > reqHeight) {
                    inSampleSize *= 2;
                }
            }
    
            return inSampleSize;
        }
    
        /**
         * 采样率压缩法
         * @param path
         * @param reqWidth
         * @param reqHeight
         * @return
         */
        private static Bitmap decodeBitmapFromFile(String path, int reqWidth, int reqHeight) {
            BitmapFactory.Options options = new BitmapFactory.Options();
            //设置读取图片的时候仅仅读取图片的长和宽
            options.inJustDecodeBounds = true;
            Bitmap bitmap = BitmapFactory.decodeFile(path, options);
            //计算合适的缩放比
            options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
            options.inPreferredConfig = Bitmap.Config.RGB_565;
            options.inJustDecodeBounds = true;
            Bitmap realBitmap = BitmapFactory.decodeFile(path, options);
            return realBitmap;
        }
    
Bitmap知识点.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容