引言
Bitmap是Android开发中常用的图像处理类,它能够加载和显示各种格式的图片。然而,Bitmap对象会占用大量的内存,特别是在加载高分辨率图片时,很容易导致内存溢出(OOM)问题。因此,掌握Bitmap优化技巧对于提升Android应用性能至关重要。
Bitmap内存占用分析
Bitmap对象的内存占用主要取决于以下两个因素:
- 图像尺寸: 图像的尺寸越大,占用的内存就越多。
- 图像格式: 不同的图像格式具有不同的存储方式,因此占用的内存也会有所不同。
可以通过以下公式计算 Bitmap 对象的内存占用:
内存占用 = 图像宽度 * 图像高度 * 每个像素所占字节数
例如: 一张分辨率为 1080 * 1920 的 RGBA 格式的 Bitmap 对象,其内存占用为:
1080 * 1920 * 4 = 8294400 字节
也就是说,这张图片占用了约 8 MB 的内存。
有了这些基础,针对Bitmap的优化方式就简单许多,主要有以下6种,下面详细来分析一下。
采样率压缩
采样率压缩是通过降低Bitmap的分辨率来减少其内存占用。BitmapFactory.Options类提供了inSampleSize属性来控制采样率,该属性的值表示解码后的Bitmap宽高将为原始Bitmap宽高的1/inSampleSize。例如,设置inSampleSize为2,则解码后的Bitmap宽高将为原始Bitmap的一半,内存占用也将减少四分之一。
BitmapFactory类提供的参数:
参数 | 描述 |
---|---|
inSampleSize | 采样率 |
inPreferredConfig | 色彩模式 |
inJustDecodeBounds | 是否仅解码图像边界 |
inPurgeable | 是否可被系统回收 |
inMutable | 是否可变 |
// 加载本地资源图片,并进行压缩处理
fun loadBitmap(context: Context, @DrawableRes resId: Int, reqWidth: Int, reqHeight: Int): Bitmap {
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeResource(context.resources, resId, options)
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
options.inJustDecodeBounds = false
return BitmapFactory.decodeResource(context.resources, resId, options)
}
// 计算采样率
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
val (width, height) = options.run { outWidth to outHeight }
var inSampleSize = 1
if (width > reqWidth || height > reqHeight) {
val halfWidth = width / 2
val halfHeight = height / 2
while ((halfWidth / inSampleSize) >= reqWidth && (halfHeight / inSampleSize) >= reqHeight) {
inSampleSize *= 2
}
}
return inSampleSize
}
质量压缩
质量压缩是通过降低图像质量来减少其文件大小。Bitmap类提供了compress()方法来进行质量压缩,该方法接受两个参数:
- outFormat:指定压缩后的图像格式,常见的格式包括JPEG、PNG等。
- quality:指定压缩质量,取值范围为0到100,数值越小,压缩率越高,图像质量越低。
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.image)
val outStream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outStream)
val bytes = outStream.toByteArray()
使用低色彩格式
Bitmap支持多种色彩格式,每种格式占用不同的内存空间。例如,ARGB_8888格式每个像素占用4个字节,而RGB_565格式每个像素仅占用2个字节。因此,在不需要透明度的情况下,可以使用低色彩格式来减少Bitmap内存占用。
模式 | 描述 | 内存占用 |
---|---|---|
ARGB_8888 | 每个像素包含8位透明度、8位红色、8位绿色和8位蓝色 | 4字节 |
RGB_565 | 每个像素包含5位红色、6位绿色和5位蓝色 | 2字节 |
ALPHA_8 | 每个像素包含8位透明度 | 1字节 |
val options = BitmapFactory.Options()
options.inPreferredConfig = Bitmap.Config.RGB_565
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.image, options)
复用Bitmap
Bitmap对象可以被复用,以避免频繁创建和销毁Bitmap对象导致的内存开销。可以通过LruCache等缓存机制来管理Bitmap对象的复用。
val lruCache = LruCache<String, Bitmap>(maxSize)
fun getBitmap(key: String): Bitmap? {
var bitmap = lruCache.get(key)
if (bitmap == null) {
bitmap = BitmapFactory.decodeResource(resources, R.drawable.image)
lruCache.put(key, bitmap)
}
return bitmap
}
除此之外,可以使用 BitmapFactory.Options 的 inBitmap 属性来指定一个可复用的 Bitmap 对象。
options.inBitmap = bitmap
使用硬件Bitmap
Android 8.0 (API 26) 引入了硬件Bitmap,它可以将Bitmap数据存储在GPU内存中,从而减少内存占用并提升绘制效率。
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.HARDWARE);
val canvas = Canvas(bitmap)
// 绘制内容
及时回收
及时回收不再需要的Bitmap是防止内存泄漏的重要步骤。调用Bitmap.recycle()方法可以释放Bitmap占用的内存。
bitmap.recycle()
注意事项
- 采样率压缩会导致图像质量下降,应根据实际需求选择合适的采样率。
- 质量压缩会导致图像细节丢失,应根据实际需求选择合适的压缩质量。
- 低色彩格式不适用于需要透明度的图像。
- 复用Bitmap需要根据应用场景设计合理的缓存策略。
- 硬件Bitmap需要在API 26及以上的设备上才能使用。
总结
掌握Bitmap优化技巧可以有效提升Android应用性能,避免OOM异常。本文介绍了6种常见的Bitmap优化技巧,大家可以根据实际需求选择合适的优化技巧。
最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)
PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题