一句话总结:
用 Bitmap 就像搬砖------图片太大直接扛,内存分分钟爆缸!得学会"巧裁切、会缓存、勤回收",否则 App 卡崩没商量!
一、内存暴增,原地爆炸(OOM)
问题: 一张照片原图 4000x3000 像素 → ARGB_8888 格式占内存 48MB(4000x3000x4字节)!直接加载?低端机当场崩溃!
保命操作1:压缩到控件大小
kotlin
// 先获取图片尺寸,不加载内存
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeFile(path, options)
val (width, height) = options.outWidth to options.outHeight
// 计算缩放比例(目标控件大小 200x200)
val inSampleSize = calculateInSampleSize(options, 200, 200)
// 真正加载压缩后的图片
options.inJustDecodeBounds = false
options.inSampleSize = inSampleSize
val bitmap = BitmapFactory.decodeFile(path, options) // 内存降到 200x200x4 ≈ 0.16MB!
// 计算缩放比例的方法
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
val (width, height) = options.outWidth to options.outHeight
var inSampleSize = 1
if (height > reqHeight || width > reqWidth) {
val halfHeight = height / 2
val halfWidth = width / 2
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
inSampleSize *= 2
}
}
return inSampleSize
}
保命操作2:用低内存格式(有损画质)
ini
options.inPreferredConfig = Bitmap.Config.RGB_565 // 每个像素占2字节(省一半内存!)
二、不回收Bitmap,内存泄漏!
案例: 在 Activity 中加载 Bitmap 没回收 → Activity 销毁后 Bitmap 还在内存 → 反复打开关闭 → 内存泄漏!
解决:
-
用
Bitmap.recycle()
(谨慎使用!):kotlinoverride fun onDestroy() { bitmap?.recycle() // 手动回收(注意:回收后不能再使用!) bitmap = null super.onDestroy() }
-
用
LruCache
或Glide
自动管理:kotlin// LruCache 示例(最大内存的1/8) val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt() val cacheSize = maxMemory / 8 val bitmapCache = object : LruCache<String, Bitmap>(cacheSize) { override fun sizeOf(key: String, bitmap: Bitmap): Int { return bitmap.byteCount / 1024 } }
三、主线程加载大图,ANR 警告!
作死代码:
ini
button.setOnClickListener {
// 直接在主线程加载大图 → 卡死5秒 → ANR!
val bitmap = BitmapFactory.decodeFile("sdcard/big_image.jpg")
imageView.setImageBitmap(bitmap)
}
保命操作:子线程加载 + 切主线程显示
scss
// 用协程(Kotlin推荐)
lifecycleScope.launch(Dispatchers.IO) {
val bitmap = loadBitmapFromFile("sdcard/big_image.jpg")
withContext(Dispatchers.Main) {
imageView.setImageBitmap(bitmap)
}
}
四、重复创建Bitmap,内存抖动!
案例: ListView/RecyclerView 的 getView() 里频繁 new Bitmap → GC 疯狂回收 → UI 卡顿!
解决: 用内存缓存 + 磁盘缓存 + 复用 ConvertView
kotlin
// RecyclerView.ViewHolder 中复用 Bitmap
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private var currentBitmap: Bitmap? = null
fun bind(imagePath: String) {
// 异步加载并复用 Bitmap
loadBitmapAsync(imagePath) { bitmap ->
currentBitmap?.recycle() // 释放旧的
currentBitmap = bitmap
itemView.imageView.setImageBitmap(bitmap)
}
}
fun clear() {
currentBitmap?.recycle()
}
}
五、终极保命指南
- 能缩则缩: 图片尺寸对齐控件大小,别加载原图!
- 能用缓存就别新建: LruCache、DiskLruCache 搞起来!
- 能异步就别阻塞主线程: 协程、RxJava、AsyncTask 任选!
- 能复用就别回收: ViewHolder 复用 Bitmap,减少GC压力!
- 能交给库就别自己写: Glide、Picasso 它不香吗?
口诀:
"大图不压必崩,复用缓存保命,
主线程里别硬刚,Glide一甩全搞定!"
(附:第三方库 Glide 的终极偷懒写法👇)
scss
Glide.with(context)
.load("http://xxx.jpg")
.override(200, 200) // 强制缩放
.format(DecodeFormat.PREFER_RGB_565) // 低内存格式
.into(imageView)