Android资源管理中的Runtime Resources Overlay-------之overlay包的生效(五)
前文我们介绍了overlay package和idmap文件是怎么加载到AsseetManager中的。本文主要分析当AssetManager加载完target package和overlay package后,我们在java文件中或者xml文件里访问这些资源时,AssetManager是如何处理的。另外,本篇对Android资源管理中的Runtime Resources Overlay-------之overlay包的加载(四)的依赖非常强,毕竟资源是在加载后组织的,本篇则是去获取,所以要对资源特别是overlay package到底是如何组织在内存中的要有个总体认识。
我们就以在java文件中获取一个drawable资源为例来说明(xml中资源的获取最终和java文件中的获取大同小异,只不过是多了xml文件的解析)。我们用的比较多的就是类似mContext.getResources().getDrawable(int resId)
这样的代码,我们就从Context开始分析。前面我们讲过,Context有三个方面的意义,其中一个就是它提供了我们的应用已经加载的资源,因为它的内部有一个Resources类的实例mResources,我们看它的实现:
//frameworks/base/core/java/android/content/res/Resources.javapublic Drawable getDrawable(int id, @Nullable Theme theme) throws NotFoundException { TypedValue value; synchronized (mAccessLock) { value = mTmpValue; if (value == null) { value = new TypedValue(); } else { mTmpValue = null; } getValue(id, value, true); } final Drawable res = loadDrawable(value, id, theme); synchronized (mAccessLock) { if (mTmpValue == null) { mTmpValue = value; } } return res;}
这里的mTmpValue主要就是用来做缓存的,剩下的代码主要可以分为两部分:先去AssetManager里获取这项资源相关的信息(getValue方法),再去根据这些信息就创建具体的资源对象(loadDrawable方法)。这里的资源相关的信息是:如果这个Drawable是ColorDrawable,那么就是指具体的颜色值;如果是图片或者xml则是指图片或者xml文件的路径。我们先看getValue方法:
//frameworks/base/core/java/android/content/res/Resources.java public void getValue(int id, TypedValue outValue, boolean resolveRefs) throws NotFoundException { boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs); if (found) { return; } throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)); }
这个方法去调用了AssetManager的getResourceValue()方法。需要说明的是getValue方法的这三个参数:id表示我们要找的资源的id,就是R文件里的R.drawable.icon;outValue是个输出参数,用来保存我们的查寻结果;resolveRefs表示如果我们查寻到的结果是个引用,那么我们会去解析这个引用,直到拿到具体的值为止(注意,这里有可能是多层次的引用哦)。
//frameworks/base/core/java/android/content/res/AssetManager.java /** *ident 我们资源的id *density 0 *outValue 输出参数 *resolveRefs true 如果是引用会解析之 */ final boolean getResourceValue(int ident, int density, TypedValue outValue, boolean resolveRefs) { /** *block 表示我们要查询的这个资源存在于哪个包中 *注意,这里resolveRefs = true,我们得到的结果只有两种类型: *一、具体的值(这里就是颜色值了,表示一个colorDrawable) *二、字符串在对应 global string pool中的索引,表示资源(xml或者png)的路径 */ int block = loadResourceValue(ident, (short) density, outValue, resolveRefs); if (block >= 0) { if (outValue.type != TypedValue.TYPE_STRING) { return true; } //mStringBlocks存储所有资源包的global(or value) string pool outValue.string = mStringBlocks[block].get(outValue.data); return true; } return false; }
前面我们知道当我们的应用起来后,AssetManager至少已经加载了系统资源包framework-res.apk、我们的应用包app.apk、以及我们的overlay 包 overlay.apk。那么我们的这个变量此时应该是2,也就是我们的overlay包在AssetManager(确切地说是ResTable)中的索引。另外,我们在前文中讲过,mStringBlocks就是已经加载的资源包的global string pool(也叫 value string pool)。如果我们的drawable是个图片的话,loadResourceValue方法返回的outValue.data就是这个图片的名字在mStringBlocks[block]中的索引。loadResourceValue是个native方法,我们看其实现:
//frameworks/base/core/jni/android_util_AssetManager.cppstatic jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz, jint ident, jshort density, jobject outValue, jboolean resolve){ if (outValue == NULL) { jniThrowNullPointerException(env, "outValue"); return 0; } //拿到AssetManager AssetManager* am = assetManagerForJavaObject(env, clazz); if (am == NULL) { return 0; } //获取ResTable const ResTable& res(am->getResources()); Res_value value; ResTable_config config; uint32_t typeSpecFlags; //除了第一个和第三个参数外,全都是输出参数 ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config);#if THROW_ON_BAD_ID if (block == BAD_INDEX) { jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); return 0; }#endif uint32_t ref = ident; if (resolve) {//true //如有必要,会解析引用的值,block有可能会不同,跨包引用 block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config);#if THROW_ON_BAD_ID if (block == BAD_INDEX) { jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); return 0; }#endif } if (block >= 0) { //把结果给outValue,以便java层访问 return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config); } //资源在哪个包中。 系统?app?overlay return static_cast<jint>(block);}
这个方法通过AssetManager最终走到了ResTable里:
ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag, uint16_t density, uint32_t* outSpecFlags, ResTable_config* outConfig) const{ //常规操作,拿到PackageGroup、Type、Entry的索引。 const ssize_t p = getResourcePackageIndex(resID); const int t = Res_GETTYPE(resID); const int e = Res_GETENTRY(resID); /** *拿到PackageGroup,需要注意的是,此时这个packageGroup里有我们的 *app资源包,也有overlay包 */ const PackageGroup* const grp = mPackageGroups[p]; //拿到配置信息 ResTable_config desiredConfig = mParams; if (density > 0) { desiredConfig.density = density; } //拿到Entry,这是最关键的一步。 Entry entry; status_t err = getEntry(grp, t, e, &desiredConfig, &entry); /** *在resources.arsc中,每一个entry(如果有值,且不考虑bag资源)后面 *都会跟一个Res_value表示entry的结果 */ const Res_value* value = reinterpret_cast<const Res_value*>( reinterpret_cast<const uint8_t*>(entry.entry) + entry.entry->size); //把结果传给输出参数 outValue->size = dtohs(value->size); outValue->res0 = value->res0; outValue->dataType = value->dataType; outValue->data = dtohl(value->data); //输出outSpecFlags if (outSpecFlags != NULL) { *outSpecFlags = entry.specFlags; } //输出config if (outConfig != NULL) { *outConfig = entry.config; } //表示这个资源在哪个资源包中。系统?app?overlay return entry.package->header->index;}
需要注意的是,此时我们拿到的PackageGroup里不仅有我们的app自身,还有我们的overlay包。这个我们在Android资源管理中的Runtime Resources Overlay-------之overlay包的加载(四)中做过详细介绍,这里不再多讲。
status_t ResTable::getEntry(const PackageGroup* packageGroup, int typeIndex, int entryIndex, const ResTable_config* config, Entry* outEntry) const{ /** *这一句非常关键,overlay package会被放到和target package相同的那个 *PackageGroup里,TypeList也一样。overlay package中,类型为drawable的 *资源也会和target package中的drawable放到同一个TypeList中,并且 *TypeList[0]是target package的drawable *TypeList[1]以及后面的是overlay package的drawable */ const TypeList& typeList = packageGroup->types[typeIndex]; const ResTable_type* bestType = NULL; uint32_t bestOffset = ResTable_type::NO_ENTRY; const Package* bestPackage = NULL; uint32_t specFlags = 0; uint8_t actualTypeIndex = typeIndex; ResTable_config bestConfig; memset(&bestConfig, 0, sizeof(bestConfig)); //遍历target包中和overlay包中该类型的资源 const size_t typeCount = typeList.size(); for (size_t i = 0; i < typeCount; i++) { //typeList[0]是target package中的资源 const Type* const typeSpec = typeList[i]; int realEntryIndex = entryIndex; int realTypeIndex = typeIndex; bool currentTypeIsOverlay = false; if (typeSpec->idmapEntries.hasEntries()) { uint16_t overlayEntryIndex; /** * idmap中去查找,我们在上一篇文章中介绍过其实现 * 根据target entryId拿到overlay entryId */ if (typeSpec->idmapEntries.lookup(entryIndex, &overlayEntryIndex) != NO_ERROR) { // No such mapping exists //这里一般不会error! continue; } /** *此处高能 *realEntryIndex、realTypeIndex全部改成了overlay package的!!! */ realEntryIndex = overlayEntryIndex; realTypeIndex = typeSpec->idmapEntries.overlayTypeId() - 1; currentTypeIsOverlay = true; } //......一系列的检查,略过 //开始处理每一种配置下该类的资源 const size_t numConfigs = typeSpec->configs.size(); for (size_t c = 0; c < numConfigs; c++) { const ResTable_type* const thisType = typeSpec->configs[c]; if (thisType == NULL) { continue; } ResTable_config thisConfig; thisConfig.copyFromDtoH(thisType->config); /** *如果当前配置匹不上设备的配置,则跳过 *比如当前这种config是德文,但设备配置的语言是中文,明显不匹配 */ if (config != NULL && !thisConfig.match(*config)) { continue; } //指向thisType的结尾 const uint8_t* const end = reinterpret_cast<const uint8_t*>(thisType) + dtohl(thisType->header.size); /** *在一个ResTable_type后面会有ResTable_type->entryCount个uint_32 *它们表示每一个entry相对于ResTable_type->entriesStart的偏移量 *而ResTable_type->entriesStart则表示头一个entry相对于ResTable_type的偏移量 */ const uint32_t* const eindex = reinterpret_cast<const uint32_t*>( reinterpret_cast<const uint8_t*>(thisType) + dtohs(thisType->header.headerSize)); //该entry相对于thisType->entriesStart的偏移量 uint32_t thisOffset = dtohl(eindex[realEntryIndex]); if (thisOffset == ResTable_type::NO_ENTRY) {//0xFFFFFFFF,不需映射时我们的填充值 // There is no entry for this index and configuration. continue; } //之前已经找到了合适的配置,那么我们就要看当前的entry是否比那个更合适 if (bestType != NULL) { //当前的没有之前的那个更合适 if (!thisConfig.isBetterThan(bestConfig, config)) { /** *但是当前的也还匹配得上,并且当前这个是overlay package, *那还是得用当前的。 */ if (!currentTypeIsOverlay || thisConfig.compare(bestConfig) != 0) { continue; } } } //记录匹配得最好的entry bestType = thisType; bestOffset = thisOffset; bestConfig = thisConfig; bestPackage = typeSpec->package; actualTypeIndex = realTypeIndex; // If no config was specified, any type will do, so skip if (config == NULL) { break; } } } //匹配得最好的entry相对与bestType的偏移量 bestOffset += dtohl(bestType->entriesStart); //拿到这个entry const ResTable_entry* const entry = reinterpret_cast<const ResTable_entry*>( reinterpret_cast<const uint8_t*>(bestType) + bestOffset); //写入返回值 if (outEntry != NULL) { outEntry->entry = entry; outEntry->config = bestConfig; outEntry->type = bestType; outEntry->specFlags = specFlags; outEntry->package = bestPackage; //资源项的type在type string pool的索引 outEntry->typeStr = StringPoolRef(&bestPackage->typeStrings, actualTypeIndex - bestPackage->typeIdOffset); //资源项的Name在key string pool的索引 outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, dtohl(entry->key.index)); } return NO_ERROR;}
从这个方法的实现我们可以看到overlay package中的资源的优先级还是比target package要高的。只要overlay package的资源能够匹配得上设备的当前配置,那么即使target package可以匹配得更好,还是会用overlay package的。但是如果overlay package的资源根本匹配不上当前设备的配置,那就起不到覆盖target package的作用了。
到这里,如果这个资源的类型是values的(color、string、integer等)、我们的查找过程就基本结束了。但是如果是xml或者图片,这里得到的只是它们的路径,此时还去加载具体的资源,这跟overlay关系已经不大,我们就不去描述了。
到这里Runtime Resources Overlay的具体使用和原理我们已经基本介绍完了。说是基本,因为还有一种静态的资源overlay,它只是在编译的时候对资源做替换,这个完全是aapt的东西了,我们在这里先不多说。
总结与说明:
-
AssetManager中相关的数据结构比较多,特别是ResTable_XXXX和ResTable::XXXX,我们要注意它们的区别,前者注重对resources.arsc的描述,后者是为了方便我们管理资源。
-
PackageGroup和TypeList的概念比较难懂,就像DynamicRefTable是为资源共享库而生一样; PackageGroup和TypeList以及IdmapEntries就是为了Runtime Resources Overlay而存在的。
-
阅读代码的过程中,要注意ResTable、PackageGroup、Pakage、TypeList、Type、ResTable_type、Entry这七级资源管理架构。
-
RRO的本质是对资源entry的映射,并将这种映射关系持久化(生成idmap文件)。如果没有持久化的过程,那就和资源的动态加载没区别了。
-
RRO也好,资源的动态加载框架也罢,甚至包括前面我们介绍的资源共享库,这些概念的提出,都需要我们对Android现有的资源管理框架有足够的理解,也需要有比较活跃的思维,把传统的概念迁移过来。
-
RRO和资源共享库是Android资源管理框架中不常用,但非常重要的概念。因为它们相关的代码在aapt、AssetManager、PMS中都有分布。如果想把Android的资源管理弄透彻,这些概念是绕不开的。
更多相关文章
- WebView简介(加速加载篇)
- Android(安卓)四大核心组件
- 糊里糊涂学Android(1)——Activity之间的数据传递
- 一个Activity的显示过程总结(一)
- android Activity启动流程
- [Android(安卓)Training视频系列]1.4 Starting Another Activity
- 【总结】Mac版Android(安卓)Studio常用快捷键总结
- Android中manager
- listview 中Item中含有Button 等造成Item点击无效的解决方法