Bitmap 内存优化
问题
Bitmap 为什么占用内存大?如何优化 Bitmap 的内存使用?
答案
Bitmap 内存计算
内存大小 = 宽 × 高 × 每像素字节数
| 格式 | 每像素字节 | 说明 |
|---|---|---|
| ARGB_8888 | 4 | 默认,最高质量 |
| RGB_565 | 2 | 无透明通道,省一半 |
| ARGB_4444 | 2 | 已废弃 |
| HARDWARE | - | GPU 纹理,不占 Java 堆 |
一张 4000×3000 的照片:4000 × 3000 × 4 = 48MB
采样压缩(inSampleSize)
fun decodeSampledBitmap(res: Resources, resId: Int, reqWidth: Int, reqHeight: Int): Bitmap {
val options = BitmapFactory.Options().apply {
// 第一步:只读尺寸,不加载像素
inJustDecodeBounds = true
}
BitmapFactory.decodeResource(res, resId, options)
// 第二步:计算采样率
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
// 第三步:按采样率加载
options.inJustDecodeBounds = false
return BitmapFactory.decodeResource(res, resId, options)
}
fun calculateInSampleSize(options: BitmapFactory.Options, reqW: Int, reqH: Int): Int {
val (height, width) = options.outHeight to options.outWidth
var inSampleSize = 1
if (height > reqH || width > reqW) {
val halfH = height / 2
val halfW = width / 2
while (halfH / inSampleSize >= reqH && halfW / inSampleSize >= reqW) {
inSampleSize *= 2 // 只能是 2 的整数次幂
}
}
return inSampleSize
}
Bitmap 复用(inBitmap)
val options = BitmapFactory.Options().apply {
inMutable = true
inBitmap = reusableBitmap // 复用已有 Bitmap 的内存
}
val bitmap = BitmapFactory.decodeStream(stream, null, options)
inBitmap 限制
- Android 4.4 以前:新 Bitmap 必须与被复用 Bitmap 大小完全一致
- Android 4.4+:被复用 Bitmap 大小 >= 新 Bitmap 即可
图片加载库的优化策略
Glide / Coil 内部已做了以下优化:
- 自动降采样:根据 ImageView 大小加载合适尺寸
- 内存缓存 + 磁盘缓存:LRU 缓存
- Bitmap 回收池:BitmapPool 复用
- 请求生命周期管理:页面销毁取消加载
常见面试问题
Q1: 如何加载超大图片(比如长图)?
答案:
使用 BitmapRegionDecoder 按区域解码,配合自定义 View 实现滑动查看:
val decoder = BitmapRegionDecoder.newInstance(inputStream, false)
val rect = Rect(0, 0, screenWidth, screenHeight) // 只解码可见区域
val bitmap = decoder.decodeRegion(rect, BitmapFactory.Options())
Q2: RGB_565 什么时候使用?
答案:
当图片不需要透明通道时(如照片、纯色背景图),可使用 RGB_565 节省 50% 内存。但对于 PNG 图片(含透明区域),必须使用 ARGB_8888。
Glide 默认使用 RGB_565(不含透明),Coil 默认使用 ARGB_8888。