Bitmap内存管理

Bitmap内存计算:

简单地说是分辨率像素点大小 (长宽*像素点占用的字节)

  1. ARGB_8888 ARGB各占8位所以4个字节
  2. ARGB_4444 2个字节(不推荐使用,看起来质量太差)
  3. RGB_565 R 5位,G 6位,B 5位,即16位,2个字节
    更详细的讲解:
    https://www.cnblogs.com/dasusu/p/9789389.html

getByteCount
返回位图像素点的最小字节数,即内存。
Bitmap.Options
inDensity: 表示这个bitmap的像素密度,根据drawable/mipmap目录。
inTargetDensity: 表示屏幕的像素密度。
getResource().getDisplayMetrics().densityDpi。

Bitmap内存压缩

Bitmap.Options
inJustDecodeBounds: 设置true 读取图片outxxx参数如:outWidth、outHeight。
inPreferedConfig:设置图片解码后的像素格式,如ARGB_888/RGB565
inSampleSize:设置图片解码缩放比,值为2的倍数,1则不进行缩放,如设置4,则加载图片的宽高是原图的1/4,内存大小则是1/16。

对于内存的降低,无论是选择jpg还是png更或者是webp。其实都是毫无意义的。Jpg是属于有损压缩,我们看见的jpg比png文件小,那是因为压缩率高。这都是属于文件存储范畴。对于内存来说,我们加载一张不带alpha通道使用RGB_565格式的png与一张jpg占用的内存大小都是一样的。
对于内存的压缩我们能做的就是缩小图片尺寸与改变像素格式。
网上大多计算压缩比的方式如下:

private Bitmap getimage(String srcPath) {
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        //开始读入图片,此时把options.inJustDecodeBounds 设回true了
        newOpts.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeFile(srcPath,newOpts);//此时返回bm为空

        newOpts.inJustDecodeBounds = false;
        int w = newOpts.outWidth;
        int h = newOpts.outHeight;
        float hh = 800f;
        float ww = 480f;
        //缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
        int be = 1;//be=1表示不缩放
        if (w > h && w > ww) {//如果宽度大的话根据宽度固定大小缩放
            be = (int) (newOpts.outWidth / ww);
        } else if (w < h && h > hh) {//如果高度高的话根据宽度固定大小缩放
            be = (int) (newOpts.outHeight / hh);
        }
        if (be <= 0)
            be = 1;
        newOpts.inSampleSize = be;//设置缩放比例
        //重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
        bitmap = BitmapFactory.decodeFile(srcPath, newOpts);
        return compressImage(bitmap);//压缩好比例大小后再进行质量压缩
    }

看见一个有一点点不一样的计算缩放比的方式,旨在记录一下:

 /**
     *
     * @param context
     * @param id 图片id
     * @param maxWidth 限制最大的宽
     * @param maxHeight 限制最大的高
     * @param hasAlpha 是否有透明度
     * @return
     */
    public static Bitmap resizeBitmap(Context context, int id, int maxWidth, int maxHeight, Boolean hasAlpha) {
        Resources resources = context.getResources();
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(resources, id, options);
        int w = options.outWidth;
        int h = options.outHeight;
        //设置缩放系数
        options.inSampleSize = calculateInSampleSize(w, h, maxWidth, maxHeight);
        if (!hasAlpha) {
            options.inPreferredConfig = Bitmap.Config.RGB_565;
        }
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(resources, id, options);
    }

    private static int calculateInSampleSize(int w, int h, int maxWidth, int maxHeight) {
        int inSampleSize = 1;
        if (w > maxWidth && h > maxHeight) {
            inSampleSize = 2;
            //循环使宽高小于最大的宽高
            while (w / inSampleSize > maxWidth && h / inSampleSize > maxHeight) {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }

Bitmap缓存

Bitmap内存管理的官方文档
https://developer.android.google.cn/topic/performance/graphics/manage-memory.html#java
Google的图片浏览demo
https://developer.android.google.cn/samples/DisplayingBitmaps/index.html

内存缓存

首先是创建图片,不管是网络加载还是本地加载的,在这个创建的过程中,系统会为每一个图片申请一块内存,我们使用LruCache缓存这些bitmap,每张图片都会申请内存,会导致内存占用过多,所以就需要使用到bitmap的内存复用,就是当缓存的bitmap被回收的时候,我们使用一个复用池来存放这些bitmap,当有新的图片需要创建并申请内存的时候,我们把复用池中的bitmap内存给他,这样有利的减少内存的申请。这里使用HashSet作为复用池

 //线程安全
        resuablePool = Collections.synchronizedSet(new HashSet<WeakReference<Bitmap>>());

        //maxSize 能够缓存的内存最大数
        mMemoryCache = new LruCache<Integer, Bitmap>(memory / 8 * 1024 * 1024) {
            /**
             *getAllocationByteCount
    一般情况下getByteCount()与getAllocationByteCount()是相等的;通过复用Bitmap来解码图片,
如果被复用的Bitmap的内存比待分配内存的Bitmap大,
那么getByteCount()表示新解码图片占用内存的大小(并非实际内存大小,实际大小是复用的那个Bitmap的大小),
getAllocationByteCount()表示被复用Bitmap占用的内存大小。所以可能allocation比bytecount大。

             * @param key
             * @param value
             * @return value占用的内存
             */
            @Override
            protected int sizeOf(Integer key, Bitmap value) {
                if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
                    return value.getAllocationByteCount();
                }
                return value.getByteCount();
            }

            /**
             * 当bitmap从lruCache中移除时回调
             * @param evicted
             * @param key
             * @param oldValue
             * @param newValue
             */
            @Override
            protected void entryRemoved(boolean evicted, Integer key, Bitmap oldValue, Bitmap newValue) {
                if (oldValue.isMutable()) {
                    resuablePool.add(new WeakReference<Bitmap>(oldValue));
                } else {
                    oldValue.recycle();
                }
            }
        };

但是这个内存复用是有条件的:

  • 可被复用的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的内存;

磁盘缓存
磁盘缓存使用DiskLruCache
https://github.com/JakeWharton/DiskLruCache

public void putBitmap2Disk(String key, Bitmap bitmap) {
        DiskLruCache.Snapshot snapshot = null;
        OutputStream os = null;
        try {
            snapshot = diskLruCache.get(key);
            if (snapshot == null) {
                DiskLruCache.Editor edit = diskLruCache.edit(key);
                if (edit != null) {
                    os = edit.newOutputStream(0);
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 50, os);
                    edit.commit();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (snapshot != null) {
                snapshot.close();
            }
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

当内存没有图片,就要从磁盘获取,这个时候获取到的新图片就应该使用内存复用了

/**
     * 
     * @param key
     * @param reusable 复用池里的图片
     * @return
     */
    public Bitmap getBitmapFromDisk(String key,Bitmap reusable) {
        DiskLruCache.Snapshot snapshot = null;
        Bitmap bitmap = null;
        try {
            snapshot = diskLruCache.get(key);
            if (snapshot == null) {
                return null;
            }
            //获得文件输入流 读取bitmap
            //is的close snapshot.close() 已经帮助close了
            InputStream is = snapshot.getInputStream(0);
            //内存复用
            options.inMutable = true;
            options.inBitmap = reusable;
            bitmap = BitmapFactory.decodeStream(is,null,options);
            if (bitmap != null){
                mMemoryCache.put(key,bitmap);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (snapshot != null) {
                snapshot.close();
            }
        }
        return bitmap;
    }
/**
     * 从复用池里获取可用于内存复用的bitmap
     * @param w
     * @param h
     * @param inSampleSize
     * @return
     */
    public Bitmap getReusable(int w, int h, int inSampleSize) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
            return null;
        }
        Iterator<WeakReference<Bitmap>> iterator = resuablePool.iterator();
        Bitmap reusable = null;
        //迭代查找符合复用条件的bitmap
        while (iterator.hasNext()) {
            Bitmap bitmap = iterator.next().get();
            if (bitmap != null) {
                if (checkInBitmap(bitmap, w, h, inSampleSize)) {
                    reusable = bitmap;
                    //移除复用池
                    iterator.remove();
                    break;
                }
            } else {
                iterator.remove();
            }
        }
        return reusable;
    }
private boolean checkInBitmap(Bitmap bitmap, int w, int h, int inSampleSize) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            return bitmap.getWidth() == w && bitmap.getHeight() == h
                    && inSampleSize == 1;
        }
        //如果缩放系数大于1 获得缩放后的宽高
        if (inSampleSize > 1) {
            w /= inSampleSize;
            h /= inSampleSize;
        }
        int byteCount = w * h * getPixeCount(bitmap.getConfig());
        return byteCount <= bitmap.getAllocationByteCount();
    }

    int getPixeCount(Bitmap.Config config) {
        if (config == Bitmap.Config.ARGB_8888) {
            return 4;
        }
        return 2;
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 目录介绍 01.如何计算Bitmap占用内存1.1 如何计算占用内存1.2 上面方法计算内存对吗1.3 一个像素占...
    杨充211阅读 3,944评论 1 9
  • 2021期待与你一起共事,点击查看岗位[https://www.jianshu.com/p/6f4d67fa406...
    闲庭阅读 16,582评论 0 75
  • 7.1 压缩图片 一、基础知识 1、图片的格式 jpg:最常见的图片格式。色彩还原度比较好,可以支持适当压缩后保持...
    AndroidMaster阅读 2,467评论 0 13
  • 近期听了一些写作课,有软文的、有新媒体的、也有销售文案类的,里面不约而同的提到了自己写作的定位是什么,优秀作者们都...
    九思常阅读 1,267评论 15 19
  • 念自己好:晚上因为工作上的事有很大的情绪,一时心烦意乱,满心焦虑,想发脾气,想丢东西,事后及时调整,安抚好崽崽,洗...
    q晓琼阅读 341评论 0 0