Android开发时难免会遇到图片加载的问题,简单的做法就是把问题丢给图片框架处理,几个主流的图片框架各有特色,这里也不展开说,今天突然想了解一下Android图片资源的加载,主要是想参考一下,view是如何加载drawable的,因为我们可以直接在UI线程直接设置view的背景res,如果这个资源图很大会不会导致ANR或者OOM?
首先从View.setBackgroundResource(int resid)
开始:
public void setBackgroundResource(@DrawableRes int resid) {
if (resid != 0 && resid == mBackgroundResource) {
return;
}
Drawable d = null;
if (resid != 0) {
d = mContext.getDrawable(resid);
}
setBackground(d);
mBackgroundResource = resid;
}
显然,如果是设置当前的资源ID,则不会处理。这里直接通过mContext.getDrawable(resid)
获取drawable,然后设置为background,看来这里并不是异步加载图片的,如果是大图时会不会导致ANR呢?
我们接着看:
public final Drawable getDrawable(@DrawableRes int id) {
return getResources().getDrawable(id, getTheme());
}
ResourcesImpl.java
Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme,
boolean useCache) throws NotFoundException {
try {
final boolean isColorDrawable;
final DrawableCache caches;
final long key;
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
&& value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
isColorDrawable = true;
caches = mColorDrawableCache;
key = value.data;
} else {
isColorDrawable = false;
caches = mDrawableCache;
key = (((long) value.assetCookie) << 32) | value.data;
}
// First, check whether we have a cached version of this drawable
// that was inflated against the specified theme. Skip the cache if
// we're currently preloading or we're not using the cache.
if (!mPreloading && useCache) {
final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
if (cachedDrawable != null) {
return cachedDrawable;
}
}
// Next, check preloaded drawables. Preloaded drawables may contain
// unresolved theme attributes.
final Drawable.ConstantState cs;
if (isColorDrawable) {
cs = sPreloadedColorDrawables.get(key);
} else {
cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
}
Drawable dr;
if (cs != null) {
dr = cs.newDrawable(wrapper);
} else if (isColorDrawable) {
dr = new ColorDrawable(value.data);
} else {
dr = loadDrawableForCookie(wrapper, value, id, null);
}
...
return dr;
} catch (Exception e) {
...
}
}
这个方法加载drawable时,先判断是否有缓存,有缓存则直接返回缓存的drawable。这里也区分了isColorDrawable
,并且缓存也是分开的。下面我们就看看loadDrawableForCookie(wrapper, value, id, null);
如何加载Drawable的。
private Drawable loadDrawableForCookie(Resources wrapper, TypedValue value, int id, Resources.Theme theme) {
final String file = value.string.toString();
final Drawable dr;
try {
if (file.endsWith(".xml")) {
final XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "drawable");
dr = Drawable.createFromXml(wrapper, rp, theme);
rp.close();
} else {
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
is.close();
}
} catch (Exception e) {
...
}
return dr;
}
这里区分了需要加载的文件是xml还是图片文件,如果是xml则直接丢给XmlResourceParser
,若是图片文件,则是通过mAssets.openNonAsset()
得到一个InputStream
,然后将InputStream
转为Drawable。这里的AssetManager.openNonAsset()
是native方法,而这里恰恰是可能产生ANR的地方,猜想之所以采用native实现,就是防止ANR吧。没有继续看native方法,暂且认为这样加载图片不会产生ANR吧。
继续往下,看看Drawable是如何将InputStream
转为Drawable的:
public static Drawable createFromResourceStream(Resources res, TypedValue value, InputStream is, String srcName, BitmapFactory.Options opts) {
Rect pad = new Rect();
if (opts == null) opts = new BitmapFactory.Options();
opts.inScreenDensity = Drawable.resolveDensity(res, 0);
Bitmap bm = BitmapFactory.decodeResourceStream(res, value, is, pad, opts);
if (bm != null) {
byte[] np = bm.getNinePatchChunk();
if (np == null || !NinePatch.isNinePatchChunk(np)) {
np = null;
pad = null;
}
final Rect opticalInsets = new Rect();
bm.getOpticalInsets(opticalInsets);
return drawableFromBitmap(res, bm, np, pad, opticalInsets, srcName);
}
return null;
}
private static Drawable drawableFromBitmap(Resources res, Bitmap bm, byte[] np, Rect pad, Rect layoutBounds, String srcName) {
if (np != null) {
return new NinePatchDrawable(res, bm, np, pad, layoutBounds, srcName);
}
return new BitmapDrawable(res, bm);
}
最终还是调用的BitmapFactory.decodeResourceStream
,但是上面一句:
opts.inScreenDensity = Drawable.resolveDensity(res, 0);
这里是设置屏幕密度,也就是这里会更加屏幕密度来加载图片,所以资源图片放错位置,或者太大也是会导致OOM的。参考郭神的 Android drawable微技巧,你所不知道的drawable的那些细节。