性能优化:Android中Bitmap内存大小优化的几种常见方式

简介: 性能优化:Android中Bitmap内存大小优化的几种常见方式

性能优化:Android中Bitmap内存大小优化的几种常见方式

Android中的bitmap是比较占用内存的,bitmap的大小直接影响到了应用占用内存的大小。bitmap占用内存大小的计算方式为:

bitmap内存大小 = bitmap宽度(px) * bitmap长度(px) * 一个像素点占用的字节数

BitmapFactory给我们提供了多个decode方法,我们可以从不同的数据源中加载bitmap,如下图所示:

BitmapFactory加载Bitmap

一个像素点占用的字节数对应的参数是Bitmap.Config,它是一个枚举类,具体取值如下:

Bitmap.Config

而每个新建的Bitmap的默认Config的值就Bitmap.Config.ARGB_8888,它表示一个像素点占用4个字节((8 + 8 + 8 + 8) / 8 = 4byte)。

改变Bitmap内存大小

根据上面的原理,我们可以从两个方面减少Bitmap的内存占用,一个是改变Bitmap的宽高,另一个是改变Bitmap.Config的值,将Bitmap.Config.ARGB_8888改为占用字节更少的Bitmap.Config.ARGB_4444或者Bitmap.Config.RGB_565

具体有以下几种方法:

1、采样率压缩(改变Bitmap大小)。

2、通过martix进行压缩(改变Bitmap大小)。Bitmap.createBitmap或者Bitmap.createScaledBitmap方法。

3、更改Bitmap.Config格式。

具体示例如下:
我们先来看下原图大小:

原图

原图大小为640px * 360px,且是放置在assets目录下的,表示系统不会有缩放操作;如果是放在对应的drawable目录下,则会由系统进行相应的缩放操作。

1、采样率压缩(改变Bitmap大小)。

val assetFileDescriptor = assets.openFd("maomi.jpg")
val fileInputStream = assetFileDescriptor.createInputStream()
val bmpInSampleSizeJpg = BitmapOptionsUtil.decodeSampledBitmapFromFileStream(
    fileInputStream,
    ScreenUtils.dip2px(this, 80f),
    ScreenUtils.dip2px(this, 45f)
)
val descBmpConfigJpg =
    "height:${bmpInSampleSizeJpg?.height},\nwidth:${bmpInSampleSizeJpg?.width},\nallocationByteCount:${bmpInSampleSizeJpg?.allocationByteCount}byte,\n" +
            "byteCount:${bmpInSampleSizeJpg?.byteCount}byte,\nrowBytes:${bmpInSampleSizeJpg?.rowBytes}byte,\ndensity:${bmpInSampleSizeJpg?.density}"
tvInSampleSizeJpgInfo.text = "Jpg通过inSampleSize压缩后的信息:$descBmpConfigJpg"
ivInSampleSizeJpg.setImageBitmap(bmpInSampleSizeJpg)

采样率

可以明显的看到,图片的宽高各自缩小为原来的1/4,bitmap占用内存大小为原来的1/16。

采样率压缩Bitmap大小的方式,适用于与原有的图片对象宽高比较大,而目标Bitmap的尺寸比较小,此时就没有必要将Bitmap按照原有的大尺寸加载进来,可以有效的避免内存浪费和OOM。具体采样率(inSampleSize)的计算,请看BitmapOptionsUtil

2、通过martix进行压缩(改变Bitmap大小)。

val matrix = Matrix()
matrix.setScale(0.1f, 0.1f)
val bmpMatrixJpg = Bitmap.createBitmap(
    bmpOriginJpg, 0, 0, bmpOriginJpg.getWidth(),
    bmpOriginJpg.getHeight(), matrix, true
)
// Bitmap.createScaledBitmap内部也会使用Matrix进行缩放
//            val bmpMatrixJpg = Bitmap.createScaledBitmap(
//                bmpOriginJpg,
//                ScreenUtils.dip2px(this, 60f),
//                ScreenUtils.dip2px(this, 45f),
//                true
//            )
val descBmpConfigJpg =
    "height:${bmpMatrixJpg?.height},\nwidth:${bmpMatrixJpg?.width},\nallocationByteCount:${bmpMatrixJpg?.allocationByteCount}byte,\n" +
            "byteCount:${bmpMatrixJpg?.byteCount}byte,\nrowBytes:${bmpMatrixJpg?.rowBytes}byte,\ndensity:${bmpMatrixJpg?.density}"
tvMatrixJpgInfo.text = "Jpg通过Matrix压缩后的信息:$descBmpConfigJpg"
ivMatrixJpg.setImageBitmap(bmpMatrixJpg)

Matrix缩放Bitmap

由于我们设置的缩放比是0.1f,也就是宽高均是之前的1/10,所以压缩后的bitmap占用的内存大小变为原来的1/100。

这种情况适用原图大小和目标bitmap大小均已知的情况。

3、更改Bitmap.Config格式。

val option = BitmapFactory.Options()
option.inPreferredConfig = Bitmap.Config.ARGB_4444
//  option.inPreferredConfig = Bitmap.Config.RGB_565 // 对透明度没要求的话可以试一下rgb_565
val bmpBmpConfigJpg = BitmapFactory.decodeStream(assets.open("maomi.jpg"), null, option)
val descBmpConfigJpg =
    "height:${bmpBmpConfigJpg?.height},\nwidth:${bmpBmpConfigJpg?.width},\nallocationByteCount:${bmpBmpConfigJpg?.allocationByteCount}byte,\n" +
            "byteCount:${bmpBmpConfigJpg?.byteCount}byte,\nrowBytes:${bmpBmpConfigJpg?.rowBytes}byte,\ndensity:${bmpBmpConfigJpg?.density}"
tvBmpConfigJpgInfo.text = "Jpg通过Bitmap.Config压缩后的信息:$descBmpConfigJpg"
ivBmpConfigJpg.setImageBitmap(bmpBmpConfigJpg)

Bitmap.Config减少内存占用

我们将Bitmap.Config的值改为了根据输出的byteCount的值改为了Bitmap.Config.ARGB_4444,根据byteCount输出的值可以明显的看到,bitmap的内存大小减少了一半。

如果对透明度没要求的话可以试一下Bitmap.Config.RGB_565

这种情况适用于对图片分辨率要求不高的情况。

通过Bitmap#compress方法压缩,质量压缩。

还有一种很重要的压缩方式,通过Bitmap#compress方法,修改quality的值,来改变Bitmap生成的字节流的大小。这种方法不会改变Bitmap占用的内存大小。

质量压缩不会减少图片的像素,它是在保持像素的前提下改变图片的位深及透明度等,来达到压缩图片的目的。图片的长,宽,像素都不变,那么bitmap所占内存大小是不会变的。这里改变的是bitmap对应的字节数组的大小,适合去传递二进制的图片数据,比如微信分享。

val bytearray = getBytesFromCompressBitmap(bmpOriginJpg, 32 * 1024)
val bmpQualityJpg = BitmapFactory.decodeByteArray(bytearray, 0, bytearray.size)
val descQualityJpg =
    "height:${bmpQualityJpg.height},\nwidth:${bmpQualityJpg.width},\nallocationByteCount:${bmpQualityJpg.allocationByteCount}byte,\n" +
            "byteCount:${bmpQualityJpg.byteCount}byte,\nrowBytes:${bmpQualityJpg.rowBytes}byte,\ndensity:${bmpQualityJpg.density},\n" +
            "bytearray:${bytearray.size}"
tvQualityJpgInfo.text = "Jpg进行Quality压缩后的信息:$descQualityJpg"
ivQualityJpg.setImageBitmap(bmpQualityJpg)
/**
 * 将Bitmap的字节流压缩为目标大小
 * @targetSize  单位为Byte
 */
private fun getBytesFromCompressBitmap(
    bitmap: Bitmap,
    targetSize: Int
): ByteArray {
    val baos = ByteArrayOutputStream()
    var quality = 100
    bitmap.compress(Bitmap.CompressFormat.PNG, quality, baos)
    var bytes = baos.toByteArray()
    while (bytes.size > targetSize && quality >= 5) {
        quality -= 5
        if (quality < 0) {
            quality = 0
        }
        // 重置,不然会累加
        baos.reset()
        bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos)
        bytes = baos.toByteArray()
    }
    try {
        baos.close()
    } catch (e: Exception) {
        e.printStackTrace()
    }
    return bytes
}

质量压缩

可以看到,质量压缩不会改变原有bitmap的大小,它改变的是通过Bitmap#compress方法的字节流。

具体开发过程中,可以根据需要自行选择合适的方式。

项目地址

tinyvampirepudge/AndroidStudy

具体页面地址:提供了jpg和png两种图片格式的demo。

BitmapCompressActivity

参考

How to get FileInputStream to File in assets folder

bitmap的六种压缩方式,Android图片压缩

相关文章
|
4天前
|
缓存 程序员 定位技术
Android Studio 插件,那些被大厂优化的程序员们
Android Studio 插件,那些被大厂优化的程序员们
|
4天前
|
设计模式 算法 前端开发
Android面经分享,失业两个月,五一节前拿到Offer,设计思想与代码质量优化+程序性能优化+开发效率优化
Android面经分享,失业两个月,五一节前拿到Offer,设计思想与代码质量优化+程序性能优化+开发效率优化
|
4天前
|
测试技术 Android开发
Android开发规范,性能优化,Android面试2024
Android开发规范,性能优化,Android面试2024
|
5天前
|
Android开发
Android WindowFeature小探究,Android客户端Web页面通用性能优化实践
Android WindowFeature小探究,Android客户端Web页面通用性能优化实践
|
1天前
|
缓存 移动开发 Android开发
Android应用性能优化实践
【5月更文挑战第20天】 在移动开发领域,应用的性能直接关乎用户体验。特别是对于Android平台,由于设备多样性和应用生态环境的复杂性,性能优化成为了开发者必须面对的挑战。本文将深入探讨Android应用性能优化的多个方面,包括内存管理、UI渲染、多线程处理以及电池效率等,旨在为开发者提供一系列实用的优化策略和技巧。
|
1天前
|
缓存 编解码 移动开发
构建高效Android应用:从优化布局到提升性能
【5月更文挑战第20天】 在移动开发领域,一个流畅且响应迅速的应用是用户留存的关键。本文将深入探讨如何针对安卓平台优化应用性能,涵盖从布局优化、内存管理到多线程处理等多个方面。我们将通过实例和最佳实践指导开发者避免常见的性能陷阱,并利用现代安卓工具和技巧提高应用的整体效率。
|
2天前
|
并行计算 索引 Python
讨论如何优化 DataFrame 操作,减少内存占用和提高执行速度
【5月更文挑战第19天】优化 DataFrame 操作涉及选择合适的数据类型、避免复制、使用向量化、高效迭代和设置索引。通过这些策略,如使用 `np.int8` 节省内存,直接修改列数据,利用 `itertuples`,设置分类数据类型,以及分块和并行计算,可以显著减少内存占用和提高执行速度,从而更好地处理大规模数据。实践中需结合具体情况综合运用,不断测试和优化。
12 2
|
3天前
|
缓存 移动开发 Android开发
构建高效Android应用:从内存优化到电池寿命
【5月更文挑战第18天】在移动开发领域,一个优秀的Android应用不仅要拥有流畅的用户界面和丰富的功能,更要在设备资源有限的前提下保持高效运行。本文将探讨Android应用开发中关键的性能优化策略,包括内存使用优化、CPU使用减少和电池寿命延长等方面。通过分析常见的性能瓶颈和提供实用的解决方案,帮助开发者打造更高效、更受欢迎的Android应用。
|
3天前
|
算法 Java Python
【Python 的内存管理机制专栏】Python 内存管理实战:性能优化与内存泄漏检测
【5月更文挑战第18天】Python内存管理关乎程序性能与稳定性。优化包括避免过多临时对象,如优化列表推导式减少对象创建。警惕循环引用造成的内存泄漏,如示例中的Node类。使用`gc`模块检测泄漏,通过`gc.set_debug(gc.DEBUG_LEAK)`和`gc.collect()`获取信息。实践中需持续分析内存使用,优化算法、数据结构和资源释放,以提升程序质量与效率。
【Python 的内存管理机制专栏】Python 内存管理实战:性能优化与内存泄漏检测
|
4天前
|
存储 定位技术 开发工具
Android 开发前的设计,Android之内存泄漏调试学习与总结
Android 开发前的设计,Android之内存泄漏调试学习与总结