Android 性能优化-Bitmap知识梳理 & 高效加载

2022-10-27  本文已影响0人  JackDaddy

一、Bitmap 占用内存计算

bitmap 的内存计算可由下面的计算公式得出来:

Bitmap 内存占用 ≈ 像素数据总大小 = 图片宽 × 图片高× ( 设备dpi / 资源目录dpi ) ^ 2 × 单个像素的字节大小

其中单个像素的字节大小由 Bitmap 的一个可配置的参数 Config 来决定。Bitmap 中存在一个枚举类 Config,定义了 Android 中支持的 Bitmap 配置:

Bitmap Config 参数

Android 系统中,默认 Bitmap 加载图片使用24位真彩色(ARGB_8888)模式。
资源目录dpi 跟图片存放的资源文件的目录有关系:

当图片不特别放置任何资源目录时,其默认使用 mdpi 分辨率:160

根据我们上面的公式计算来验证一下:

res\drawable-xhdpi:1920 * 2304 * (272/ 160)^ 2 * 4 = 17280000
res\drawable-xxxhdpi:1920 * 2304 * (272/ 640)^ 2 * 4 = 1080000
res\drawable-xxhdpi:1920 * 2304 * (272/ 480)^ 2 * 4 = 1920000

二、BitMap 优化

Bitmap内存优化从下面四个方面进行优化:

2.1 编码

在第一节中已经列举出的枚举配置中存在几种不同的配置:
其中,A代表透明度;R代表红色;G代表绿色;B代表蓝色。

也即是说我们可以通过改变图片格式,来改变每个像素占用字节数,来改变占用的内存,看下面代码

fun compressBitmap() {
        // 不获取图片,不加载到内存,只返回图片的宽高
        val mOptions = BitmapFactory.Options()
        mOptions.inJustDecodeBounds = true
        BitmapFactory.decodeResource(resources, R.drawable.yasuo, mOptions)
        // 获取图片的宽高
        val mOrigWidth = mOptions.outWidth
        val mOrigHeight = mOptions.outHeight
        Log.e(TAG, "原图:mOrigWidth = $mOrigWidth, mOrigHeight = $mOrigHeight")

        // 图片压缩-更改图片格式
        mOptions.inPreferredConfig = Bitmap.Config.RGB_565
        mOptions.inJustDecodeBounds = false
        val mTestBitmap = BitmapFactory.decodeResource(resources, R.drawable.yasuo, mOptions)
        Log.e(TAG, "===========================压缩后=================================")
        Log.e(TAG, "mTestBitmap width = ${mTestBitmap.width}, height = ${mTestBitmap.height}")
        Log.e(TAG,"mTestBitmap byteCount = ${mTestBitmap.byteCount}")
        Log.e(TAG,"mTestBitmap allocationByteCount = ${mTestBitmap.allocationByteCount}")
    }


从日志中可以看出从改变bitmap的格式可以有效的降低其占用内存大小。从 ARGB_8888 切换到 RGB_565 减少约 2 倍的内存大小。

2.2 采样

我们了解到了计算bitmap的占用内存的方法 ,是以bitmap的宽高和每个像素占用的字节数决定的。图片的大小就是bitmap的宽高,按公式我们可以缩减bitmap的宽高来达到压缩图片占用内存的目的,看下面代码,以缩减宽高来达到压缩的目的

fun decodeSampleSizeBitmapWithRes(res: Resources, resId: Int, reqWidth: Int, reqHeight: Int) {
        val mOption = BitmapFactory.Options()
        mOption.inJustDecodeBounds = true
        BitmapFactory.decodeResource(res, resId, mOption)
        mOption.inSampleSize = calculateInSampleSize(mOption, reqWidth, reqHeight)
        Log.e("mTestBitmap", "sampleSize:${calculateInSampleSize(mOption, reqWidth, reqHeight)}")
        mOption.inJustDecodeBounds = false
        val mCompressBitmap = BitmapFactory.decodeResource(res, resId, mOption)
        Log.e(TAG, "===========================压缩后=================================")
        Log.e(TAG, "mTestBitmap width = ${mCompressBitmap.width}, height = ${mCompressBitmap.height}")
        Log.e(TAG, "mTestBitmap byteCount = ${mCompressBitmap.byteCount}")
        Log.e(TAG, "mTestBitmap allocationByteCount = ${mCompressBitmap.allocationByteCount}")
    }

这种我们根据BitmapFactory 的采样率进行压缩 设置采样率,不能小于1 假如是2 则宽为之前的1/2,高为之前的1/2,一共缩小1/4 一次类推,我们看到log ,确实起到了压缩的目的

2.2 复用

重复加载图片资源耗费太多资源(CPU、内存 & 流量),我们可以使用三级缓存机制,即内存缓存、本地缓存(硬盘、数据库、文件)、网络缓存。当加载 Bitmap 图片资源时,先从内存缓存中寻找;若内存缓存中没有,则从本地缓存中查找;若本地缓存没有,则从网络中加载寻找。

另外还可以使用软引用(内存空间不足时才回收这些对象的内存)的方式实现内存敏感的高速缓存。
同时还可以开启 inBitmap 这个属性
inBitmap 属性的作用:

不使用这个属性,你加载三张图片,系统会给你分配三份内存空间,用于分别储存这三张图片
如果用了inBitmap这个属性,加载三张图片,这三张图片会指向同一块内存,而不用开辟三块内存空间

inBitmap的限制

2.3 压缩

一个是上面讲到的采样压缩,另外一种则是下面的 质量压缩
质量压缩

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
//quality 为0~100,0表示最小体积,100表示最高质量,对应体积也是最大
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);

在上述代码中,我们选择的压缩格式是 CompressFormat.JPEG,除此之外还有两个选择:CompressFormat.PNG, PNG 格式是无损的,它无法再进行质量压缩,quality 这个参数就没有作用了,会被忽略,所以最后图片保存成的文件大小不会有变化;CompressFormat.WEBP ,这个格式是 google 推出的图片格式,它会比 JPEG 更加省空间,经过实测大概可以优化 30% 左右。

参考文章:

Android BitMap 优化
Android Bitmap 的高效加载解析
性能优化:Android中Bitmap内存大小优化的几种常见方式

上一篇下一篇

猜你喜欢

热点阅读