一、背景说明
在Android开发过程中,我们经常使用Drawable,但是我们的使用方式大多数情况都是以资源文件的的方式进行使用(自定义Drawable的case相对比较少了)。那我们是否熟悉”资源文件“生成Drawable的的过程呢? 今天我们要从源码的角度分析一下,Drawable对象的生成流程。
我们先来看一下Drawable的“继承体系” , 有将近90个子类。
其中比较典型的:BitmapDrawable、ColorDrawable、StateListDrawable等。 本文将从如下步骤介绍Drawable的生成过程:
- Drawable功能介绍
- Drawable生成之Context获取
- Drawable生成之Resources获取
- ResourcesImpl生成Drawable
二、Drawable功能介绍
2.1 Drawable概述
一言以蔽之:能被"画"(绘制)的一种东西
A Drawable is a general abstraction for "something that can be drawn."
Drawable 能够绘制一些内容到画布(Canvas)上。
- 什么时候绘制?依赖于”调用者“以及自身的Callback实现。
- 在哪里画? 根据mBounds绘制在Canavas上。
- 怎么画? 交给了具体的子类,draw方法是实现。并且提供了一些 静态方法,便于实例化Drawable对象。
接下来,我们看一下Drawable类的具体声明,并对其核心代码逻辑进行说明。
public abstract class Drawable {
// 可见性
private boolean mVisible = true;
// 方向
private int mLayoutDirection;
// 透明区域、图片宽高(getIntrinsicWidth、getIntrinsicHeight)
// 关键方法,进行 Canvas “绘制“
public abstract void draw(@NonNull Canvas canvas);
// 绘制的位置,通过mBounds进行体现
public void setBounds(int left, int top, int right, int bottom) {
Rect oldBounds = mBounds;
if (oldBounds == ZERO_BOUNDS_RECT) {
oldBounds = mBounds = new Rect();
}
if (oldBounds.left != left || oldBounds.top != top ||
oldBounds.right != right || oldBounds.bottom != bottom) {
if (!oldBounds.isEmpty()) {
// 通知”客户端“或者”使用者“ 进行重新绘制
invalidateSelf();
}
// 更新新的边界,并通过”onBoundsChange方法“ 通知到”子类“具体实现者
mBounds.set(left, top, right, bottom);
onBoundsChange(mBounds);
}
}
// Drawable为”使用者“提供的回调接口,通过提供的对象是View
public interface Callback {
// Drawable失效时,调用。View对象会进行自我刷新,从调用Drawable的draw方法实现更新
void invalidateDrawable(@NonNull Drawable who);
// 执行时间,执行runable任务
void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when);
// 取消runable任务
void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what);
}
public Callback getCallback() {
return mCallback != null ? mCallback.get() : null;
}
// 调用上述callback中的方法
public void invalidateSelf() {
final Callback callback = getCallback();
if (callback != null) {
callback.invalidateDrawable(this);
}
}
// 调用上述callback中的方法
public void scheduleSelf(@NonNull Runnable what, long when) {
final Callback callback = getCallback();
if (callback != null) {
callback.scheduleDrawable(this, what, when);
}
}
// 调用上述callback中的方法
public void unscheduleSelf(@NonNull Runnable what) {
final Callback callback = getCallback();
if (callback != null) {
callback.unscheduleDrawable(this, what);
}
}
// "ConstantState" 实现常量对象复用的。能够在避免复用Drawable所依赖对象,实例化一个同类别的新Drawable对象
public static abstract class ConstantState {
public abstract @NonNull Drawable newDrawable();
public @NonNull Drawable newDrawable(@Nullable Resources res) {
return newDrawable();
}
public @NonNull Drawable newDrawable(@Nullable Resources res,
@Nullable @SuppressWarnings("unused") Theme theme) {
return newDrawable(res);
}
public abstract @Config int getChangingConfigurations();
public boolean canApplyTheme() {
return false;
}
}
// 接下来是一些工具类: 生成Drawable的(包括.9)、颜色过滤器、
public static Drawable createFromStream(InputStream is, String srcName) {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, srcName != null ? srcName : "Unknown drawable");
try {
return createFromResourceStream(null, null, is, srcName);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
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);
}
}
通过上述代码分析,我们已经对Drawable的职责有了一个较为基础的认识。
2.2 ConstantState应用
ConstantState之于Drawable,犹如Iterator之于 "线性数据结构"。前者是为了基于ConstantState对象快速实例化Drawable(深度拷贝);后者则是提供了一个迭代器。
public abstract class Drawable {
...
// Drawable提供了一个 获取ConstantState 实例的方法,用作缓存,便于实例化一个新的Drawable对象。
public @Nullable ConstantState getConstantState() {
return null;
}
...
}
那么我们接下来,怎么分析ConstantState的应用场景呢?我们就从getConstantState的调用时机来查看:
2.2.1 读取Drawable的ConstantState 分析
我们以Resources加载Drawable核心方法loadDrawable为例,分析一下Drawable 的ConstantState应用。
public class ResourcesImpl {
// 预加载信息Drawable信息的缓存
@UnsupportedAppUsage
private static final LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables;
@UnsupportedAppUsage
private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables
= new LongSparseArray<>();
// 非预加载信息Drawable信息的缓存
@UnsupportedAppUsage
private final DrawableCache mDrawableCache = new DrawableCache();
@UnsupportedAppUsage
private final DrawableCache mColorDrawableCache = new DrawableCache();
@Nullable
Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
int density, @Nullable Resources.Theme theme)
throws NotFoundException {
// 判断是否需要缓存的依据
final boolean useCache = density == 0 || value.density == mMetrics.densityDpi;
...
... 这里省略了一个try-catch
final boolean isColorDrawable; //要加载的是否是一个ColorDrawable
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;
}
// 非预加载资源,在缓存中获取
if (!mPreloading && useCache) {
final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
if (cachedDrawable != null) {
// 同步配置信息
cachedDrawable.setChangingConfigurations(value.changingConfigurations);
return cachedDrawable;
}
}
// 预加载资源
final Drawable.ConstantState cs;
if (isColorDrawable) {
cs = sPreloadedColorDrawables.get(key);
} else {
cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
}
Drawable dr;
boolean needsNewDrawableAfterCache = false;
if (cs != null) {
// 直接获得一个Drawable实例
dr = cs.newDrawable(wrapper);
} else if (isColorDrawable) {
...
}
if (dr instanceof DrawableContainer) {
needsNewDrawableAfterCache = true;
}
...
if (dr != null) {
dr.setChangingConfigurations(value.changingConfigurations);
if (useCache) {
// 缓存的关键逻辑
cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
if (needsNewDrawableAfterCache) {
Drawable.ConstantState state = dr.getConstantState();
if (state != null) {
dr = state.newDrawable(wrapper);
}
}
}
}
return dr;
}
}
2.2.2 存储Drawable的ConstantState 分析
存储逻辑依据于是否是”预加载“ 来决定存储的位置。 非预加载,存储在DrawableCache对象中,依据Theme进行分类,依据key进行查找 也有关系。
// file:ResourceImpl.java
// 是选择存储 预加载的架构中,还是存储在caches中
private void cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches,
Resources.Theme theme, boolean usesTheme, long key, Drawable dr) {
final Drawable.ConstantState cs = dr.getConstantState();
if (cs == null) {
return;
}
if (mPreloading) {
final int changingConfigs = cs.getChangingConfigurations();
if (isColorDrawable) {
if (verifyPreloadConfig(changingConfigs, 0, value.resourceId, "drawable")) {
sPreloadedColorDrawables.put(key, cs);
}
} else {
if (verifyPreloadConfig(
changingConfigs, ActivityInfo.CONFIG_LAYOUT_DIRECTION, value.resourceId, "drawable")) {
// 都存储
if ((changingConfigs & ActivityInfo.CONFIG_LAYOUT_DIRECTION) == 0) {
sPreloadedDrawables[0].put(key, cs);
sPreloadedDrawables[1].put(key, cs);
} else {
// 单方向存储
sPreloadedDrawables[mConfiguration.getLayoutDirection()].put(key, cs);
}
}
}
} else {
synchronized (mAccessLock) {
// DrawableCache在进行ConstantState存储时,先根据Theme分类,在根据key存储相应的LongSparseArray。
caches.put(key, theme, cs, usesTheme);
}
}
}
三、Drawable生成之Context获取
3.1 Context功能如其名
接下来,我们回到”主线“上来。继续分析Drawable的生成流程分析。
我们使用Drawable的方式通常是:
- 通过xml描述Drawable。
- 将xml对应的ResId 设置到Background或者ImageResource等API中。
// file: ImageView.java
public void setImageResource(@DrawableRes int resId) {
...
resolveUri();
...
invalidate();
}
// 该方法属于复合作用,既可以通过id获取Drawable,也可以通过Uri
private void resolveUri() {
Drawable d = null;
if (mResource != 0) {
// 通过Context获取Drawable。
d = mContext.getDrawable(mResource);
} else if (mUri != null) {
// 通过Uri获取Drawable,暂不进行分析,接下来的文章会进行分析
d = getDrawableFromUri(mUri);
} else {
return;
}
updateDrawable(d);
}
从上面可以看出:ImageView的setImageResource方法是通过Context获取Drawable对象的。 那么View的setBackground方法呢?
public void setBackgroundResource(@DrawableRes int resid) {
...
Drawable d = null;
if (resid != 0) {
d = mContext.getDrawable(resid); //背景 也是通过resid 去解析Drawable对象
}
...
}
那么View的setBackground方法也是通过Context获取Drawable对象的。
小结:Drawable对象是通过Context进行获取(或者生成)的。
3.2 Context,到底是‘谁在干活’
public abstract class Context {
....
public abstract AssetManager getAssets();
public abstract Resources getResources();
public abstract PackageManager getPackageManager();
public abstract ContentResolver getContentResolver();
public abstract Looper getMainLooper();
...
public final Drawable getDrawable(@DrawableRes int id) {
return getResources().getDrawable(id, getTheme());
}
}
从上面可以看出Context给我们提供了哪些环境(上下文环境):AssetManager、Resources、PackageManager、ContentResolverm , Theme 这也是Context几乎所有的功能了,"天下九州得起8.5"。
接下来我们要进一步了解Drawable的生成过程:
getResources().getDrawable(id, getTheme()); // theme,我们暂且忽略,后续会专门去探索一下Theme
对于Context通过Resources对象获取Drawable这件事情,我们并不感到意外:
如果看一下AS的项目结构,就能够发现 项目中生成Drawable的xml就是在res目录下。
接下来我们要想分析Drawable的生成流程,那首先要确定Resources的类型或者实例化场景。 又由于Context是一个抽象类。因此我们需要:
- 弄清Context是‘谁’?
- 弄清Resources是‘谁’?
- 最后弄清Drawable的生成流程。
因为Resources的子类有7个,无法直接确认。
我们回到Context是‘谁’的问题上来。由于Context是抽象类,并且getResources() 是抽象方法,因此需要看一下Context子类有哪些?
- ContextImpl
- ContextThemeWrapper
- ContextWrapper
- DecorContext
这四个从名字上比较敏感的类 实现了getResources方法。
- DecorContext 是 ContextThemeWrapper 子类,用于测试,故pass;
3.2.1 ContextThemeWrapper
// file:ContextThemeWrapper.java
@Override
public Resources getResources() {
return getResourcesInternal(); //内部调用
}
private Resources getResourcesInternal() {
// 从这个判断可以看出,每个ContextThemeWrapper实例(比如Activity) 默认情况下只有一个固定的mResources实例。
// 我们来看一下,Resources对象是怎么拿到的?
if (mResources == null) {
// 如果配置没有重写,则用其父类(实现)实现的方式获取Resources, 由于ContextThemeWrapper的父类是ContextWrapper。
if (mOverrideConfiguration == null) {
mResources = super.getResources();
} else {
// 否则自己根据mOverrideConfiguration情况,创建一个Context,并且拿到其Resources对象。
// 那么?为啥不直接返回Resources对象呢? 没想到,这个方法createConfigurationContext 也是Context定义 //ContextWrapper实现.
final Context resContext = createConfigurationContext(mOverrideConfiguration);
mResources = resContext.getResources();
}
}
return mResources;
}
ContextThemeWrapper 根据 mOverrideConfiguration 来决策,是使用ContextWrapper的getResources,还是自己直接创建Context。 对于这两个”分支“ 【3.2.2 ContextWrapper】会进行分析。
3.2.2 ContextWrapper
// file:ContextWrapper.java
@Override
public Resources getResources() {
// ContextWrapper 对象将获取Resources的逻辑 委托给了 mBase
return mBase.getResources();
}
@Override
public Context createConfigurationContext(Configuration overrideConfiguration) {
// ContextWrapper 对象将获取createConfigurationContext的逻辑 委托给了 mBase
return mBase.createConfigurationContext(overrideConfiguration);
}
ContextWrapper 对于getResources()和createConfigurationContext()方法的实现,都委托给了mBase成员变量了。 那mBase是谁呢?它就是ContextImpl.!!! 我们继续大胆的分析吧。
3.2.3 ContextImpl
从 前面几个小结, 我们能够了解到,Resources对象的获取,分为两个分支:
- 一个是通过ContextThemeWrapper(Activity)重写了Configuration的case
- 一个普通getResource的case。
3.2.3.1 createConfigurationContext
再次强调一遍 createConfigurationContext方法,是在Activity重写了Configuration时,会执行的的逻辑
// file:ContexImpl.java
public Context createConfigurationContext(Configuration overrideConfiguration) {
// 防御式写法, 很好,判断错误,就应该需要买单
if (overrideConfiguration == null) {
throw new IllegalArgumentException("overrideConfiguration must not be null");
}
// 创建了一个新的ContextImpl对象,为接下来Resources创建之后,正常发挥功能,提供基础。具体看下面的setResources方法。
ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName,
mActivityToken, mUser, mFlags, mClassLoader, null);
final int displayId = getDisplayId();
// setResources方法,有可能为创建的Resources提供Context。
context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId,
overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo()));
return context;
}
// file:ContexImpl.java
private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName,
int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) {
final String[] splitResDirs;
final ClassLoader classLoader;
try {
// splitResDirs 分目录的目的是什么?
splitResDirs = pi.getSplitPaths(splitName);
classLoader = pi.getSplitClassLoader(splitNam e);
} catch (NameNotFoundException e) {
throw new RuntimeException(e);
}
// 最终Resources的创建交给了ResourcesManager来处理。 接下来Resources稍后跟踪,盲猜ContextImpl 的getResources()方法最终也会执行到此处。
return ResourcesManager.getInstance().getResources(activityToken,
pi.getResDir(),
splitResDirs,
pi.getOverlayDirs(),
pi.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfig,
compatInfo,
classLoader);
}
//file:ContextImpl.java
void setResources(Resources r) {
// API < 26 ,判断true
if (r instanceof CompatResources) {
((CompatResources) r).setContext(this);
}
mResources = r;
}
先创建了ContextImpl,又为其设置了Resources对象。而Resources对象是通过ResourcesManager得到的。
3.2.3.2 getResources
// file:ContexImpl.java
public Resources getResources() {
return mResources; //居然是一个成员变量,那我们就要看 谁给mResources属性赋值的。
}
//file:ContextImpl.java
void setResources(Resources r) {
// API < 26 ,判断true
if (r instanceof CompatResources) {
((CompatResources) r).setContext(this);
}
// mResources 赋值,仅此一处。因此要看 , 该setResources方法的调用在哪里
mResources = r;
}
getResources获得的对象,是谁在什么时候创建的呢?
| 方法名称 | 作用 | 备注 |
|---|---|---|
| ContextImpl | 构造函数, 可以理解成拷贝构造,创建子 ContextImpl(View层级上的子)。 Fragment应该就是这样 | |
| ContextActivityContext | Activity上下文。 Resources:依赖于 createBaseActivityResources。 值得注意的是 activityToken用来充当Resources缓存的Key。 | |
| createAppContext | App进程创建的第一个Context | |
| createApplictionContext | 创建ApplicationContext; Apk从ApplicationInfo到LoadedApk过度,但是已经存在LoadedApk了呀? 这是怎么回事? RemoteView/Notifaction都是这种玩法 |
上述各种Context的创建姿势,最终都是调用到ContextImpl构造。并且通过ContextImpl 的静态方法,来创建Resources对象。
private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName,
int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo);
3.2.4 小结
Context的种类很多,继承体系也也很大,但是对于Resources对象的获取,都归于ResourcesMananger去创建Resources对象。
四、Drawable生成之Resources获取
在上一节中,我们已经知道了Resources对象需要通过ResourcesMananger去获取。因此接下来,我们要针对此内容进行源码分析:
public @Nullable Resources getResources(@Nullable IBinder activityToken, //非空代表Activity资源,否则是全局资源
@Nullable String resDir, //基本的资源目录
@Nullable String[] splitResDirs, // 资源目录
@Nullable String[] overlayDirs,
@Nullable String[] libDirs,
int displayId,
@Nullable Configuration overrideConfig,
@NonNull CompatibilityInfo compatInfo,
@Nullable ClassLoader classLoader) {
try {
...
// 弱弱地问一句,Glide 是不是比这行代码早一些?怎么这么像呢,哈哈
final ResourcesKey key = new ResourcesKey(
resDir,
splitResDirs,
overlayDirs,
libDirs,
displayId,
overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
compatInfo);
// 系统ClassLoader 进行兜底。 和LoadedApk 拆分资源有关
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
return getOrCreateResources(activityToken, key, classLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
// 获取或者 创建资源对象
private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
// 加锁了
synchronized (this) {
...
if (activityToken != null) {
// 获得一个activityResources对象。 存储 overrideconfig和Resources
final ActivityResources activityResources =
getOrCreateActivityResourcesStructLocked(activityToken);
// 利用快排,删除被回收的元素
ArrayUtils.unstableRemoveIf(activityResources.activityResources,
sEmptyReferencePredicate);
// 将新的配置信息更新到overrideconfig中
if (key.hasOverrideConfiguration()
&& !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
final Configuration temp = new Configuration(activityResources.overrideConfig);
temp.updateFrom(key.mOverrideConfiguration);
key.mOverrideConfiguration.setTo(temp);
}
// 在缓存中获取一个ResourcesImpl对象
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
...
// 1. 再次去缓存中遍历,找到Resources对象等于 resourcesImpl的resource
// 2. 否则去创建这么一套组合 :
// a.创建resource对象,设置ResourcesImpl
return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
}
// We will create the ResourcesImpl object outside of holding this lock.
} else {
...
}
// 创建ResourcesImpl:
// 1. 创建createAssetManager
// 2. 根据DisplayId获取 显示信息:DisplayMetrics
// 3. 生成Configuration , (横竖屏、DPI等信息,来源于DisplayMetrics)
// 4. 最后实例化ResourcesImpl
ResourcesImpl resourcesImpl = createResourcesImpl(key);
if (resourcesImpl == null) {
return null;
}
// 1. 将ResourcesImpl存储缓存
mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
final Resources resources;
if (activityToken != null) {
// 和上面的if分支是一样的了
resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
} else {
// 和上面的if分支相似
resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
return resources;
}
}
上述代码,是Resources对象获取的核心逻辑:
- 首先判断要获取的Resources是否是Activity的Resources对象 ,进而在对应的数据结构中找ResourcesImpl对象,如果找到则2,否则3
- 是否找到合适的Resources,如果找到,就返回;否则4
- 创建ResourcesImpl对象,然后4
- 根据创建一个新的Resources加入到缓存,并返回。
在缓存中获得Resources的逻辑,以”非ContextThemeWrapper“为例:
private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
@NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
final int refCount = mResourceReferences.size();
for (int i = 0; i < refCount; i++) {
WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
Resources resources = weakResourceRef.get();
// 找到合适缓存的条件是: CL相同;Impl相同
if (resources != null &&
Objects.equals(resources.getClassLoader(), classLoader) &&
resources.getImpl() == impl) {
return resources;
}
}
// 否则就创建一个新的Resources对象,并且加入到缓存中
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);
resources.setImpl(impl);
mResourceReferences.add(new WeakReference<>(resources));
return resources;
}
五、Resources生成Drawable
现在Resources对象已经存在了,接下来看一下,如何通过Resources获得Drawable对象的。
//file:Resources.java
public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)
throws NotFoundException {
return getDrawableForDensity(id, 0, theme);
}
@Nullable
public Drawable getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme) {
// 这是一个出参(只有资源的结构)
final TypedValue value = obtainTempTypedValue();
try {
final ResourcesImpl impl = mResourcesImpl;
// 获取资源信息到value中
impl.getValueForDensity(id, density, value, true);
// 根据TypedValue、theme获取Drawable对象
return impl.loadDrawable(this, value, id, density, theme);
} finally {
releaseTempTypedValue(value);
}
}
impl.getValueForDensity(id, density, value, true); 方法 根据ID 和 density 能够获取资源信息,并且存储在TypedValue变量中。这个操作是通过AssetManager实现的。 本文暂不分析,会在后续的文章中进行分析。
接下来继续看loadDrawable的加载过程:
//file:ResourcesImpl.java
Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
int density, @Nullable Resources.Theme theme)
throws NotFoundException {
if (cs != null) {
// Constant Drawable 执行这里
dr = cs.newDrawable(wrapper);
} else if (isColorDrawable) {
//... 直接New了
dr = new ColorDrawable(value.data);
} else {
//...
dr = loadDrawableForCookie(wrapper, value, id, density);
}
}
Drawable来源 要么是ConstantState缓存;要么是ColorDrawable实例化;要么是loadDrawableForCookie。
我们继续分析一下loadDrawableForCookie方法:
- 通过color目录下xml去读,获取Drawable
- 普通xml获取Drawable
- 普通图片文件,进行解码后的BitmapDrawable。
@Nullable
private Drawable loadDrawableForCookie(@NonNull Resources wrapper, @NonNull TypedValue value,
int id, int density) {
//...
LookupStack stack = mLookupStack.get();
try {
//...
try {
if (file.endsWith(".xml")) {
// 对待颜色的处理
if (file.startsWith("res/color/")) {
dr = loadColorOrXmlDrawable(wrapper, value, id, density, file);
} else {
// 普通Drawable的处理
dr = loadXmlDrawable(wrapper, value, id, density, file);
}
} else {
// 加载 & 解码
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
AssetInputStream ais = (AssetInputStream) is;
dr = decodeImageDrawable(ais, wrapper, value);
}
} finally {
//...
}
} catch (Exception | StackOverflowError e) {
//...
}
return dr;
}
private ComplexColor loadComplexColorForCookie(Resources wrapper, TypedValue value, int id,
Resources.Theme theme) {
//...
try {
final XmlResourceParser parser = loadXmlResourceParser(
file, id, value.assetCookie, "ComplexColor");
final AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
// Seek parser to start tag.
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
final String name = parser.getName();
if (name.equals("gradient")) {
// 渐变颜色
complexColor = GradientColor.createFromXmlInner(wrapper, parser, attrs, theme);
} else if (name.equals("selector")) {
// 状态处理
complexColor = ColorStateList.createFromXmlInner(wrapper, parser, attrs, theme);
}
parser.close();
} catch (Exception e) {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
final NotFoundException rnf = new NotFoundException(
"File " + file + " from ComplexColor resource ID #0x"
+ Integer.toHexString(id));
rnf.initCause(e);
throw rnf;
}
return complexColor;
}
// 再来看一下解码普通的Drawable文件
private Drawable decodeImageDrawable(@NonNull AssetInputStream ais,
@NonNull Resources wrapper, @NonNull TypedValue value) {
// 底层Sk解码器
ImageDecoder.Source src = new ImageDecoder.AssetInputStreamSource(ais,
wrapper, value);
try {
// 解码图片
return ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
});
} catch (IOException ioe) {
// This is okay. This may be something that ImageDecoder does not
// support, like SVG.
return null;
}
}
六、总结
本文 粗浅的分析了Drawable获取过程。 既没有没涉及到Native层AssetManager的读取,又没有将具体的Drawable实现逻辑细化,在接下来的文章中,会逐步细化、优化。