Bitmap 内存池
问题
BitmapPool 的原理是什么?inBitmap 如何实现 Bitmap 内存复用?
答案
Bitmap 内存占用
Bitmap 内存 = 宽 × 高 × 每像素字节数
| 格式 | 每像素字节 | 说明 |
|---|---|---|
| ARGB_8888 | 4 bytes | 默认格式,最高品质 |
| RGB_565 | 2 bytes | 不需要透明度时可节省 50% |
| ARGB_4444 | 2 bytes | 已废弃 |
| HARDWARE | - | Android 8.0+,存储在 GPU 内存 |
一张 1920×1080 的 ARGB_8888 图片占用:1920 × 1080 × 4 = 7.9MB。
inBitmap 复用
Android 3.0+ 支持通过 inBitmap 复用已有 Bitmap 的内存空间,避免反复分配和 GC:
val options = BitmapFactory.Options().apply {
// 先获取图片尺寸
inJustDecodeBounds = true
BitmapFactory.decodeFile(path, this)
// 计算采样率
inSampleSize = calculateSampleSize(outWidth, outHeight, targetWidth, targetHeight)
// 设置复用的 Bitmap
inMutable = true
inBitmap = getReusableBitmap(outWidth / inSampleSize, outHeight / inSampleSize)
inJustDecodeBounds = false
}
val bitmap = BitmapFactory.decodeFile(path, options)
BitmapPool 简易实现
class SimpleBitmapPool(private val maxSize: Int) {
// 按尺寸分组存储可复用的 Bitmap
private val pool = LinkedHashMap<String, MutableList<Bitmap>>()
private var currentSize = 0
fun get(width: Int, height: Int, config: Bitmap.Config): Bitmap? {
val key = "${width}x${height}_${config}"
return pool[key]?.removeLastOrNull()?.also {
currentSize -= it.allocationByteCount
}
}
fun put(bitmap: Bitmap) {
if (!bitmap.isMutable) return // 只能复用 mutable bitmap
val key = "${bitmap.width}x${bitmap.height}_${bitmap.config}"
pool.getOrPut(key) { mutableListOf() }.add(bitmap)
currentSize += bitmap.allocationByteCount
trimToSize()
}
private fun trimToSize() {
while (currentSize > maxSize) {
// LRU 淘汰
val entry = pool.entries.firstOrNull() ?: break
val list = entry.value
val removed = list.removeFirstOrNull()
if (list.isEmpty()) pool.remove(entry.key)
removed?.let { currentSize -= it.allocationByteCount }
}
}
}
Glide 的 BitmapPool
Glide 内置了 LruBitmapPool,在图片变换(缩放、裁剪、圆角)时自动复用 Bitmap,大幅减少内存分配和 GC 触发。
HARDWARE Bitmap
Android 8.0+ 可使用 Bitmap.Config.HARDWARE,Bitmap 存储在 GPU 内存中,不占用 Java Heap。适合只需要显示、不需要修改像素的场景。但 HARDWARE Bitmap 不可 mutable,无法用于 Canvas 绘制或 inBitmap 复用。
常见面试问题
Q1: inBitmap 的限制是什么?
答案:
- Android 4.4 之前:复用的 Bitmap 必须尺寸和配置完全相同
- Android 4.4+:复用的 Bitmap 的
allocationByteCount必须 ≥ 新 Bitmap 所需的大小 - Bitmap 必须是
mutable的 HARDWARE配置的 Bitmap 不可复用
Q2: Bitmap 存储在 Java Heap 还是 Native Heap?
答案:
- Android 3.0~7.1:像素数据存储在 Java Heap,受 dalvik.vm.heapsize 限制
- Android 8.0+:像素数据存储在 Native Heap,不受 Java Heap 限制(HARDWARE 配置存储在 GPU 内存)
- 这就是为什么 Android 8.0+ 上 Bitmap 不容易触发 OOM,但仍需关注 Native 内存