一句话总结:
Bitmap优化就像“为照片科学瘦身”,核心是区分两大目标:一是通过“降采样”和“格式转换”减少其在手机内存中的占地面积,避免OOM;二是通过“质量压缩”和“WebP格式”减小其文件**体积,节省存储和流量。
核心:两大优化目标及其策略
目标一:降低内存占用 (避免OutOfMemoryError)
Bitmap加载到内存后的大小由其像素数量和每个像素占用的字节数决定:内存大小 ≈ 宽 × 高 × 单像素字节数。所有降低内存的手段都围绕这个公式展开。
1. 采样率压缩(最高效的尺寸缩减方案)
这是在解码图片时直接加载一个缩小版的、内存占用更低的Bitmap,是避免OOM的首选方案。
标准三步法:
- 探测尺寸:设置
BitmapFactory.Options的inJustDecodeBounds = true,此时解码操作只会读取图片的宽高,不分配内存。 - 计算采样率:根据图片原始尺寸和目标View尺寸,计算出最佳的
inSampleSize值(表示宽和高分别缩小的倍数)。 - 正式解码:将
inJustDecodeBounds设为false,并应用上一步计算出的inSampleSize,最终解码出内存占用合理的Bitmap。1
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
val (height: Int, width: Int) = options.run { outHeight to outWidth }
var inSampleSize = 1
if (height > reqHeight || width > reqWidth) {
val halfHeight: Int = height / 2
val halfWidth: Int = width / 2
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
inSampleSize *= 2
}
}
return inSampleSize
}
2. 更改像素格式 (inPreferredConfig)
通过使用更节省空间的像素配置来减少内存。
Bitmap.Config.ARGB_8888(默认): 32位,每个像素占4字节,画质最高,支持透明度。Bitmap.Config.RGB_565: 16位,每个像素占2字节,内存占用减半,但不支持透明度,色彩细节略有损失。适用于无需透明效果的背景图、照片等。
3. 内存复用 (inBitmap) (高级技巧)
在解码新Bitmap时,复用旧Bitmap的内存。这能极大减少GC频率,防止内存抖动导致的卡顿,是RecyclerView等列表流畅性的关键。
目标二:减小文件体积 (节省存储与网络)
1. 质量压缩 (bitmap.compress)
这种方法通过降低图片的编码质量来减小文件的大小,常用于图片上传或保存场景。
// 将Bitmap以70%的质量压缩为JPEG格式
bitmap.compress(Bitmap.CompressFormat.JPEG, 70, outputStream)
关键警告:质量压缩不会改变Bitmap在内存中的大小。一个4K的图片,即使质量压缩到1%,加载到内存中依然是4K的大小,同样会引发OOM。
2. 使用更高效的图片格式 (WebP)
WebP是Google推出的现代图片格式,旨在提供更优的压缩。
- 优势:在同等画质下,WebP格式的文件体积通常比JPEG小25-35%,比PNG小26%(无损模式)。它还支持透明度和动画。
- 实践:在项目中应优先使用WebP格式来减小APK包体积和网络数据传输量。
现代Android中的Bitmap内存回收
误区纠正:你几乎不再需要手动调用 bitmap.recycle() 。
自Android 3.0以后,Bitmap的内存由GC自动管理。开发者需要做的不是手动回收,而是避免内存泄漏——即确保不再使用的Bitmap对象不被任何长生命周期的对象(如static变量)持有,以便GC能够及时回收它。错误的recycle()调用是导致应用崩溃的常见原因。
终极方案:善用图片加载库
像 Glide, Picasso, Coil 这样的现代图片加载库,已经在内部完美地封装了上述所有优化策略:
- 自动根据
ImageView尺寸进行采样率压缩。 - 拥有复杂的内存和磁盘缓存机制。
- 自动处理Bitmap的生命周期和内存复用。
在绝大多数情况下,直接使用这些库是最高效、最安全的选择。