5招精通Bitmap内存优化,告别OOM!

引言

Bitmap是Android开发中常用的图像处理类,它能够加载和显示各种格式的图片。然而,Bitmap对象会占用大量的内存,特别是在加载高分辨率图片时,很容易导致内存溢出(OOM)问题。因此,掌握Bitmap优化技巧对于提升Android应用性能至关重要。

Bitmap内存占用分析

Bitmap对象的内存占用主要取决于以下两个因素:

  • 图像尺寸: 图像的尺寸越大,占用的内存就越多。
  • 图像格式: 不同的图像格式具有不同的存储方式,因此占用的内存也会有所不同。

可以通过以下公式计算 Bitmap 对象的内存占用:

复制代码
内存占用 = 图像宽度 * 图像高度 * 每个像素所占字节数

例如: 一张分辨率为 1080 * 1920 的 RGBA 格式的 Bitmap 对象,其内存占用为:

ini 复制代码
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 是否可变
kotlin 复制代码
// 加载本地资源图片,并进行压缩处理
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,数值越小,压缩率越高,图像质量越低。
kotlin 复制代码
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字节
kotlin 复制代码
val options = BitmapFactory.Options()
options.inPreferredConfig = Bitmap.Config.RGB_565
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.image, options)

复用Bitmap

Bitmap对象可以被复用,以避免频繁创建和销毁Bitmap对象导致的内存开销。可以通过LruCache等缓存机制来管理Bitmap对象的复用。

kotlin 复制代码
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 对象。

ini 复制代码
options.inBitmap = bitmap

使用硬件Bitmap

Android 8.0 (API 26) 引入了硬件Bitmap,它可以将Bitmap数据存储在GPU内存中,从而减少内存占用并提升绘制效率。

kotlin 复制代码
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.HARDWARE);
val canvas = Canvas(bitmap)
// 绘制内容

及时回收

及时回收不再需要的Bitmap是防止内存泄漏的重要步骤。调用Bitmap.recycle()方法可以释放Bitmap占用的内存。

scss 复制代码
bitmap.recycle()

注意事项

  • 采样率压缩会导致图像质量下降,应根据实际需求选择合适的采样率。
  • 质量压缩会导致图像细节丢失,应根据实际需求选择合适的压缩质量。
  • 低色彩格式不适用于需要透明度的图像。
  • 复用Bitmap需要根据应用场景设计合理的缓存策略。
  • 硬件Bitmap需要在API 26及以上的设备上才能使用。

总结

掌握Bitmap优化技巧可以有效提升Android应用性能,避免OOM异常。本文介绍了6种常见的Bitmap优化技巧,大家可以根据实际需求选择合适的优化技巧。

推荐

android_startup: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。

AwesomeGithub: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。

flutter_github: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。

android-api-analysis: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。

daily_algorithm: 每日一算法,由浅入深,欢迎加入一起共勉。

相关推荐
夏幻灵3 分钟前
Express 静态资源托管详解:从本地 HTML 到 express.static()
前端·html·express
天空之城--6 分钟前
近一周Android开发动态:AI编码、Compose与自适应实践
android
mmsx20 分钟前
Android 混淆挡不住二次打包:一条完整的攻击链与突破口
android·混淆
yyuuuzz27 分钟前
云服务器部署 web 项目的进程常驻记录
运维·服务器·前端·阿里云·云计算·aws
IT_陈寒31 分钟前
一文搞懂React的核心概念
前端·人工智能·后端
薛一半1 小时前
Redux实战:组件状态管理全解析
开发语言·前端·javascript·react.js
风骏时光牛马1 小时前
功能模块性能测试用例验证
前端
对空六课2 小时前
两个统计平台数据不同:用同一时间窗和事件样本做差异定位
大数据·前端·网络·数据库
聚美智数9 小时前
智能结构化识别-图片结构解析-图像结构化解析-照片结构化解析
android·java·数据库
lpfasd12310 小时前
node-sass 停止维护!老项目迁移 Dart Sass 完整教程
前端·rust·sass