@act262
2017-06-01T08:52:07.000000Z
字数 7016
阅读 1485
Android_Drawable
API24+开始对Drawable的内部加载流程有所变动,但在Resources的调用表现接口不变。
以下基于API 25的SDK源码分析
Resources#getDrawable -> ResourcesImpl#loadDrawable -> ResourcesImpl#loadDrawableForCookie -> Drawable#createFromXml
-> Drawable#createFromXmlInner -> Resources#getDrawableInflater -> DrawableInflater#inflateFromXml
Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme, boolean useCache) throws NotFoundException {try {if (TRACE_FOR_PRELOAD) {// Log only framework resourcesif ((id >>> 24) == 0x1) {final String name = getResourceName(id);if (name != null) {Log.d("PreloadDrawable", name);}}}final boolean isColorDrawable;final DrawableCache caches;final long key;// 区分普通Drawable还是ColorDrawableif (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.// 从预加载的缓存中获取ConstantStatefinal Drawable.ConstantState cs;if (isColorDrawable) {cs = sPreloadedColorDrawables.get(key);} else {cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);}// 使用缓存已有的ConstantState new出Drawable,复用ConstantStateDrawable dr;if (cs != null) {dr = cs.newDrawable(wrapper);} else if (isColorDrawable) {dr = new ColorDrawable(value.data);} else {// 缓存没有找到则是0开始加载进来dr = loadDrawableForCookie(wrapper, value, id, null);}// Determine if the drawable has unresolved theme attributes. If it// does, we'll need to apply a theme and store it in a theme-specific// cache.final boolean canApplyTheme = dr != null && dr.canApplyTheme();if (canApplyTheme && theme != null) {dr = dr.mutate();dr.applyTheme(theme);dr.clearMutated();}// If we were able to obtain a drawable, store it in the appropriate// cache: preload, not themed, null theme, or theme-specific. Don't// pollute the cache with drawables loaded from a foreign density.if (dr != null && useCache) {// 缓存起来dr.setChangingConfigurations(value.changingConfigurations);cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);}return dr;} catch (Exception e) {String name;try {name = getResourceName(id);} catch (NotFoundException e2) {name = "(missing name)";}// The target drawable might fail to load for any number of// reasons, but we always want to include the resource name.// Since the client already expects this method to throw a// NotFoundException, just throw one of those.final NotFoundException nfe = new NotFoundException("Drawable " + name+ " with resource ID #0x" + Integer.toHexString(id), e);nfe.setStackTrace(new StackTraceElement[0]);throw nfe;}}
private Drawable loadDrawableForCookie(Resources wrapper, TypedValue value, int id,Resources.Theme theme) {if (value.string == null) {throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("+ Integer.toHexString(id) + ") is not a Drawable (color or path): " + value);}final String file = value.string.toString();if (TRACE_FOR_MISS_PRELOAD) {// Log only framework resourcesif ((id >>> 24) == 0x1) {final String name = getResourceName(id);if (name != null) {Log.d(TAG, "Loading framework drawable #" + Integer.toHexString(id)+ ": " + name + " at " + file);}}}if (DEBUG_LOAD) {Log.v(TAG, "Loading drawable for cookie " + value.assetCookie + ": " + file);}final Drawable dr;Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);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) {Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);final NotFoundException rnf = new NotFoundException("File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));rnf.initCause(e);throw rnf;}Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);return dr;}
不从缓存获取的情况下,也就是首次获取走的是全新的解析流程
DrawableInflater.javaDrawable解析
public Drawable inflateFromXml(@NonNull String name, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)throws XmlPullParserException, IOException {// Inner classes must be referenced as Outer$Inner, but XML tag names// can't contain $, so the <drawable> tag allows developers to specify// the class in an attribute. We'll still run it through inflateFromTag// to stay consistent with how LayoutInflater works.// 解析自定义Drawableif (name.equals("drawable")) {name = attrs.getAttributeValue(null, "class");if (name == null) {throw new InflateException("<drawable> tag must specify class attribute");}}// 解析系统定义的drawable类Drawable drawable = inflateFromTag(name);if (drawable == null) {// 没有则解析自定义的Drawabledrawable = inflateFromClass(name);}// inflate具体子类的实现,解析Drawable其他的属性drawable.inflate(mRes, parser, attrs, theme);return drawable;}// 这里解析的对应系统的各类Drawableprivate Drawable inflateFromTag(@NonNull String name) {switch (name) {case "selector":return new StateListDrawable();case "animated-selector":return new AnimatedStateListDrawable();case "level-list":return new LevelListDrawable();case "layer-list":return new LayerDrawable();case "transition":return new TransitionDrawable();case "ripple":return new RippleDrawable();case "color":return new ColorDrawable();case "shape":return new GradientDrawable();case "vector":return new VectorDrawable();case "animated-vector":return new AnimatedVectorDrawable();case "scale":return new ScaleDrawable();case "clip":return new ClipDrawable();case "rotate":return new RotateDrawable();case "animated-rotate":return new AnimatedRotateDrawable();case "animation-list":return new AnimationDrawable();case "inset":return new InsetDrawable();case "bitmap":return new BitmapDrawable();case "nine-patch":return new NinePatchDrawable();default:return null;}}// 这里解析的是自定义的Drawable类,自API24+以后可以在xml文件中使用自定义的Drawable类private Drawable inflateFromClass(@NonNull String className) {try {Constructor<? extends Drawable> constructor;synchronized (CONSTRUCTOR_MAP) {constructor = CONSTRUCTOR_MAP.get(className);if (constructor == null) {final Class<? extends Drawable> clazz =mClassLoader.loadClass(className).asSubclass(Drawable.class);constructor = clazz.getConstructor();CONSTRUCTOR_MAP.put(className, constructor);}}return constructor.newInstance();} catch (NoSuchMethodException e) {final InflateException ie = new InflateException("Error inflating class " + className);ie.initCause(e);throw ie;} catch (ClassCastException e) {// If loaded class is not a Drawable subclass.final InflateException ie = new InflateException("Class is not a Drawable " + className);ie.initCause(e);throw ie;} catch (ClassNotFoundException e) {// If loadClass fails, we should propagate the exception.final InflateException ie = new InflateException("Class not found " + className);ie.initCause(e);throw ie;} catch (Exception e) {final InflateException ie = new InflateException("Error inflating class " + className);ie.initCause(e);throw ie;}}
inflate将解析具体的每个实现类。inflate去解析其他的属性。