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.java
public 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.cpp
static 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的资源管理弄透彻,这些概念是绕不开的。

你可能感兴趣的:(AssetManager)