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的那些細節。