首先分析一下 BitmapFactory.decodeResource 的实现代码:
public static Bitmap decodeResource(Resources res, int id, Options opts) {
Bitmap bm = null;
InputStream is = null;
try {
final TypedValue value = new TypedValue();
is = res.openRawResource(id, value);
bm = decodeResourceStream(res, value, is, null, opts);
} catch (Exception e) {
} finally {
try {
if (is != null) is.close();
} catch (IOException e) {
// Ignore
}
}
return bm;
}
可以看出 decodeResource 主要是通过调用 decodeResourceStream 来解码图片的。我们看看
decodeResourceStream 的实现:
public static Bitmap decodeResourceStream(Resources res, TypedValue value,
InputStream is, Rect pad, Options opts) {
if (opts == null) {
opts = new Options();
}
if (opts.inDensity == 0 && value != null) {
final int density = value.density;
if (density == TypedValue.DENSITY_DEFAULT) {
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
} else if (density != TypedValue.DENSITY_NONE) {
opts.inDensity = density;
}
}
if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}
return decodeStream(is, pad, opts);
}
我们看到,decodeResourceStream
会根据图片本身的密度以及屏幕的密度,设置合适的inDensity和inTargetDensity,以使 Android
可以对图片资源进行自适应缩放。设置完这两个参数,接着就直接调用 decodeStream 来完成解码任务。
我们继续分析 decodeStream 方法:
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
// 此处省略部分代码...
Bitmap bm;
if (is instanceof AssetManager.AssetInputStream) {
bm = nativeDecodeAsset(((AssetManager.AssetInputStream) is).getAssetInt(),
outPadding, opts);
} else {
// pass some temp storage down to the native code. 1024 is made up,
// but should be large enough to avoid too many small calls back
// into is.read(...) This number is not related to the value passed
// to mark(...) above.
byte [] tempStorage = null;
if (opts != null) tempStorage = opts.inTempStorage;
if (tempStorage == null) tempStorage = new byte[16 * 1024];
bm = nativeDecodeStream(is, tempStorage, outPadding, opts);
}
return finishDecode(bm, outPadding, opts);
}
我们看到,在 decodeStream 里面,是直接调用了 native 接口来解码图片的。那么,decodeResource
和 decodeStream 的区别到底体现在哪里呢?秘密藏在最后一句里: finishDecode(...)。
我们具体看一下 finishDecode 的实现:
private static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) {
if (bm == null || opts == null) {
return bm;
}
final int density = opts.inDensity;
if (density == 0) {
return bm;
}
bm.setDensity(density);
final int targetDensity = opts.inTargetDensity;
if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
return bm;
}
byte[] np = bm.getNinePatchChunk();
final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
if (opts.inScaled || isNinePatch) {
float scale = targetDensity / (float)density;
// TODO: This is very inefficient and should be done in native by Skia
final Bitmap oldBitmap = bm;
bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f),
(int) (bm.getHeight() * scale + 0.5f), true);
oldBitmap.recycle();
if (isNinePatch) {
np = nativeScaleNinePatch(np, scale, outPadding);
bm.setNinePatchChunk(np);
}
bm.setDensity(targetDensity);
}
return bm;
}
其实,我们只需看那句 "TODO" 注释及其附近的代码就会明白,原来 Android
会在java层做效率较低的自适应缩放。而前面我们提到,decodeResourceStream 方法里,会设置 inDensity 和
inTargetDensity 两个参数,这两个参数就是在这里起作用的。
总结如下:
BitmapFactory.decodeResource 加载的图片可能会经过缩放
该缩放目前是放在 java 层做的,效率比较低,而且需要消耗 java
层的内存。因此,如果大量使用该接口加载图片,容易导致OOM错误
BitmapFactory.decodeStream 不会对所加载的图片进行缩放,相比之下占用内存少,效率更高
这两个接口各有用处,如果对性能要求较高,则应该使用 decodeStream;如果对性能要求不高,且需要 Android
自带的图片自适应缩放功能,则可以使用 decodeResource。