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的那些细节。

你可能感兴趣的:(Android View如何加载drawable资源)