美文网首页
Android 性能优化-Bitmap知识梳理 & 高效加载

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

作者: JackDaddy | 来源:发表于2022-10-27 14:51 被阅读0次

    一、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代表蓝色。

    • ALPHA_8
      表示8位Alpha位图,即A=8,一个像素点占用1个字节,它没有颜色,只有透明度
    • ARGB_4444
      表示16位ARGB位图,即A=4,R=4,G=4,B=4,一个像素点占4+4+4+4=16位,2个字节
    • ARGB_8888
      表示32位ARGB位图,即A=8,R=8,G=8,B=8,一个像素点占8+8+8+8=32位,4个字节
    • RGB_565
      表示16位RGB位图,即R=5,G=6,B=5,它没有透明度,一个像素点占5+6+5=16位,2个字节

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

    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的限制

    • 3.0-4.3
      复用的图片大小必须相同
      编码必须相同
    • 4.4以上
      复用的空间大于等于即可
      编码不必相同
      不支持WebP
      图片复用,这个属性必须设置为true; options.inMutable = true;
    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内存大小优化的几种常见方式

    相关文章

      网友评论

          本文标题:Android 性能优化-Bitmap知识梳理 & 高效加载

          本文链接:https://www.haomeiwen.com/subject/hcrzzrtx.html