Android BitmapRegionDecoder 详解
Android BitmapRegionDecoder 详解
BitmapRegionDecoder
是 Android 提供的一个用于局部解码大图的类,适用于加载超大型图片(如高清地图、长图、全景照片等),避免一次性加载整个图片导致内存溢出(OOM)。
1. 基本使用
(1) 初始化 BitmapRegionDecoder
可以从 InputStream
、FileDescriptor
或 ByteArray
创建:
val inputStream = context.assets.open("huge_image.jpg")
val decoder = BitmapRegionDecoder.newInstance(inputStream, false)
⚠️ 注意:
false
表示不共享输入流,解码完成后需手动关闭inputStream
。
(2) 解码指定区域
val rect = Rect(left, top, right, bottom) // 要解码的区域坐标
val options = BitmapFactory.Options().apply {inPreferredConfig = Bitmap.Config.RGB_565 // 减少内存占用inSampleSize = 2 // 可选:进一步缩放
}
val regionBitmap = decoder.decodeRegion(rect, options)
imageView.setImageBitmap(regionBitmap)
Rect
:指定要解码的矩形区域(单位:像素)。inSampleSize
:缩放系数(2 表示宽高各缩小一半)。inPreferredConfig
:Bitmap.Config.ARGB_8888
(默认,每个像素占 4 字节)Bitmap.Config.RGB_565
(推荐,每个像素占 2 字节,适合不透明图片)
(3) 释放资源
decoder.recycle() // 不再使用时释放内存
regionBitmap?.recycle() // 手动回收 Bitmap
2. 典型应用场景
(1) 超大图片分块加载(如地图)
// 根据 ImageView 当前显示区域动态加载
fun loadVisibleRegion(imageView: ImageView, decoder: BitmapRegionDecoder) {val visibleRect = getVisibleRect(imageView) // 计算可见区域val options = BitmapFactory.Options().apply {inPreferredConfig = Bitmap.Config.RGB_565}val regionBitmap = decoder.decodeRegion(visibleRect, options)imageView.setImageBitmap(regionBitmap)
}
(2) 高清长图浏览(类似微博长图)
结合 ImageView
的 OnTouchListener
或 RecyclerView
,动态解码当前屏幕区域:
imageView.setOnTouchListener { _, event ->when (event.action) {MotionEvent.ACTION_MOVE -> {val visibleRect = calculateVisibleRect(event) // 根据手势计算新区域val newRegion = decoder.decodeRegion(visibleRect, options)imageView.setImageBitmap(newRegion)}}true
}
3. 优化技巧
(1) 复用 Bitmap 对象
避免频繁创建/回收 Bitmap,使用 BitmapPool
(如 Glide 的实现):
val options = BitmapFactory.Options().apply {inBitmap = reusableBitmap // 复用已有 Bitmap 内存
}
(2) 内存缓存
使用 LruCache
缓存已解码的局部区域:
private val regionCache = LruCache<String, Bitmap>(maxSize)fun getCachedRegion(key: String): Bitmap? {return regionCache.get(key)
}fun cacheRegion(key: String, bitmap: Bitmap) {regionCache.put(key, bitmap)
}
(3) 异步加载
配合 Coroutine
或 RxJava
防止 UI 卡顿:
viewModelScope.launch(Dispatchers.IO) {val regionBitmap = decoder.decodeRegion(rect, options)withContext(Dispatchers.Main) {imageView.setImageBitmap(regionBitmap)}
}
4. 对比其他方案
方案 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
BitmapRegionDecoder | 超大图局部加载 | 精准控制内存占用 | 需手动计算区域坐标 |
Glide/Picasso | 常规图片加载 | 自动缓存、生命周期管理 | 不支持局部解码 |
SubsamplingScaleImageView | 开源库(支持手势缩放) | 功能完善 | 增加 APK 体积 |
5. 完整示例代码
class BigImageActivity : AppCompatActivity() {private lateinit var decoder: BitmapRegionDecoderprivate var currentBitmap: Bitmap? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_big_image)// 初始化解码器val inputStream = assets.open("huge_image.jpg")decoder = BitmapRegionDecoder.newInstance(inputStream, false)inputStream.close()// 首次加载中心区域loadInitialRegion()imageView.setOnTouchListener(gestureListener)}private fun loadInitialRegion() {val centerX = decoder.width / 2val centerY = decoder.height / 2val rect = Rect(centerX - 500, centerY - 500,centerX + 500, centerY + 500)currentBitmap?.recycle()currentBitmap = decoder.decodeRegion(rect, BitmapFactory.Options().apply {inPreferredConfig = Bitmap.Config.RGB_565})imageView.setImageBitmap(currentBitmap)}private val gestureListener = object : View.OnTouchListener {override fun onTouch(v: View, event: MotionEvent): Boolean {// 实现手势滑动逻辑(略)return true}}override fun onDestroy() {decoder.recycle()currentBitmap?.recycle()super.onDestroy()}
}
总结
- 使用场景:
BitmapRegionDecoder
适合加载无法缩放的全尺寸大图(如高清地图、医学影像)。 - 关键优化:通过
Rect
控制解码范围 +inSampleSize
缩放 +RGB_565
配置。 - 进阶方案:结合手势库(如
PhotoView
)实现交互式浏览。