Android 性能优化-Bitmap知识梳理 & 高效加载

一、Bitmap 占用内存计算

bitmap 的内存计算可由下面的计算公式得出来:

Bitmap 内存占用 ≈ 像素数据总大小 = 图片宽 × 图片高× ( 设备dpi / 资源目录dpi ) ^ 2 × 单个像素的字节大小

其中单个像素的字节大小由 Bitmap 的一个可配置的参数 Config 来决定。Bitmap 中存在一个枚举类 Config,定义了 Android 中支持的 Bitmap 配置:

Bitmap Config 参数

Android 系统中,默认 Bitmap 加载图片使用24位真彩色(ARGB_8888)模式。
资源目录dpi 跟图片存放的资源文件的目录有关系:

当图片不特别放置任何资源目录时,其默认使用 mdpi 分辨率:160

根据我们上面的公式计算来验证一下:

res\drawable-xhdpi:1920 * 2304 * (272/ 160)^ 2 * 4 = 17280000
res\drawable-xxxhdpi:1920 * 2304 * (272/ 640)^ 2 * 4 = 1080000
res\drawable-xxhdpi:1920 * 2304 * (272/ 480)^ 2 * 4 = 1920000

二、BitMap 优化

Bitmap内存优化从下面四个方面进行优化:

  • 编码
  • 采样
  • 复用
  • 匿名共享区
2.1 编码

在第一节中已经列举出的枚举配置中存在几种不同的配置:
其中,A代表透明度;R代表红色;G代表绿色;B代表蓝色。

  • ALPHA_8
    表示8位Alpha位图,即A=8,一个像素点占用1个字节,它没有颜色,只有透明度
  • ARGB_4444
    表示16位ARGB位图,即A=4,R=4,G=4,B=4,一个像素点占4+4+4+4=16位,2个字节
  • ARGB_8888
    表示32位ARGB位图,即A=8,R=8,G=8,B=8,一个像素点占8+8+8+8=32位,4个字节
  • RGB_565
    表示16位RGB位图,即R=5,G=6,B=5,它没有透明度,一个像素点占5+6+5=16位,2个字节

也即是说我们可以通过改变图片格式,来改变每个像素占用字节数,来改变占用的内存,看下面代码

fun compressBitmap() {
        // 不获取图片,不加载到内存,只返回图片的宽高
        val mOptions = BitmapFactory.Options()
        mOptions.inJustDecodeBounds = true
        BitmapFactory.decodeResource(resources, R.drawable.yasuo, mOptions)
        // 获取图片的宽高
        val mOrigWidth = mOptions.outWidth
        val mOrigHeight = mOptions.outHeight
        Log.e(TAG, "原图:mOrigWidth = $mOrigWidth, mOrigHeight = $mOrigHeight")

        // 图片压缩-更改图片格式
        mOptions.inPreferredConfig = Bitmap.Config.RGB_565
        mOptions.inJustDecodeBounds = false
        val mTestBitmap = BitmapFactory.decodeResource(resources, R.drawable.yasuo, mOptions)
        Log.e(TAG, "===========================压缩后=================================")
        Log.e(TAG, "mTestBitmap width = ${mTestBitmap.width}, height = ${mTestBitmap.height}")
        Log.e(TAG,"mTestBitmap byteCount = ${mTestBitmap.byteCount}")
        Log.e(TAG,"mTestBitmap allocationByteCount = ${mTestBitmap.allocationByteCount}")
    }


从日志中可以看出从改变bitmap的格式可以有效的降低其占用内存大小。从 ARGB_8888 切换到 RGB_565 减少约 2 倍的内存大小。

2.2 采样

我们了解到了计算bitmap的占用内存的方法 ,是以bitmap的宽高和每个像素占用的字节数决定的。图片的大小就是bitmap的宽高,按公式我们可以缩减bitmap的宽高来达到压缩图片占用内存的目的,看下面代码,以缩减宽高来达到压缩的目的

fun decodeSampleSizeBitmapWithRes(res: Resources, resId: Int, reqWidth: Int, reqHeight: Int) {
        val mOption = BitmapFactory.Options()
        mOption.inJustDecodeBounds = true
        BitmapFactory.decodeResource(res, resId, mOption)
        mOption.inSampleSize = calculateInSampleSize(mOption, reqWidth, reqHeight)
        Log.e("mTestBitmap", "sampleSize:${calculateInSampleSize(mOption, reqWidth, reqHeight)}")
        mOption.inJustDecodeBounds = false
        val mCompressBitmap = BitmapFactory.decodeResource(res, resId, mOption)
        Log.e(TAG, "===========================压缩后=================================")
        Log.e(TAG, "mTestBitmap width = ${mCompressBitmap.width}, height = ${mCompressBitmap.height}")
        Log.e(TAG, "mTestBitmap byteCount = ${mCompressBitmap.byteCount}")
        Log.e(TAG, "mTestBitmap allocationByteCount = ${mCompressBitmap.allocationByteCount}")
    }

这种我们根据BitmapFactory 的采样率进行压缩 设置采样率,不能小于1 假如是2 则宽为之前的1/2,高为之前的1/2,一共缩小1/4 一次类推,我们看到log ,确实起到了压缩的目的

2.2 复用

重复加载图片资源耗费太多资源(CPU、内存 & 流量),我们可以使用三级缓存机制,即内存缓存、本地缓存(硬盘、数据库、文件)、网络缓存。当加载 Bitmap 图片资源时,先从内存缓存中寻找;若内存缓存中没有,则从本地缓存中查找;若本地缓存没有,则从网络中加载寻找。

另外还可以使用软引用(内存空间不足时才回收这些对象的内存)的方式实现内存敏感的高速缓存。
同时还可以开启 inBitmap 这个属性
inBitmap 属性的作用:

不使用这个属性,你加载三张图片,系统会给你分配三份内存空间,用于分别储存这三张图片
如果用了inBitmap这个属性,加载三张图片,这三张图片会指向同一块内存,而不用开辟三块内存空间

inBitmap的限制

  • 3.0-4.3
    复用的图片大小必须相同
    编码必须相同
  • 4.4以上
    复用的空间大于等于即可
    编码不必相同
    不支持WebP
    图片复用,这个属性必须设置为true; options.inMutable = true;
2.3 压缩

一个是上面讲到的采样压缩,另外一种则是下面的 质量压缩
质量压缩

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
//quality 为0~100,0表示最小体积,100表示最高质量,对应体积也是最大
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);

在上述代码中,我们选择的压缩格式是 CompressFormat.JPEG,除此之外还有两个选择:CompressFormat.PNG, PNG 格式是无损的,它无法再进行质量压缩,quality 这个参数就没有作用了,会被忽略,所以最后图片保存成的文件大小不会有变化;CompressFormat.WEBP ,这个格式是 google 推出的图片格式,它会比 JPEG 更加省空间,经过实测大概可以优化 30% 左右。

参考文章:

Android BitMap 优化
Android Bitmap 的高效加载解析
性能优化:Android中Bitmap内存大小优化的几种常见方式

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