Android View如何加載drawable資源

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章