Android中Drawable获取过程分析

739 阅读7分钟

一、背景说明

在Android开发过程中,我们经常使用Drawable,但是我们的使用方式大多数情况都是以资源文件的的方式进行使用(自定义Drawable的case相对比较少了)。那我们是否熟悉”资源文件“生成Drawable的的过程呢? 今天我们要从源码的角度分析一下,Drawable对象的生成流程。

我们先来看一下Drawable的“继承体系” , 有将近90个子类。

image.png

其中比较典型的:BitmapDrawable、ColorDrawable、StateListDrawable等。 本文将从如下步骤介绍Drawable的生成过程:

  1. Drawable功能介绍
  2. Drawable生成之Context获取
  3. Drawable生成之Resources获取
  4. ResourcesImpl生成Drawable

二、Drawable功能介绍

2.1 Drawable概述

Drawable与View的更新机制

一言以蔽之:能被"画"(绘制)的一种东西

A Drawable is a general abstraction for "something that can be drawn."

Drawable 能够绘制一些内容到画布(Canvas)上。

  1. 什么时候绘制?依赖于”调用者“以及自身的Callback实现。
  2. 在哪里画? 根据mBounds绘制在Canavas上。
  3. 怎么画? 交给了具体的子类,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的方式通常是:

  1. 通过xml描述Drawable。
  2. 将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这件事情,我们并不感到意外:

0001AS结构.jpeg

如果看一下AS的项目结构,就能够发现 项目中生成Drawable的xml就是在res目录下。

接下来我们要想分析Drawable的生成流程,那首先要确定Resources的类型或者实例化场景。 又由于Context是一个抽象类。因此我们需要:

  1. 弄清Context是‘谁’?
  2. 弄清Resources是‘谁’?
  3. 最后弄清Drawable的生成流程。

因为Resources的子类有7个,无法直接确认。 0000Resources.jpeg

我们回到Context是‘谁’的问题上来。由于Context是抽象类,并且getResources() 是抽象方法,因此需要看一下Context子类有哪些?

  • ContextImpl
  • ContextThemeWrapper
  • ContextWrapper
  • DecorContext

这四个从名字上比较敏感的类 实现了getResources方法。

  1. 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对象的获取,分为两个分支:

  1. 一个是通过ContextThemeWrapper(Activity)重写了Configuration的case
  2. 一个普通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获得的对象,是谁在什么时候创建的呢?

0001_setre.jpeg

方法名称作用备注
ContextImpl构造函数, 可以理解成拷贝构造,创建子 ContextImpl(View层级上的子)。 Fragment应该就是这样
ContextActivityContextActivity上下文。 Resources:依赖于 createBaseActivityResources。 值得注意的是 activityToken用来充当Resources缓存的Key。
createAppContextApp进程创建的第一个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对象获取的核心逻辑:

  1. 首先判断要获取的Resources是否是Activity的Resources对象 ,进而在对应的数据结构中找ResourcesImpl对象,如果找到则2,否则3
  2. 是否找到合适的Resources,如果找到,就返回;否则4
  3. 创建ResourcesImpl对象,然后4
  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方法:

  1. 通过color目录下xml去读,获取Drawable
  2. 普通xml获取Drawable
  3. 普通图片文件,进行解码后的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实现逻辑细化,在接下来的文章中,会逐步细化、优化。