Bitmap 用来描述一张图片的长、宽、颜色等信息。通常情况下,我们可以使用 BitmapFactory 来将某一路径下的图片解析为 Bitmap 对象。
1 | Bitmap bitmap = BitmapFactory.decodeResource(getResource(),R.drawable.xxx); |
xxx 是保存在 res/drawable-xhdpi 目录下的一张 600*600,大小为 65Kb 的图片。
默认情况下 BitmapFactory 使用 Bitmap.Config.ARGB_8888 的存储方式来加载图片内容,而在这种存储模式下,每一个像素需要占用 4 个字节。
通过 Bitmap.getAllocationByteCount() 方法获取 Bitmap 占用的字节大小:
bitmap size is 6006004 = 1440000
BitmapFactory 在解析图片的过程中,会根据当前设备屏幕密度和图片所在的 drawable 目录来做一个对比,根据这个对比值进行缩放操作。具体公式为如下所示:
缩放比例 scale = 当前设备屏幕密度 / 图片所在 drawable 目录对应屏幕密度
Bitmap 实际大小 = 宽 * scale * 高 * scale * Config 对应存储像素数
- drawable-mdpi:densityDpi = 160(默认值)
- drawable-hdpi:densityDpi = 240
- drawable-xhdpi:densityDpi = 320
- drawable-xxhdpi:densityDpi = 480
- drawable-xxxhdpi:densityDpi = 640
补充:dp = px / (dpi/160) 其中dpi是设备屏幕密度
assets 中的图片大小
xxx 是保存在 assets 目录下的一张 600*600,大小为 65Kb 的图片。
1 | InputStream in = getAssets().open(“xxx.png”); |
通过 Bitmap.getAllocationByteCount() 方法获取 Bitmap 占用的字节大小:
bitmap size is 6006004 = 1440000
可以看出,加载 assets 目录中的图片,系统并不会对其进行缩放操作。
Bitmap 加载优化
修改图片加载的 Config
1 | BitmapFactory.Options op = new BitmapFactory.Options(); |
这种存储方式一个像素占用 2 个字节,所以最终占用内存直接减半
bitmap size is 6006002 = 720000
inSampleSize 参数
可以实现 Bitmap 采样压缩,这个参数的含义是宽高维度上每隔 inSampleSize 个像素进行一次采集。
1 | op.inSampleSize = 2 |
因为宽高都会进行采样,所以最终图片会被缩略 4 倍
bitmap size is 720000 / 4 = 180000 = 170KB
Bitmap 复用
场景:bitmap切换显示,导致频繁创建+GC回收,造成内存抖动。
使用 Options.inBitmap 优化
第一次显示之后,内存中已经存在了一个 Bitmap 对象。每次切换图片只是显示的内容不一样,我们可以重复利用已经占用内存的 Bitmap 空间
1 | Bitmap reuseBitmap; |
//创建一个可以用来复用的 Bitmap 对象。
//将 options.inBitmap 赋值为之前创建的 reuseBitmap 对象,从而避免重新分配内存。
复用 inBitmap 之前,需要调用 canUseForInBitmap 方法来判断 reuseBitmap 是否可以被复用。这是因为 Bitmap 的复用有一定的限制:
在 Android 4.4 版本之前,只能重用相同大小的 Bitmap 内存区域;
4.4 之后你可以重用任何 Bitmap 的内存区域,只要这块内存比将要分配内存的 bitmap 大就可以。
BitmapRegionDecoder 图片分片显示
图片可以以绝对路径、文件描述符、输入流的方式传递给 BitmapRegionDecoder
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(in,false);
Bitmap bitmap = decoder.decodeRegion(new Rect(0,0,200,200),op);
只显示部分区域,可以通过touch实现滑动展示其余部分,具体可以参考开源库:
https://github.com/LuckyJayce/LargeImage
BitmapRegionDecoder 容易造成内存抖动,卡顿,写法繁琐,不推荐
推荐:Glide+SubsamplingScaleImageView
https://github.com/davemorrissey/subsampling-scale-image-view
https://juejin.cn/post/6955427322291814431
https://github.com/LuckyJayce/LargeImage
Bitmap 缓存
1 | //内存缓存 |
都是基于LinkedHashMap(有序)
LinkedHashMap继承于HashMap,底层基于HashMap和双向链表来实现的。
”三级缓存“的逻辑:
- 加载时 先从内存缓存获取,有就返回bitmap绘制图片到view,若没有就从磁盘缓存获取;
- 磁盘缓存有就返回bitmap并缓存到内存缓存,没有就请求网络;
- 网络请求回来,就缓存到磁盘缓存,然后从磁盘缓存获取返回。
图片储存位置
- 3.0 - 7.0 存在java堆
- 3.0之前,8.0及之后,存native堆