Android開發(fā)時難免會遇到圖片加載的問題蝎毡,簡單的做法就是把問題丟給圖片框架處理娱颊,幾個主流的圖片框架各有特色申窘,這里也不展開說耕腾,今天突然想了解一下Android圖片資源的加載,主要是想?yún)⒖家幌屡詈猓瑅iew是如何加載drawable的喻杈,因為我們可以直接在UI線程直接設(shè)置view的背景res,如果這個資源圖很大會不會導(dǎo)致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;
}
顯然筒饰,如果是設(shè)置當前的資源ID,則不會處理壁晒。這里直接通過mContext.getDrawable(resid)
獲取drawable瓷们,然后設(shè)置為background,看來這里并不是異步加載圖片的秒咐,如果是大圖時會不會導(dǎo)致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固蚤。這里也區(qū)分了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;
}
這里區(qū)分了需要加載的文件是xml還是圖片文件,如果是xml則直接丟給XmlResourceParser
惊豺,若是圖片文件燎孟,則是通過mAssets.openNonAsset()
得到一個InputStream
,然后將InputStream
轉(zhuǎn)為Drawable尸昧。這里的AssetManager.openNonAsset()
是native方法揩页,而這里恰恰是可能產(chǎn)生ANR的地方,猜想之所以采用native實現(xiàn)烹俗,就是防止ANR吧爆侣。沒有繼續(xù)看native方法,暫且認為這樣加載圖片不會產(chǎn)生ANR吧幢妄。
繼續(xù)往下兔仰,看看Drawable是如何將InputStream
轉(zhuǎn)為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);
}
最終還是調(diào)用的BitmapFactory.decodeResourceStream
,但是上面一句:
opts.inScreenDensity = Drawable.resolveDensity(res, 0);
這里是設(shè)置屏幕密度蕉鸳,也就是這里會更加屏幕密度來加載圖片乎赴,所以資源圖片放錯位置忍法,或者太大也是會導(dǎo)致OOM的。參考郭神的 Android drawable微技巧榕吼,你所不知道的drawable的那些細節(jié)饿序。