前文我们介绍了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的资源管理弄透彻,这些概念是绕不开的。

更多相关文章

  1. WebView简介(加速加载篇)
  2. Android(安卓)四大核心组件
  3. 糊里糊涂学Android(1)——Activity之间的数据传递
  4. 一个Activity的显示过程总结(一)
  5. android Activity启动流程
  6. [Android(安卓)Training视频系列]1.4 Starting Another Activity
  7. 【总结】Mac版Android(安卓)Studio常用快捷键总结
  8. Android中manager
  9. listview 中Item中含有Button 等造成Item点击无效的解决方法

随机推荐

  1. Android通过http协议POST传输方式
  2. TableLayout 中 stretchColumns的用法
  3. ANDROID STUDIO, GRADLE AND NDK INTEGRA
  4. android 拨打电话
  5. android中生成和使用jar 分享
  6. Android(安卓)- 按钮组件详解
  7. Android(安卓)Device Monitor工具的使用
  8. 【Android】 Android中对于时间的转换
  9. 输入法问题
  10. Layout 笔记