跳到主要内容

Bitmap 内存池

问题

BitmapPool 的原理是什么?inBitmap 如何实现 Bitmap 内存复用?

答案

Bitmap 内存占用

Bitmap 内存 = 宽 × 高 × 每像素字节数

格式每像素字节说明
ARGB_88884 bytes默认格式,最高品质
RGB_5652 bytes不需要透明度时可节省 50%
ARGB_44442 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 内存

相关链接