图片加载性能优化永远是Android领域中一个无法绕过的话题,经过数年的发展,涌现了很多成熟的图片加载开源库,比如Fresco、Picasso、UIL等等,使得图片加载不再是一个头疼的问题,并且大幅降低了OOM发生的概率。然而,在图片加载方面我们是否可以就此放松警惕了呢?
开源图片加载库能为我们解决绝大部分有关图片的问题,然而并不是所有!
首先,图片从来源上可以分成三大类:网络图片、手机图片、APK资源图片。网络图片和手机图片都在图片加载库功能的覆盖范围内,基本上不用开发者太操心,但是APK资源图片却不在此范围!
关于APK资源图片有3个特征:
1、资源图片基本都是在xml中引用 ,在Java中也是通过资源ID查找 。
2、资源图片一般不使用异步记载,不会出现loading图这些中间状态。
3、资源图片不会加载失败,如果失败了那么APP也挂掉了。
正是由于这3点特征,所以图片加载库实在鞭长莫及。那么就很容易出现一个问题:图片过大导致OOM!
很多APP为了追求酷炫的效果,热衷于设计绚丽全屏背景页面。既然是为了炫酷,考虑到用户体验,这些全屏背景图自然不能使用网络图片了,所以,这些图片都被放在apk包中作为资源文件直接引用。
使用这些资源图片的方式一般都是:
android:background="@drawable/xxx"
正常情况下,这样使用自然不会出现问题,但是如果APP内存紧张,很容易就出现OOM,尤其是5.0版本以下的手机,经常跑着跑着就Crash了,始作俑者就是这个。
为了解决这种问题,最常用的方式是找设计师压缩图片。而压缩图片有两种方式:缩小尺寸和降低质量。那么,这两种方式是否有效呢?
1、缩小尺寸: 压缩图片的宽度和高度。由于图片的内存占用与宽高成正比,这种方式确实有效,但是图片显示时会被拉伸导致变形,从而失却美感。
2、降低质量: 降低图片的色彩度,像素颜色密度。这其实是一个误区,很多人认为图片的存储占用空间小,图片的内存占用就会小,其实是错误的观点。这是方式并不会影响图片的内存占用,反而由于质量降低(下文具体分析),使得页面缺乏质感。必须记住:图片的内存占用与图片质量毫无干系!
为了寻求一个合理的解决方案,必须知彼知己。下面,我们来详细分析下资源图片的内存占用的情况!(后文所说的图片,除非特殊指明,否则都默认指APK资源图片)。
1、计算Bitmap的内存占用
我们以一张标准720p的全屏图片为例,宽高比为720x1280,对应的资源文件夹为drawable-xhdpi。同样,设备以标准720p的小米2S手机为例,density=320。
首先,android设备上图片都被处理成Bitmap对象。生成Bitmap有一个非常重要的参数Config,属性值有ALPHA_8、RGB_565、ARGB_4444、ARGB_8888四种。不同的属性值对应的图片每个像素点占用内存大小不同,ALPHA_8每个像素占用1byte,RGB_565和ARGB_4444占用2byte,ARGB_8888占用4byte,其中ARGB_4444在高版本中已经废弃。
那么,资源图片被decode成Bitmap的时候,Config参数值是哪个呢?来看几段代码。
Resources.java
private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) {
...
final String file = value.string.toString();
...
final Drawable dr;
if (file.endsWith(".xml")) {
final XmlResourceParser rp = loadXmlResourceParser(file, id, value.assetCookie, "drawable");
dr = Drawable.createFromXml(this, rp, theme);
rp.close();
} else {
final InputStream is = mAssets.openNonAsset(value.assetCookie, file, AssetManager.ACCESS_STREAMING);
dr = Drawable.createFromResourceStream(this, value, is, file, null);
is.close();
}
...
}
Drawable.java
public static Drawable createFromResourceStream(Resources res, TypedValue value,
InputStream is, String srcName, BitmapFactory.Options opts) {
...
if (opts == null) opts = new BitmapFactory.Options();
opts.inScreenDensity = res != null ? res.getDisplayMetrics().noncompatDensityDpi : DisplayMetrics.DENSITY_DEVICE;
Bitmap bm = BitmapFactory.decodeResourceStream(res, value, is, pad, opts);
...
return null;
}
BitmapFactory.java
public static class Options {
...
/**
* Image are loaded with the {@link Bitmap.Config#ARGB_8888} config by default.
*/
public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888;
...<