转载请说明出处 https://www.jianshu.com/p/0ffbfcf97902
Android资源加载
使用Resource资源文件有两种方式, 以ImageView为例
- 在Xml文件的src中去设置资源文件 android:src = "@drawable/xxx"
- 通过 setImageResource(R.drawable.xxx) 去使用资源文件
接下来分析一下Resource在资源加载时起到的作用
------------------------------------1. 通过Xml文件的android:src使用资源文件----------------------------------------------
public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initImageView();
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.ImageView, defStyleAttr, defStyleRes);
// 我们在XML中设置的资源的是通过TypedArray拿到的, 往下跟进
final Drawable d = a.getDrawable(R.styleable.ImageView_src);
...
}
/**
* TypedArray.getDrawable
*/
@Nullable
public Drawable getDrawable(@StyleableRes int index) {
final TypedValue value = mValue;
if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
// 这里我们迅速定位到mResource这个对象, 也就说, 我们系统资源是通过Resource加载的
// 这个方法最终会调用到 ResourceImpl.loadDrawableForCookie 中去
return mResources.loadDrawable(value, value.resourceId, mTheme);
}
return null;
}
-------------------------------------------2. setImageResource使用资源文件------------------------------------------------
public void setImageResource(@DrawableRes int resId) {
// 获取之前drawable的宽高
final int oldWidth = mDrawableWidth;
final int oldHeight = mDrawableHeight;
// 因为设置了新的resId, 所以调用该方法, 将当前ImageView的drawable置空
updateDrawable(null);
// 更新当前ImageView的资源id
mResource = resId;
mUri = null;
// 重新加载资源
resolveUri();
// 判断设置了新的资源之后, drawable的宽高是否有变化, 若不相等则 requestLayout
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
requestLayout();
}
// 重新绘制
invalidate();
}
private void resolveUri() {
if (mDrawable != null) return;
if (getResources() == null) return;
// 获取Drawable
Drawable d = null;
if (mResource != 0) {
d = mContext.getDrawable(mResource);
} else if (mUri != null) {
d = getDrawableFromUri(mUri);
} else {
return;
}
// 更新当前 ImageView 的 Drawable
updateDrawable(d);
}
public final Drawable getDrawable(@DrawableRes int id) {
// 这个方法最终也会调用到 ResourceImpl.loadDrawable 中去
// 进而调用 ResourceImpl.loadDrawableForCookie
return getResources().getDrawable(id, getTheme());
}
--------------------------------------------------------------------------------------------------------------------------
/**
* ResourceImpl.loadDrawableForCookie
*/
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 {
// 通过AsstsManager去加载系统资源流文件
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
// 这里就将我们的文件流加载为Bitmap转为Drawable
dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
is.close();
}
}
return dr;
}
从上面的源码分析来看, 无论是通过Xml文件的 android:src 使用资源文件还是通过 setImageResource 使用资源文件, 最终都会交由Resource去加载, 可见我们使用的资源文件, 最终都与 mResources 这个对象相关, 接下来我们看看 Resource 实例化的走向
Resource 实例化走向
我们都知道在App开发中, 通过context.getResource() 可以拿到 Resource 对象, 接下来我们就来分析一下, Resource 是如何实例化的
/**
* ContextImpl.getResources
* 在应用中通过 getResource() 方法最后会来到 ContextImpl 的 getResource() 中
*/
@Override
public Resources getResources() {
// 可见返回的是 ContextImpl 的内部的一个对象
// 接下来我们就要追溯一下, mResource的创建
return mResources;
}
/**
* ContextImpl.createResources
* 1. 该方法在以下方法创建 Context 实例时均会调用, 可见它是实例化 mResources 的入口
* ContextImpl.createApplicationContext()
* ContextImpl.createPackageContextAsUser()
* ContextImpl.createConfigurationContext()
* ContextImpl.createSystemUiContext()
*/
private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName,
int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) {
final String[] splitResDirs;
final ClassLoader classLoader;
try {
splitResDirs = pi.getSplitPaths(splitName);
classLoader = pi.getSplitClassLoader(splitName);
} catch (NameNotFoundException e) {
throw new RuntimeException(e);
}
// 2. 将创建Resources的任务交给了ResourcesManager, 从 ResourcesManager.getInstance() 可见进程单例的
return ResourcesManager.getInstance().getResources(activityToken,
pi.getResDir(),
splitResDirs,
pi.getOverlayDirs(),
pi.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfig,
compatInfo,
classLoader);
}
/**
* ResourcesManager.getResource
*/
public Resources getResources(IBinder activityToken,
@Nullable String[] splitResDirs,
@Nullable String[] overlayDirs,
@Nullable String[] libDirs,
int displayId,
@Nullable Configuration overrideConfig,
@NonNull CompatibilityInfo compatInfo,
@Nullable ClassLoader classLoader) {
try {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
// 3.1 构造Resource的key, 通过key可以从缓存中查找Resource对象
final ResourcesKey key = new ResourcesKey(
resDir,
splitResDirs,
overlayDirs,
libDirs,
displayId,
overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
compatInfo);
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
// 3.2 调用了 getOrCreateResources 方法去获取 Resource 实例
return getOrCreateResources(activityToken, key, classLoader);
}
}
/**
* ResourcesManager.getOrCreateResources
*/
private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
synchronized (this) {
// 4.1 通过 findResourcesImplForKeyLocked() 方法获取 resourcesImpl
// 若 key -> resourcesImpl 能够获取到, 则直接通过 getOrCreateResourcesForActivityLocked / getOrCreateResourcesLocked 获取实例
if (activityToken != null) {
// ResourcesImpl 是 Resource 中方法的具体执行者
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
}
} else {
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
}
}
// 4.2 通过 createResourcesImpl 创建 ResourcesImpl 对象
ResourcesImpl resourcesImpl = createResourcesImpl(key);
if (resourcesImpl == null) {
return null;
}
synchronized (this) {
// 用于二次检验, 防止往 mResourceImpls Map 中 put 多次, 类似于双重锁机制
ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
if (existingResourcesImpl != null) {
resourcesImpl.getAssets().close();
resourcesImpl = existingResourcesImpl;
} else {
// 将我们创建好的 resourcesImpl 实例加入缓存中
mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
}
final Resources resources;
if (activityToken != null) {
resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
} else {
resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
return resources;
}
}
- ContextImpl.createResources 这个方法是App创建资源文件的入口, 在构建 ContextImpl 创建 Context 实例的时候均会调用
- ContextImpl.createResources 通过 ResourcesManager.getInstance().getResources() 来获取 Resource 对象
- ResourcesManager 从名字就能看的出来, 这个类主要负责 Resource 的管理
- ResourcesManager.getInstance() 可见 ResourcesManager 是进程间单例的
- ResourcesManager.getResources 这个方法将构建了 ResourcesKey , 并交由 ResourcesManager.getOrCreateResources 方法去获取 Resource 对象
- ResourcesManager.getOrCreateResources
- 先通过 findResourcesImplForKeyLocked() 方法去获取当前 ResourcesKey 对应的 resourcesImpl 对象的缓存
- 若有缓存: 直接通过 ResourcesManager.getOrCreateResourcesForActivityLocked(activityToken != null) / ResourcesManager.getOrCreateResourcesLocked(activityToken == null) 获取实例
- 若无缓存
- 通过 ResourcesManager.createResourcesImpl() 来创建 resourcesImpl 实例, 添加进 mResourceImpls 的 Map 集合中缓存
- 通过 ResourcesManager.getOrCreateResourcesForActivityLocked(activityToken != null) / ResourcesManager.getOrCreateResourcesLocked(activityToken == null) 获取实例
接下来我们先对 ==getOrCreateResourcesForActivityLocked、getOrCreateResourcesLocked(activityToken == null)== 进行分析
/**
* ResourcesManager.getOrCreateResourcesForActivityLocked
*/
private @NonNull Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
@NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl) {
// 1.1 遍历 activityResources 集合, 在缓存中找到当前传入 classLoader/impl 对应的 resources
final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
activityToken);
final int refCount = activityResources.activityResources.size();
for (int i = 0; i < refCount; i++) {
WeakReference weakResourceRef = activityResources.activityResources.get(i);
Resources resources = weakResourceRef.get();
if (resources != null
&& Objects.equals(resources.getClassLoader(), classLoader)
&& resources.getImpl() == impl) {
return resources;
}
}
// 1.2 若缓存中没有, 则创建新的对象
Resources resources = new Resources(classLoader);
// 1.3 将 resource 与 impl 绑定
resources.setImpl(impl);
// 1.4 添加到缓存集合中
activityResources.activityResources.add(new WeakReference<>(resources));
return resources;
}
/**
* ResourcesManager.getOrCreateResourcesLocked
*/
private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
@NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
// 2.1 遍历 mResourceReferences 集合, 在缓存中找到当前传入 classLoader/impl 对应的 resources
final int refCount = mResourceReferences.size();
for (int i = 0; i < refCount; i++) {
WeakReference weakResourceRef = mResourceReferences.get(i);
Resources resources = weakResourceRef.get();
if (resources != null &&
Objects.equals(resources.getClassLoader(), classLoader) &&
resources.getImpl() == impl) {
return resources;
}
}
// 2.2 若缓存中没有, 则创建新的对象
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);
// 2.3 将 resource 与 impl 绑定
resources.setImpl(impl);
// 2.4 添加到缓存集合中
mResourceReferences.add(new WeakReference<>(resources));
return resources;
}
/**
* 这是一个过时的构造方法, 我们在插件式换肤框架的搭建中, 常用这个过时的方法去实例化 Resources 资源对象
*/
@Deprecated
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
this(null);
mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());
}
可以看到 getOrCreateResourcesForActivityLocked/getOrCreateResourcesLocked 处理的事情十分相似
- 从各自对应的缓存中查找与当前传入 classLoader/impl 相对应的 resources
- 若存在则直接返回
- 若不存在
- Resources resources = new Resources(classLoader); 创建对象
- resources.setImpl(impl); 绑定 ResourceImpl
- 添加到各自对应的缓存集合中
还记得我们上面有一个 ResourcesManager.createResourcesImpl() 没有分析吗, 这个方法非常重要, 所以我们接着往下走
ResourcesImpl 与 AssetManager
/**
* ResourcesManager.createResourcesImpl
* 创建 ResourcesImpl 的实例
*/
private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
// 配置相关构建 ResourcesImpl 对象的参数
final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
daj.setCompatibilityInfo(key.mCompatInfo);
// 可以看到这里有一个非常非常重要的方法 createAssetManager, 通过这个方法, 我们构建了 AssetManager 对象
final AssetManager assets = createAssetManager(key);
if (assets == null) { return null; }
final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
final Configuration config = generateConfig(key, dm);
// 这里直接 new 了一个 ResourcesImpl 对象
final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
return impl;
}
/**
* ResourcesManager.createAssetManager
*/
protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
// 1. new 了一个 AssetManager
AssetManager assets = new AssetManager();
if (key.mResDir != null) {
// 2. 将当前 App 的资源(resource.arsc)加载到 AssetManager 中
if (assets.addAssetPath(key.mResDir) == 0) {
return null;
}
}
if (key.mSplitResDirs != null) {
for (final String splitResDir : key.mSplitResDirs) {
if (assets.addAssetPath(splitResDir) == 0) {
return null;
}
}
}
if (key.mOverlayDirs != null) {
for (final String idmapPath : key.mOverlayDirs) {
assets.addOverlayPath(idmapPath);
}
}
return assets;
}
/**
* AssetManager.Constructor
*/
public AssetManager() {
synchronized (this) {
// 重点!, 这里调用了init方法
init(false);
if (localLOGV) Log.v(TAG, "New asset manager: " + this);
ensureSystemAssets();
}
}
/**
* AssetManager.init
* 1.1 调用了一个 native 层的 init 方法
*/
private native final void init(boolean isSystem);
public final int addAssetPath(String path) {
return addAssetPathInternal(path, false);
}
private final int addAssetPathInternal(String path, boolean appAsLib) {
synchronized (this) {
// 调用了addAssetPathNative
int res = addAssetPathNative(path, appAsLib);
makeStringBlocks(mStringBlocks);
return res;
}
}
/**
* AssetManager.init
* 2.1 调用了 native 层的 addAssetPathNative 方法
*/
private native final int addAssetPathNative(String path, boolean appAsLib);
- ResourcesManager.createResourcesImpl 构造 ResourcesImpl 实例的时候, 通过 ResourcesManager.createAssetManager 去创建了 AssetManager 对象
- ResourcesManager.createAssetManager
- AssetManager assets = new AssetManager(); 创建了 AssetManager 对象
- 在构造方法 AssetManager() 中 init(false); 最终调用了一个 native 层的 init 方法
- assets.addAssetPath(key.mResDir); 最终调用了一个 native 层的 addAssetPathNative 方法
- AssetManager assets = new AssetManager(); 创建了 AssetManager 对象
并不能满足于此, 我们要看看, AssetManager 初始化的时候做了些什么
AssetManager 的初始化过程(AssetManager.cpp 进入 Native 层)
// init 方法
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
{
if (isSystem) {
verifySystemIdmaps();
}
// 1. 构建对象
AssetManager* am = new AssetManager();
if (am == NULL) {
jniThrowException(env, "java/lang/OutOfMemoryError", "");
return;
}
// 2. 添加 Android 系统默认的资源
am->addDefaultAssets();
ALOGV("Created AssetManager %p for Java object %p\n", am, clazz);
env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast(am));
}
// 添加 Android 默认的资源文件
bool AssetManager::addDefaultAssets()
{
// 2.1 获取 Android 系统资源路径
const char* root = getenv("ANDROID_ROOT");
String8 path(root);// 初始化的时候会去加载系统的 framework-res.apk 资源
path.appendPath(kSystemAssets);// 也就是说我们为什么能加载系统的资源如颜色、图片、文字等等
// 2.2 通过系统资源路径去添加系统资源
return addAssetPath(path, NULL);
}
// 通过资源路径去添加资源
bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
{
asset_path ap;
...// 省略一些校验代码
// 2.3 遍历 native 中维护的资源路径数组 mAssetPaths, 判断传入的 path 是否已经存在
for (size_t i=0; i(i+1);
}
// 若存在直接返回加载成功
return true;
}
}
// 2.4 检查传入的 path 是否有 AndroidManifest.xml 文件
Asset* manifestAsset = const_cast(this)->openNonAssetInPathLocked(
kAndroidManifest, Asset::ACCESS_BUFFER, ap);
// 2.4.1 可以看到若是没有 AndroidManifest.xml , 直接返回加载失败
if (manifestAsset == NULL) {
delete manifestAsset;
return false;
}
delete manifestAsset;
// 2.5 若是新路径, 且通过了重重校验, 则添加到这个资源路径数组 mAssetPaths 中
mAssetPaths.add(ap);
// 2.5.1 新路径总是补充到最后
if (cookie) {
*cookie = static_cast(mAssetPaths.size());
}
// 2.6 添加到Resource资源表中
if (mResources != NULL) {
appendPathToResTable(ap);
}
return true;
}
可以看到Native层的AssetManager.init()方法做了两个非常重要的事情
- AssetManager* am = new AssetManager(); 创建了一个 Native 层的 AssetManager
- am->addDefaultAssets(); 调用了 addDefaultAssets() 方法
- 获取 Android 系统资源路径: kSystemAssets(framework-res.apk)
- 调用 addAssetPath(path, NULL, false , true); 通过系统资源路径去添加系统资源
- 遍历 native 中维护的资源路径数组 mAssetPaths, 判断传入的资源路径 path 是否已经存在
- 存在直接返回加载成功
- 检查传入的 path 是否有 AndroidManifest.xml 文件
- 不存在直接返回加载失败, 代表当前资源不符合 AssetManager 加载的要求
- 若是新路径, 且通过了重重校验, 则添加到这个资源路径数组 mAssetPaths 中
- ** appendPathToResTable(ap) 添加到Resource资源表中**
接下来看看 native 层的 appendPathToResTable() 具体是如何将资源添加到表中的
bool AssetManager::appendPathToResTable(const asset_path& ap) const {
// 判断是否为系统资源覆盖
if (ap.isSystemOverlay) {
return true;
}
Asset* ass = NULL;// 用于存储我们App中的资源, 其中只有一个"resources.arsc"
ResTable* sharedRes = NULL; // 用于存储 framework-res.apk 中的资源, 其中有多个"resources.arsc"
bool shared = true;
bool onlyEmptyResources = true;
// 资源覆盖机制,暂不考虑
Asset* idmap = openIdmapLocked(ap);
size_t nextEntryIdx = mResources->getTableCount();// 当前资源表的数量
if (ap.type != kFileTypeDirectory) { // 1. 资源路径是一个apk文件 (资源包路径不是一个文件夹,那就是一个apk文件了)
// 1.1 加载路径为 Android 系统资源包(framework-res.apk)时会走该分支
// 因为在App启动的时候, 肯定会先调用 AssetManager 的 init 方法
if (nextEntryIdx == 0) {
// 通过 mZipSet 获取 ap.path 目录下所有资源, 即: "resources.arsc"文件集合 -> ResTable*
sharedRes = const_cast(this)->mZipSet.getZipResourceTable(ap.path);
// 肯定不为NULL, 因为framework-res.apk一定存在 "resources.arsc" 文件
if (sharedRes != NULL) {
nextEntryIdx = sharedRes->getTableCount();// 得到资源包路径中 "resources.arsc" 文件的个数
}
}
// 1.2 加载路径非 framework-res.apk 时会走该分支
if (sharedRes == NULL) {
// 通过尝试从缓存中直接获取 ap.path 对应的资源
ass = const_cast(this)->mZipSet.getZipResourceTableAsset(ap.path);
if (ass == NULL) {
// 从 ap 这个文件夹中查找 "resources.arsc", 即: "resources.arsc" -> Asset
ass = const_cast(this)->
openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap);
if (ass != NULL && ass != kExcludedAsset) {
// 将资源地址和资源详情, 写入缓存中
ass = const_cast(this)->mZipSet.setZipResourceTableAsset(ap.path, ass);
}
}
// 只有在 zygote 启动时,才会执行下面的逻辑, 为系统资源创建 ResTable,并加入到 mZipSet 里。
if (nextEntryIdx == 0 && ass != NULL) {
// 创建 ResTable 对象,并把前面与 resources.arsc 关联的 Asset 对象,加入到这个 ResTab l中
sharedRes = new ResTable();
sharedRes->add(ass, idmap, nextEntryIdx + 1, false);
sharedRes = const_cast(this)->
mZipSet.setZipResourceTable(ap.path, sharedRes);
}
}
} else { // 2. 资源包路径是一个文件夹
// 从 ap 这个文件夹中查找 "resources.arsc", 从中获取 Asset 资源
ass = const_cast(this)->
openNonAssetInPathLocked("resources.arsc",Asset::ACCESS_BUFFER, ap);
shared = false;
}
// 3. 将解析到的资源添加到 mResources 中维护
// mResources = new ResTable();
if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
if (sharedRes != NULL) {// Android framework-res.apk 资源包时
mResources->add(sharedRes);
} else { // 非Android framework-res.apk 资源包时
mResources->add(ass, idmap, nextEntryIdx + 1, !shared);
}
onlyEmptyResources = false;
if (!shared) {
delete ass;
}
} else {
mResources->addEmpty(nextEntryIdx + 1);
}
if (idmap != NULL) {
delete idmap;
}
return onlyEmptyResources;
}
mResources(ResTable): 为当前应用在 Native 层维护资源的资源表, 与 Framework 层的 mResource(Resource) 同名, 注意区分
- 若传入的路径对应的为一个apk
- 优先加载 framework-res.apk 中的资源, 因为我们 Framework 层的 AssetManager 创建的时候一定会先调用 init() 加载系统资源, 再调用 addAssetPath() 加载当前应用资源
- 加载当前应用资源
- 若传入的为一个文件, 则直接解析其中的 "resources.arsc" 文件
- 将解析到的所有资源("resources.arsc" convert2 Asset)添加到 mResources(ResTable) 中集中缓存
- 至此我们就可以搞懂为什么 R.drawable.xxx 就能唯一的确定一个资源文件了, 因为它具体的路径映射在了 mResources(ResTable) 资源表中
总结
1. App启动的时会创建 ContextImpl 对象
2. 在 ContextImpl 创建 Context 实例的同时会创建 mResource(Resource) 对象
- 在 mResource(Resource) 对象构造的过程中, 会绑定 impl(ResourceImpl) 对象
- ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj); 在构建 impl(ResourceImpl) 对象时会传入 AssetManager 对象
- AssetManager 对象构建时内部会调用 init() 方法, 通过 native 层的 init() 将 Android 系统资源(framework-res.apk)加载进 Native 层维护的资源表(mResources(ResTable))中
- AssetManager.addAssetPath() -> addAssetPathNative() 将当前App的资源(resources.arsc)加载进 Native 层维护的资源表(mResources(ResTable))中
3. 之后通过 Context.getResource() 即可拿到这个全局单例的 mResource(Resource) 对象, 从这时起我们便可以随心所欲的通过 R.drawable.XXX 使用资源文件了
4. Sample: ImageView.setImageResource(R.drawable.logo)
- ContextImpl.java: getDrawable(resId) -> mResources.loadDrawable(value, value.resourceId, mTheme);
- ResourceImpl.java: loadDrawable() -> loadDrawableForCookie()(Resources wrapper, TypedValue value, int id,
Resources.Theme theme) - loadDrawableForCookie()
- 若有缓存, 直接从缓存中获取Drawable
- 若无缓存, 会通过 AssetManager 去 resource.arsc 文件中查找对应的资源路径加载并放入缓存
- loadDrawableForCookie() 方法执行完成我们就拿到了 Drawable 对象了, 进而触发 ImageView