总结
- 使用完毕后,及时释放资源。
- 根据分辨率适配图片,不同分辨率的图片放到对应分辨率的资源中。
- 缩放图片,主要是对图片进行尺寸缩放,质量缩放只对存图时有用。
- 按需解码,选择合适解码方式,可以使用默认的 ARGB_8888。
- 使用缓存,三级缓存。
具体优化方法说明
1、使用完毕后,及时释放资源
- 优化原因
使用完毕后若不释放图片资源,容易造成内存泄露,从而导致内存溢出 - 优化方案
a. 在 Android2.3.3(API 10)前,调用 Bitmap.recycle()方法
b. 在 Android2.3.3(API 10)后,采用软引用(SoftReference) - 具体描述
在 Android2.3.3(API 10)前、后,Bitmap对象 & 其像素数据 的存储位置不同,从而导致释放图片资源的方式不同,具体如下图
图片存储位置:
8.0Bitmap的像素数据存储在Native,为什么又改为Native存储呢?因为8.0共享了整个系统的内存,测试8.0手机如果一直创建Bitmap,如果手机内存有1G,那么你的应用加载1G也不会oom。
2、根据分辨率适配
资源文件合理放置,高分辨率图片可以放到高分辨率目录下,和图片的具体分辨率有关,建议开发中,高分辨率的图像应该放置到合理的资源目录下,注意到Android默认放置的资源目录是对应于160dpi,目前手机屏幕分辨率越来越高,此处能节省下来的开销也是很可观的。理论上,图片放置的资源目录分辨率越高,其占用内存会越小,但是低分辨率图片会因此被拉伸,显示上出现失真。另一方面,高分辨率图片也意味着其占用的本地储存也变大。
优化原因
若 Bitmap 与 当前设备的分辨率不匹配,则会拉伸Bitmap,而Bitmap分辨率增加后,所占用的内存也会相应增加因为Bitmap 的内存占用 根据 x、y的大小来增加的
优化方案
3、缩放图片
图片缩小,减少尺寸,理论上根据适用的环境,是可以减少十几倍的内存使用的,它基于这样一个事实:源图片尺寸一般都大于目标需要显示的尺寸,因此可以通过缩放的方式,来减少显示时的图片宽高,从而大大减少占用的内存。
前两种方式,相对比较简单。第三种方式会涉及到一些编码,目前也有很多典型的使用方式,如下:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), resId,options);
options.inJustDecodeBounds = false;
options.inSampleSize = BitmapUtil.computeSampleSize(options, -1, imageView.getWidth() * imageView.getHeight());
Bitmap newBitmap = BitmapFactory.decodeResource(getResources(), resId, options);
原理很简单,充分利用了Options类里的参数设置,也可以从native底层源码上看到对应的逻辑。第一次解析bitmap只获取尺寸信息,不生成像素数据,继而比较bitmap尺寸和目标尺寸得到缩放倍数,第二次根据缩放倍数去解析我们实际需要的尺寸大小。
// Apply a fine scaling step if necessary.
if (needsFineScale(codec->getInfo().dimensions(), size, sampleSize)) {
willScale = true;
scaledWidth = codec->getInfo().width() / sampleSize;
scaledHeight = codec->getInfo().height() / sampleSize;
}
4、按需选择合适的解码方式
- 优化原因
不同的图片解码方式 对应的 内存占用大小 相差很大,具体如下
- 优化方案
根据需求 选择合适的解码方式- 使用参数:BitmapFactory.inPreferredConfig 设置
- 默认使用解码方式:ARGB_8888
5、使用缓存
- 优化原因
重复加载图片资源耗费太多资源(CPU、内存 & 流量) - 优化方案
示意图
关于三级缓存机制,此处不作过多描述,具体请看文章:三级缓存说明
压缩方法
尺寸压缩
fun zoomBitmap(bitmap: Bitmap, scaleWidth: Float, scaleHeight: Float): Bitmap {
// 获取这个图片的宽和高
val width = bitmap.width.toFloat()
val height = bitmap.height.toFloat()
// 创建操作图片用的matrix对象
val matrix = Matrix()
// 缩放图片动作
matrix.postScale(scaleWidth, scaleHeight)
return Bitmap.createBitmap(
bitmap, 0, 0, width.toInt(),
height.toInt(), matrix, true
)
}
质量压缩
fun compressBitmap(image: Bitmap): Bitmap? {
val baos = ByteArrayOutputStream()
image.compress(Bitmap.CompressFormat.JPEG, 100, baos) //质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
var options = 90
var bytes: Int = baos.toByteArray().size
while (bytes / 1024 > 100 && options >= 20) { //循环判断如果压缩后图片是否大于10kb,大于继续压缩
baos.reset() //重置baos即清空baos
options -= 10 //每次都减少10
//第一个参数 :图片格式 ,第二个参数: 图片质量,100为最高,0为最差 ,第三个参数:保存压缩后的数据的流
image.compress(Bitmap.CompressFormat.JPEG, options, baos) //这里压缩options%,把压缩后的数据存放到baos中
bytes = baos.toByteArray().size
}
image.recycle()
val isBm = ByteArrayInputStream(baos.toByteArray()) //把压缩后的数据baos存放到ByteArrayInputStream中
return BitmapFactory.decodeStream(isBm, null, null) //把ByteArrayInputStream数据生成图片
}
参考
手把手教你优化Bitmap图片资源的使用
聊聊 Bitmap 的一些知识点
Android Bitmap优化: 关于 Bitmap 你要知道的一切
Android中Bitmap内存优化