总结

  1. 使用完毕后,及时释放资源。
  2. 根据分辨率适配图片,不同分辨率的图片放到对应分辨率的资源中。
  3. 缩放图片,主要是对图片进行尺寸缩放,质量缩放只对存图时有用。
  4. 按需解码,选择合适解码方式,可以使用默认的 ARGB_8888。
  5. 使用缓存,三级缓存。

image.png

具体优化方法说明

1、使用完毕后,及时释放资源

  • 优化原因
    使用完毕后若不释放图片资源,容易造成内存泄露,从而导致内存溢出
  • 优化方案
    a. 在 Android2.3.3(API 10)前,调用 Bitmap.recycle()方法
    b. 在 Android2.3.3(API 10)后,采用软引用(SoftReference)
  • 具体描述
    在 Android2.3.3(API 10)前、后,Bitmap对象 & 其像素数据 的存储位置不同,从而导致释放图片资源的方式不同,具体如下图

图片存储位置:
image.png
8.0Bitmap的像素数据存储在Native,为什么又改为Native存储呢?因为8.0共享了整个系统的内存,测试8.0手机如果一直创建Bitmap,如果手机内存有1G,那么你的应用加载1G也不会oom。

2、根据分辨率适配

资源文件合理放置,高分辨率图片可以放到高分辨率目录下,和图片的具体分辨率有关,建议开发中,高分辨率的图像应该放置到合理的资源目录下,注意到Android默认放置的资源目录是对应于160dpi,目前手机屏幕分辨率越来越高,此处能节省下来的开销也是很可观的。理论上,图片放置的资源目录分辨率越高,其占用内存会越小,但是低分辨率图片会因此被拉伸,显示上出现失真。另一方面,高分辨率图片也意味着其占用的本地储存也变大。

  • 优化原因
    若 Bitmap 与 当前设备的分辨率不匹配,则会拉伸Bitmap,而Bitmap分辨率增加后,所占用的内存也会相应增加

    因为Bitmap 的内存占用 根据 x、y的大小来增加的

  • 优化方案

image.png

3、缩放图片

图片缩小,减少尺寸,理论上根据适用的环境,是可以减少十几倍的内存使用的,它基于这样一个事实:源图片尺寸一般都大于目标需要显示的尺寸,因此可以通过缩放的方式,来减少显示时的图片宽高,从而大大减少占用的内存。
前两种方式,相对比较简单。第三种方式会涉及到一些编码,目前也有很多典型的使用方式,如下:

  1. BitmapFactory.Options options = new BitmapFactory.Options();
  2. options.inPreferredConfig = Bitmap.Config.RGB_565;
  3. options.inJustDecodeBounds = true;
  4. BitmapFactory.decodeResource(getResources(), resId,options);
  5. options.inJustDecodeBounds = false;
  6. options.inSampleSize = BitmapUtil.computeSampleSize(options, -1, imageView.getWidth() * imageView.getHeight());
  7. Bitmap newBitmap = BitmapFactory.decodeResource(getResources(), resId, options);

原理很简单,充分利用了Options类里的参数设置,也可以从native底层源码上看到对应的逻辑。第一次解析bitmap只获取尺寸信息,不生成像素数据,继而比较bitmap尺寸和目标尺寸得到缩放倍数,第二次根据缩放倍数去解析我们实际需要的尺寸大小。

  1. // Apply a fine scaling step if necessary.
  2. if (needsFineScale(codec->getInfo().dimensions(), size, sampleSize)) {
  3. willScale = true;
  4. scaledWidth = codec->getInfo().width() / sampleSize;
  5. scaledHeight = codec->getInfo().height() / sampleSize;
  6. }

末尾会加上尺寸压缩和质量压缩的方法。

4、按需选择合适的解码方式

  • 优化原因
    不同的图片解码方式 对应的 内存占用大小 相差很大,具体如下

image.png

  • 优化方案
    根据需求 选择合适的解码方式
    1. 使用参数:BitmapFactory.inPreferredConfig 设置
    2. 默认使用解码方式:ARGB_8888

5、使用缓存

  • 优化原因
    重复加载图片资源耗费太多资源(CPU、内存 & 流量)
  • 优化方案

示意图
image.png
关于三级缓存机制,此处不作过多描述,具体请看文章:三级缓存说明

压缩方法

  • 尺寸压缩

    1. fun zoomBitmap(bitmap: Bitmap, scaleWidth: Float, scaleHeight: Float): Bitmap {
    2. // 获取这个图片的宽和高
    3. val width = bitmap.width.toFloat()
    4. val height = bitmap.height.toFloat()
    5. // 创建操作图片用的matrix对象
    6. val matrix = Matrix()
    7. // 缩放图片动作
    8. matrix.postScale(scaleWidth, scaleHeight)
    9. return Bitmap.createBitmap(
    10. bitmap, 0, 0, width.toInt(),
    11. height.toInt(), matrix, true
    12. )
    13. }
  • 质量压缩

    1. fun compressBitmap(image: Bitmap): Bitmap? {
    2. val baos = ByteArrayOutputStream()
    3. image.compress(Bitmap.CompressFormat.JPEG, 100, baos) //质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
    4. var options = 90
    5. var bytes: Int = baos.toByteArray().size
    6. while (bytes / 1024 > 100 && options >= 20) { //循环判断如果压缩后图片是否大于10kb,大于继续压缩
    7. baos.reset() //重置baos即清空baos
    8. options -= 10 //每次都减少10
    9. //第一个参数 :图片格式 ,第二个参数: 图片质量,100为最高,0为最差 ,第三个参数:保存压缩后的数据的流
    10. image.compress(Bitmap.CompressFormat.JPEG, options, baos) //这里压缩options%,把压缩后的数据存放到baos中
    11. bytes = baos.toByteArray().size
    12. }
    13. image.recycle()
    14. val isBm = ByteArrayInputStream(baos.toByteArray()) //把压缩后的数据baos存放到ByteArrayInputStream中
    15. return BitmapFactory.decodeStream(isBm, null, null) //把ByteArrayInputStream数据生成图片
    16. }

    参考

    手把手教你优化Bitmap图片资源的使用
    聊聊 Bitmap 的一些知识点
    Android Bitmap优化: 关于 Bitmap 你要知道的一切
    Android中Bitmap内存优化