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

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

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

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

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

性能优化:Bitmap内存大小优化的几种常见方式_第1张图片

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

性能优化:Bitmap内存大小优化的几种常见方式_第2张图片

而每个新建的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格式。

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

性能优化:Bitmap内存大小优化的几种常见方式_第3张图片

原图大小为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)

性能优化:Bitmap内存大小优化的几种常见方式_第4张图片

可以明显的看到,图片的宽高各自缩小为原来的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)

性能优化:Bitmap内存大小优化的几种常见方式_第5张图片

由于我们设置的缩放比是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内存大小优化的几种常见方式_第6张图片

我们将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内存大小优化的几种常见方式_第7张图片

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

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

项目地址

tinyvampirepudge/AndroidStudy

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

BitmapCompressActivity

参考

How to get FileInputStream to File in assets folder

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

你可能感兴趣的:(Bitmap,Android,android)