Android资源管理框架-------之Bag资源信息的获取(七)

        前文我们以Integer为例介绍了最简单的资源的获取,说它是最简单的主要是基于以下两个点:第一,它没有Bag;第二,它是values类型的资源,资源索引表里存放的是最终值,而不是资源路径等资源相关的信息,我们不需要再根据这些信息去取得最终值(比如去加载图片,然后创建drawable对象等等)。我们在这篇文章里主要针对第一点,也就是介绍一下带有Bag的资源,android是怎么去获取这些资源相关的信息的(对于非values类型的资源,我们从resources.arsc里只能获取到资源信息,而不是资源本身)。

        我们经常用到的带有Bag的资源主要包括:各种array(array、string-array、integer-array)、plurals、style等,我们一个一个分开说。

string-array资源的获取

        我们知道各种array是一种非常典型的带有Bag的资源,它的Bag的key是^index_%d,其中%d表示0,1,2,3…等索引号;它的value就是我们在item标签中的值了。假设我们在apk的资源文件中有如下的array:

<string-array name="camera_modes" translatable="false">
    <item>photo</item>
    <item>video</item>
    <item>refocus</item>
    <item>photosphere</item>
    <item>panorama</item>
    <item>@android:string/gcma</item> <!--这里引用了系统包中的资源-->
</string-array>

        前文我们介绍过integer资源的获取过程,它最主要的过程是在native层的ResTable对象里,通过getResource方法,根据我们传过来的资源Id,找到对应的ResTable_entry,进而找到Res_value获取我们需要的资源信息。不过,带有Bag的资源,其流程不太一样下面我们详细分析。Resources类中String数组获取的相关方法有三个:
        public CharSequence[] getTextArray(int id) throws NotFoundException
        public String[] getStringArray(int id) throws NotFoundException
        public TypedArray obtainTypedArray(int id) throws NotFoundException

        这三个方法的区别是什么呢?首先,getTextArraygetStringArray方法只能用来获取字符串类型的资源。也就是说,他们所对应的资源必须是string-array或者array(是array的时候要注意,它的每一个item都必须是一个字符串,或者是一个字符串的引用即一个指向字符串资源的id,形如@android:string/cance这种)。至于getTextArraygetStringArray方法的区别,和Resources类中getTextgetString方法的区别一样:前者获取到的字符串是带style的(比如加粗、斜体等),而后者则是纯文本的。前者返回的是一个CharSequence数组,后者返回的是一个String数组。其实我们知道在底层的ResStringPool中是有存储一个字符串的style信息的,所以我们可以通过getTextArray将其返回。另外,多说一句String本身也是CharSequence接口的实现类,getTextArray方法返回的CharSequence接口的实现类到底是哪个呢?是android自己实现的android.text.SpannedString类,这个跟本文关系不大,就不展开说了。

        相比前两者,obtainTypedArray方法则灵活得多,它可以用来获取各种类型的资源数组,比如CharSequenceStringIntegerBooleanFloatColorColorListDrawableDimensionFraction或者资源Id等等,与它对应的xml中资源可以是arrayinteger-arraystring-array等都可以。但是它的用法和前面的那两个不太一样,这个方法不会直接返回对应的资源数组,而是返回一个TypedArray对象,我们可以从这个对象中获取想要的资源。为什么这个方法可以这么灵活呢?是因为这个方法返回TypedArray的时候,并不会对里面的内容做类型检查,而前面的那两个方法则会做类型检查,如果不是String,则不会往结果里写数据。这三个方法实现上有所不同,但获取资源时的原理是相通的,我们就以getStringArray方法为例来简要分析其实现过程:

//frameworks/base/base/core/java/android/content/res/Resources.java
public String[] getStringArray(int id) throws NotFoundException {
    String[] res = mAssets.getResourceStringArray(id);
    if (res != null) {
        return res;
    }
    throw new NotFoundException("String array resource ID #0x"
                     + Integer.toHexString(id));
}

        mAssets是AssetManager的一个实例,进入Assetmanager类:

//frameworks/base/core/java/android/content/res/AssetManager.java
final String[] getResourceStringArray(final int id) {
    String[] retArray = getArrayStringResource(id);
    return retArray;
}

private native final String[] getArrayStringResource(int arrayRes);

        没有什么好说的,进入jni层:

//frameworks/base/core/jni/android_util_AssetManager.cpp
static jobjectArray android_content_AssetManager_getArrayStringResource(JNIEnv* env, jobject clazz,
                                                                        jint arrayResId)
{
    //熟悉的老套路,从java层保存的地址直接拿到native层的AssetManager对象
    AssetManager* am = assetManagerForJavaObject(env, clazz);
    if (am == NULL) {
        return NULL;
    }
    //拿到ResTable对象,所有资源相关的信息都在里面
    const ResTable& res(am->getResources());

    /**
     * 一个Bag也就是我们string-array中的一个item
     * 对应一个bag_entry结构
     */
    const ResTable::bag_entry* startOfBag;
    //拿到Bag的数目,具体到我们的例子,N = 6,我们有6个item
    const ssize_t N = res.lockBag(arrayResId, &startOfBag);
    if (N < 0) {
        return NULL;
    }

    //存储最终结果
    jobjectArray array = env->NewObjectArray(N, g_stringClass, NULL);
    if (env->ExceptionCheck()) {
        res.unlockBag(startOfBag);
        return NULL;
    }

    Res_value value;
    //指向第一个Bag,也就是我们资源中的第一个item
    const ResTable::bag_entry* bag = startOfBag;
    size_t strLen = 0;
    //遍历每一个Bag,可以认为是遍历每一个我们资源文件中的item
    for (size_t i=0; ((ssize_t)i)<N; i++, bag++) {
        //拿到其Res_value
        value = bag->map.value;
        jstring str = NULL;

        //当然,如果它不是一个string,而是一个指向string资源的Id(也就是一个引用),我们要解析这个引用
        //直到得到的终结果不再是引用为止。
        ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL);
#if THROW_ON_BAD_ID
        if (block == BAD_INDEX) {
            jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
            return array;
        }
#endif
        //注意,类型检查,只有我们得到的结果是TYPE_STRING,我们才会写入返回结果数组
        if (value.dataType == Res_value::TYPE_STRING) {
            //拿到对应包中的String Pool
            const ResStringPool* pool = res.getTableStringBlock(block);
            //拿到最终的字符串
            const char* str8 = pool->string8At(value.data, &strLen);
            if (str8 != NULL) {
                str = env->NewStringUTF(str8);
            } else {
                const char16_t* str16 = pool->stringAt(value.data, &strLen);
                str = env->NewString(str16, strLen);
            }

            // If one of our NewString{UTF} calls failed due to memory, an
            // exception will be pending.
            if (env->ExceptionCheck()) {
                res.unlockBag(startOfBag);
                return NULL;
            }
            //放到返回结果里,需要注意的是,此时的str是纯文本信息,不包括文本的style
            env->SetObjectArrayElement(array, i, str);

            // str is not NULL at that point, otherwise ExceptionCheck would have been true.
            // If we have a large amount of strings in our array, we might
            // overflow the local reference table of the VM.
            env->DeleteLocalRef(str);
        }
    }
    res.unlockBag(startOfBag);
    return array;
}

        引用的解析过程,我们在Android资源管理中的Theme和Style-------之实现(二)已经详细介绍过,这里不再重复。需要强调的是,引用有可能是跨包的,比如我们经常在我们的apk中引用系统包中的资源。所以,在引用解析的过程中,我们要更新block值,毕竟block就是包(确切地说是对应的PackageGroup在ResTable中)的索引值。

        android_content_AssetManager_getArrayStringResource方法的实现比较简单,根据资源ID拿到所有的Bag(每一个item),然后从每个Bag里取出对应的值并进行引用的解析,如果它是TYPE_STRING,则把拿到的字符串写入最终结果数组,最后把这个最终结果数组返回。这整个过程中最关键的就是Bag的获取,也就是ResTable对象的lockBag方法了。在介绍这个方法前,先看两个Bag相关的数据结构:

//frameworks/base/libs/androidfw/ResourceTypes.cpp
struct ResTable::bag_set
{
    /**
     * numAttrs表示一个bag_set结构体后面跟了多少个bag_entry
     * 从变量的命名来看,作者写这些数据结构最初是为了处理
     * style或者theme中的属性的,后来用到了所有的bag资源,哈哈
     */
    size_t numAttrs;
    /**
     * availAttrs,bag_set后面的内存能放多少个bag_entry.
     * 初看bag_set这个结构体感觉很不解,主要是bag_set和bag_entry
     * 采用了动态内存管理,availAttrs这个变量仅作内存分配时使用,跟资源本身无关
     * 当往bag_set后面添加bag_entry的时候,发现numAttrs == availAttrs,也就是
     * 坑儿满了的时候,就会再去动态扩展内存。
     */
    size_t availAttrs;
    uint32_t typeSpecFlags;
}
 //frameworks/base/include/androidfw/ResourceTypes.h
struct bag_entry {
    /**
     * 表示这个bag所属的entry,也就是我们的这个string-array所在的
     * 资源包在ResTable中的索引
     */
    ssize_t stringBlock;
    /**
     * ResTable_map 的key是一个ResTable_ref,也就是一个资源的引用或者说是ID
     * 对应与我们前面说的^index_%d
     * ResTable_map 的 value则是一个Res_value,对应于每一个item的值
     */
    ResTable_map map;
};

        我们先说一下Bag的组织形式。一个包中的所有Bag资源会缓存在它所在的PackageGroup里面的bags变量里,它的类型是ByteBucketArray<bag_set**>*ByteBucketArray中按照不同的type和entry对所有的bag资源分类,而每一个bag_set结构体表示一个entry(也就是一个资源项)的所有bag资源。比如,我们例子中camera_modes这个string-array的所有bag都放在一个bag_set里面。一个bag_set后面会跟N个bag_entry,一个bag_entry表示一个Bag资源项,也就是我们string-array的一个item了。其实一个大多数情况下,bag_entry中一个map字段,足以表达完整的Bag信息了,比如map中的value值表示一个字符串的资源ID(也就是一个ResTable_ref)的时候,我们直接根据这个ID就可以获得字符串了,stringBlock这个变量是不需要使用的;当map中的value值不是ID而直接是一个字符串的时候,stringBlock也是没有必要的:试想当我们给camera_modes这个string-array的item赋值为@android:string/gcma的时候,map中Res_value的值将会是0x01******,根据这个ID,自然知道这个字符串在哪个包中;当我们在APK中给item直接赋值一个字符串"photo"时,你说"photo"这个字符串会在哪个包中,当然是在当前包,也就是camera_modes这个string-array所在的包中了。那么为什么还要有stringBlock这个字段呢,这个问题困扰了我好久,大家可以先考虑一下,后面我们再继续说这个问题,接下来看我们的重头戏:

//frameworks/base/libs/androidfw/ResourceTypes.cpp

ssize_t ResTable::lockBag(uint32_t resID, const bag_entry** outBag) const
{
    //线程安全互斥锁
    mLock.lock();
    ssize_t err = getBagLocked(resID, outBag);
    if (err < NO_ERROR) {
        //printf("*** get failed!  unlocking\n");
        mLock.unlock();
    }
    return err;
}

ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
        uint32_t* outTypeSpecFlags) const /*除了resID是输入参数外,都是输出参数*/
{
    if (mError != NO_ERROR) {
        return mError;
    }

    //拿到PackageGroup、Type、Entry的索引
    const ssize_t p = getResourcePackageIndex(resID);
    const int t = Res_GETTYPE(resID);
    const int e = Res_GETENTRY(resID);

    if (p < 0) {
        ALOGW("Invalid package identifier when getting bag for resource number 0x%08x", resID);
        return BAD_INDEX;
    }
    if (t < 0) {
        ALOGW("No type identifier when getting bag for resource number 0x%08x", resID);
        return BAD_INDEX;
    }

    //printf("Get bag: id=0x%08x, p=%d, t=%d\n", resID, p, t);
    //得到PackageGroup对象,合法性检查
    PackageGroup* const grp = mPackageGroups[p];
    if (grp == NULL) {
        ALOGW("Bad identifier when getting bag for resource number 0x%08x", resID);
        return BAD_INDEX;
    }

    //拿到TypeList,合法性检查
    const TypeList& typeConfigs = grp->types[t];
    if (typeConfigs.isEmpty()) {
        ALOGW("Type identifier 0x%x does not exist.", t+1);
        return BAD_INDEX;
    }
    //对entry的index做合法性检查
    const size_t NENTRY = typeConfigs[0]->entryCount;
    if (e >= (int)NENTRY) {
        ALOGW("Entry identifier 0x%x is larger than entry count 0x%x",
             e, (int)typeConfigs[0]->entryCount);
        return BAD_INDEX;
    }

    // First see if we've already computed this bag...
    /**
     * 在PackageGroup中,是有对Bag资源做缓存的,先去查缓存
     */
    if (grp->bags) {
        //bags根据资源的type、entry的index做了分类
        bag_set** typeSet = grp->bags->get(t);
        if (typeSet) {
            bag_set* set = typeSet[e];
            if (set) {
                //缓存里面有
                if (set != (bag_set*)0xFFFFFFFF) {
                    if (outTypeSpecFlags != NULL) {
                        *outTypeSpecFlags = set->typeSpecFlags;
                    }
                    //返回缓存里的数据,bag_set后面跟着所有的bag_entry
                    *outBag = (bag_entry*)(set+1);
                    //ALOGI("Found existing bag for: %p\n", (void*)resID);
                    //返回bag的数量
                    return set->numAttrs;
                }
                ALOGW("Attempt to retrieve bag 0x%08x which is invalid or in a cycle.",
                     resID);
                return BAD_INDEX;
            }
        }
    }

    // Bag not found, we need to compute it!
    /**
     * 如果缓存中木有查到,那么说明我们是第一次获取,
     * 我们就只能自己去解析resources.arsc中对应的数据块来获取了
     */

    //尚未给bags初始化,那就先初始化
    if (!grp->bags) {
        grp->bags = new ByteBucketArray<bag_set**>();
        if (!grp->bags) return NO_MEMORY;
    }
    //尚未查寻过该type的bag
    bag_set** typeSet = grp->bags->get(t);
    if (!typeSet) {
        typeSet = (bag_set**)calloc(NENTRY, sizeof(bag_set*));
        if (!typeSet) return NO_MEMORY;
        grp->bags->set(t, typeSet);
    }

    // Mark that we are currently working on this one.
    typeSet[e] = (bag_set*)0xFFFFFFFF;

    TABLE_NOISY(ALOGI("Building bag: %p\n", (void*)resID));

    // Now collect all bag attributes
    Entry entry;
    //先获取camera_modes这个string-array对应的Entry
    status_t err = getEntry(grp, t, e, &mParams, &entry);
    if (err != NO_ERROR) {
        return err;
    }

    /**
     * entry.entry表示resources.arsc中对应的ResTable_entry
     * 确切地说是ResTable_map_entry,因为它有Bag资源
     * 并且ResTable_map_entry是可以有parent的,这么设计
     * 是考虑到style和theme是可以继承的
     */
    const uint16_t entrySize = dtohs(entry.entry->size);
    const uint32_t parent = entrySize >= sizeof(ResTable_map_entry)
        ? dtohl(((const ResTable_map_entry*)entry.entry)->parent.ident) : 0;
    //count表示我们的bag的数量,当然,不算parent的    
    const uint32_t count = entrySize >= sizeof(ResTable_map_entry)
        ? dtohl(((const ResTable_map_entry*)entry.entry)->count) : 0;
    //bag的个数,不包括parent
    size_t N = count;

    TABLE_NOISY(ALOGI("Found map: size=%p parent=%p count=%d\n",
                     entrySize, parent, count));

    // If this map inherits from another, we need to start
    // with its parent's values.  Otherwise start out empty.
    TABLE_NOISY(printf("Creating new bag, entrySize=0x%08x, parent=0x%08x\n",
                       entrySize, parent));

    // This is what we are building.
    bag_set* set = NULL;

    //如果有parent
    if (parent) {
        uint32_t resolvedParent = parent;

        // Bags encode a parent reference without using the standard
        // Res_value structure. That means we must always try to
        // resolve a parent reference in case it is actually a
        // TYPE_DYNAMIC_REFERENCE.
        /**
         * 上面的注释说的很明显,parent是个资源的ID或者说引用,而不是标准的Res_value,
         * 我们得考虑编译时和运行时同一个资源共享库的包ID不一致的问题
         * 有关资源共享库和DYNAMIC_REFERENCE的概念,请看这里:
         * https://blog.csdn.net/dayong198866/article/details/95226237
         */
        status_t err = grp->dynamicRefTable.lookupResourceId(&resolvedParent);
        if (err != NO_ERROR) {
            ALOGE("Failed resolving bag parent id 0x%08x", parent);
            return UNKNOWN_ERROR;
        }

        //存储parent的bag,考虑style的情况,style是可以有parent的
        const bag_entry* parentBag;
        uint32_t parentTypeSpecFlags = 0;
        //递归调用,拿到parent的所有bag,当然包括parent的parent的parent...
        const ssize_t NP = getBagLocked(resolvedParent, &parentBag, &parentTypeSpecFlags);
        //我们这个资源的所有bag的数量,不考虑parent和自身都有的
        const size_t NT = ((NP >= 0) ? NP : 0) + N;
        //我们直接分配sizeof(bag_set) + NT个bag_entry的内存,考虑到自身有可能
        //会重写parent的bag,所以NT个足够,但是不一定都能用上,可能用不完的,但我们就分配这么多啦
        set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*NT);
        if (set == NULL) {
            return NO_MEMORY;
        }
        if (NP > 0) {
            //把parent的bag拷贝过来,放到bag_set后面。
            memcpy(set+1, parentBag, NP*sizeof(bag_entry));
            //更新当前已经解析了的bag的数目
            set->numAttrs = NP;
            TABLE_NOISY(ALOGI("Initialized new bag with %d inherited attributes.\n", NP));
        } else {
            TABLE_NOISY(ALOGI("Initialized new bag with no inherited attributes.\n"));
            set->numAttrs = 0;
        }
        //表示分配了NT个内存
        set->availAttrs = NT;
        set->typeSpecFlags = parentTypeSpecFlags;
    } else {
        //没有parent的话,那就直接分配 bag_set + N个bag_entry的内存啦
        set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*N);
        if (set == NULL) {
            return NO_MEMORY;
        }
        //一个坑儿而也没占呢
        set->numAttrs = 0;
        //分配了N个坑儿
        set->availAttrs = N;
        set->typeSpecFlags = 0;
    }

    set->typeSpecFlags |= entry.specFlags;


    //到这里parent相关的东西算是处理完了,开始处理自身的bag

    // Now merge in the new attributes...
    /**
     * entry的类型为ResTable::Entry,它内部的entry成员也就是entry.entry指向resources.arsc
     * 中的ResTable_entry在内存中的地址,而entry.entry后面跟着的就是N个ResTable_map,
     * 也就是我们要找的bag了,所以curOff表示我们要找的首个bag相对与entry.type的偏移量
     */
    size_t curOff = (reinterpret_cast<uintptr_t>(entry.entry) - reinterpret_cast<uintptr_t>(entry.type))
        + dtohs(entry.entry->size);
    const ResTable_map* map;
    //bag_entry放在bag_set后面
    bag_entry* entries = (bag_entry*)(set+1);
    
    size_t curEntry = 0;
    uint32_t pos = 0;
    TABLE_NOISY(ALOGI("Starting with set %p, entries=%p, avail=%d\n",
                 set, entries, set->availAttrs));
    while (pos < count) {
        TABLE_NOISY(printf("Now at %p\n", (void*)curOff));

        if (curOff > (dtohl(entry.type->header.size)-sizeof(ResTable_map))) {
            ALOGW("ResTable_map at %d is beyond type chunk data %d",
                 (int)curOff, dtohl(entry.type->header.size));
            return BAD_TYPE;
        }
        //拿到ResTable_map的地址
        map = (const ResTable_map*)(((const uint8_t*)entry.type) + curOff);
        N++;
        //就是ResTable_map中键值对儿中的键或者key,对应一个资源id
        //对应与string-array中的^index_%d
        uint32_t newName = htodl(map->name.ident);
        if (!Res_INTERNALID(newName)) {
            // Attributes don't have a resource id as the name. They specify
            // other data, which would be wrong to change via a lookup.
            if (grp->dynamicRefTable.lookupResourceId(&newName) != NO_ERROR) {
                ALOGE("Failed resolving ResTable_map name at %d with ident 0x%08x",
                        (int) curOff, (int) newName);
                return UNKNOWN_ERROR;
            }
        }
        /**
         * 需要说明的是bag_set后面跟的bag_entry是按照id也就是map->name.ident升序排列的
         * 所以当这里添加一个bag的时候,也会按照顺序插入。
         */
        bool isInside;
        uint32_t oldName = 0;
        /**
         * 找到合适的插入位置,就是最后一个比当前map->name.ident小的哪个bag的下一个位置
         * 或者当前所有bag_entry的末尾
         */
        while ((isInside=(curEntry < set->numAttrs))
                && (oldName=entries[curEntry].map.name.ident) < newName) {
            TABLE_NOISY(printf("#%d: Keeping existing attribute: 0x%08x\n",
                         curEntry, entries[curEntry].map.name.ident));
            curEntry++;
        }

        //这种情况表示这是一个新的bag,尚未加入到bag_set后面
        if ((!isInside) || oldName != newName) {
            // This is a new attribute...  figure out what to do with it.
            if (set->numAttrs >= set->availAttrs) {
                // Need to alloc more memory...
                //内存不够,那就追加内存
                const size_t newAvail = set->availAttrs+N;
                set = (bag_set*)realloc(set,
                                        sizeof(bag_set)
                                        + sizeof(bag_entry)*newAvail);
                if (set == NULL) {
                    return NO_MEMORY;
                }
                //更新坑儿的个数和起始地址
                set->availAttrs = newAvail;
                entries = (bag_entry*)(set+1);
                TABLE_NOISY(printf("Reallocated set %p, entries=%p, avail=%d\n",
                             set, entries, set->availAttrs));
            }
            if (isInside) {
                // Going in the middle, need to make space.
                //把第curEntry个以及它后面的bag往后移动一个位置
                //这样就把第curEntry个位置空出来了,
                //空出来的这个位置就是留给当前bag的,实现当前bag的插入
                memmove(entries+curEntry+1, entries+curEntry,
                        sizeof(bag_entry)*(set->numAttrs-curEntry));
                set->numAttrs++;
            }
            TABLE_NOISY(printf("#%d: Inserting new attribute: 0x%08x\n",
                         curEntry, newName));
        } else {
            //走到这里表示第curEntry个位置的bag,已经放入bag_set
            //后面了,这样我们直接复用,不再新增bag_entry结构
            TABLE_NOISY(printf("#%d: Replacing existing attribute: 0x%08x\n",
                         curEntry, oldName));
        }
        //当前bag插入的位置
        bag_entry* cur = entries+curEntry;
        /**
         * stringBlock的值,果然被设置成了entry也就是我们的string-array所在的包在ResTable中的索引
         */
        cur->stringBlock = entry.package->header->index;
        cur->map.name.ident = newName;
        cur->map.value.copyFrom_dtoh(map->value);
        //map.value可能是动态引用,那么还需要去DynamicRefTable中更新一下
        //packageId,这样我们使用bag资源的时候就可以不用再去考虑这个问题了
        status_t err = grp->dynamicRefTable.lookupResourceValue(&cur->map.value);
        if (err != NO_ERROR) {
            ALOGE("Reference item(0x%08x) in bag could not be resolved.", cur->map.value.data);
            return UNKNOWN_ERROR;
        }

        TABLE_NOISY(printf("Setting entry #%d %p: block=%d, name=0x%08x, type=%d, data=0x%08x\n",
                     curEntry, cur, cur->stringBlock, cur->map.name.ident,
                     cur->map.value.dataType, cur->map.value.data));

        // On to the next!
        curEntry++;
        pos++;
        //这两行代码没看懂,为啥不这么写呢?
        // curOff += sizeof(*map);
        const size_t size = dtohs(map->value.size);
        curOff += size + sizeof(*map)-sizeof(map->value);
    };

    /**
     * 前面的代码中isInside为false的情况下,也就是
     * bag添加到最末尾的情况下,不会走set->numAttrs++
     * 然后在这里加了个判断,打补丁的味道很重,额,貌似补丁打的位置也不好
     * 这个代码差评,哈哈
     */
    if (curEntry > set->numAttrs) {
        set->numAttrs = curEntry;
    }

    // And this is it...
    //终于可以收尾了,直接放到typeSet里,这样更新了缓存,以后再获取就不用
    //再去解析了,直接缓存里拿
    typeSet[e] = set;
    if (set) {
        if (outTypeSpecFlags != NULL) {
            *outTypeSpecFlags = set->typeSpecFlags;
        }
        //输出bag_entry
        *outBag = (bag_entry*)(set+1);
        TABLE_NOISY(ALOGI("Returning %d attrs\n", set->numAttrs));
        //输出bag的数量
        return set->numAttrs;
    }
    return BAD_INDEX;
}

        getBagLocked方法比较长,并且有递归,阅读的时候需要些耐心,总体来说它主要做的包括:先根据resID的package、type、entry索引,去PackageGroup的缓存中查寻,如果查到,则返回,查不到的话,再去内存中的resources.arsc中去解析。解析的时候,先去加载parent的Bag,这是一个递归的过程,并且加载完成后,里面的bag_entry是按照bag的key值升序排列的。加载完parent的bag,就是解析和加载自身的bag了,这个过程其实就是选择一个合适的位置,在顺序表中做一个插入或者更新操作。更新是因为,一个资源自身的bag可以重写其parent的bag,最后更新缓存,返回结果。另外,在添加一个一个的bag的过程中,还会根据情况动态扩展分配内存。

        到这里,string-array这种带有Bag的资源信息的获取过程就算结束了。我们看到,其获取过程并不像不带Bag的那些资源那样直接到ResTable中调用getResource方法,拿到对应的ResTable::Entry,进而获取到对应的Res_value,消除动态引用,再解析一下普通引用就完事儿了;它在获取到对应的ResTable::Entry后,要根据这个ResTable::Entry,去拿到它所对应的所有Bag,毕竟我们关心的数据都放在Bag里,然后再对这些Bag做处理,才能得到最终的结果。

style资源的获取

        style也是一种典型的带有Bag的资源,一个item就是一个Bag,Bag的key是item中的属性,value则是属性的值。我们说的style资源的获取就是指,获取style中某些属性的值。其在Resources类 中对应的方法是:

public TypedArray obtainStyledAttributes(int resid, int[] attrs) throws NotFoundException {
    final int len = attrs.length;
    final TypedArray array = TypedArray.obtain(Resources.this, len);
    array.mTheme = this;
    AssetManager.applyStyle(mTheme, 0, resid, 0, attrs, array.mData, array.mIndices);
    return array;
}

        AssetManager.applyStyle这个方法的实现,我们在Android资源管理中的Theme和Style-------之实现(二)中已经做过详细的介绍,这里不再赘述,其核心依然是要调用ResTablegetBagLocked方法,获取所有的Bag,再将这些Bag的Res_value中的字段封装到TypedArray中返回。

        还记得我们在前面抛出的那个问题吗,也就是bag_entry的那个stringBlock字段是否多余,它存在的意义是什么?这里就不卖关子了,直接给出答案:不多余。在不考虑parent的情况下这个字段确实没有什么意义,但是考虑到parent的时候,就不一样了,看下面的这个例子:
        假设系统资源包中有这么个style:

<style name="Widget.CompoundButton.Switch">
    <item name="textOn">on</item>
    <item name="textOff">off</item>
</style>

        假设我们的APK中有这么个style:

<style name="Widget.CompoundButton.Switch.DarkSwitch">
    <item name="android:background">@color/dark_grey</item>
</style>

        我们知道Widget.CompoundButton.Switch.DarkSwitch继承自Widget.CompoundButton.Switch,假设bag_entry中没有stringBlock字段,那么当我们在APK中去获取android:textOn这个属性的值的时候,我们将会得到什么呢?

        根据前面的步骤,我们在获取Bag的时候,先去获取parent的,也就是Widget.CompoundButton.Switch的,我们得到的bag_entry中没有stringBlock字段,bag_entry.map.value.data的值将会是on这个字符串在系统的Global String Pool中的索引。然后,getBagLocked方法,把parent的textOn这个Bag copy到我们的APK包中的bag_set后面,然后在后面加入android:background这个Bag。我们获取到textOn这个Bag的时候,由于没有stringBlock字段,默认就是从当前包,也就是我们的APK包的的Global String Pool中根据bag_entry.map.value.data的值,也就是那个索引去查找字符串。这明显不对,应该去系统资源包而不是我们APK的资源包中去获取对应的string。所以说,在考虑parent也就是继承的时候,stringBlock这个字段是必须的。

plurals资源的获取

        plurals资源的获取,则主要是指Resources类的getQuantityText方法。关于这个方法的功能网上介绍的很多,这里就不再说了。我们直接看其实现:

//frameworks/base/base/core/java/android/content/res/Resources.java
public CharSequence getQuantityText(int id, int quantity) throws NotFoundException {

    //会根据locale信息,创建对应的rule对象
    NativePluralRules rule = getPluralRule();
    //attrForQuantityCode(rule.quantityForInt(quantity))
    //则会根据不同的quantityCode,返回不同的bag ID
    /**
     * private static int attrForQuantityCode(int quantityCode) {
        switch (quantityCode) {
            case NativePluralRules.ZERO: return 0x01000005;
            case NativePluralRules.ONE:  return 0x01000006;
            case NativePluralRules.TWO:  return 0x01000007;
            case NativePluralRules.FEW:  return 0x01000008;
            case NativePluralRules.MANY: return 0x01000009;
            default:                     return ID_OTHER;
        }
      }
     */
    CharSequence res = mAssets.getResourceBagText(id,
            attrForQuantityCode(rule.quantityForInt(quantity)));
    if (res != null) {
        return res;
    }
    res = mAssets.getResourceBagText(id, ID_OTHER);
    if (res != null) {
        return res;
    }
    throw new NotFoundException("Plural resource ID #0x" + Integer.toHexString(id)
                + " quantity=" + quantity
                + " item=" + stringForQuantityCode(rule.quantityForInt(quantity)));
}

        我们看到getQuantityText方法会根据不同的QuantityCode去获取对应的字符串,如果没有获取到,则会以ID_OTHER这个QuantityCode再去获取一次。如果还没获取到,这抛出异常。和前面的style与各种array的获取不太一样的是,这里我们只是从众多的Bag中根据QuantityCode选择一个合适的,看具体实现:

//frameworks/base/core/java/android/content/res/AssetManager.java
final CharSequence getResourceBagText(int ident, int bagEntryId) {
    synchronized (this) {
        TypedValue tmpValue = mValue;
        //loadResourceBagValue是个native方法,
        int block = loadResourceBagValue(ident, bagEntryId, tmpValue, true);
        if (block >= 0) {
            //类型检查,根据block到对应的Global String Pool中根据tmpValue.data这个索引值,拿到具体的字符串
            if (tmpValue.type == TypedValue.TYPE_STRING) {
                return mStringBlocks[block].get(tmpValue.data);
            }
            return tmpValue.coerceToString();
        }
    }
    return null;
}

        loadResourceBagValue方法的实现很简单:

static jint android_content_AssetManager_loadResourceBagValue(JNIEnv* env, jobject clazz,
                                                           jint ident, jint bagEntryId,
                                                           jobject outValue, jboolean resolve)
{
    //还是熟悉的老套路,从java层保存的地址直接拿到native层的AssetManager对象
    AssetManager* am = assetManagerForJavaObject(env, clazz);
    if (am == NULL) {
        return 0;
    }
    //拿到ResTable对象后才算开始干活,哈哈
    const ResTable& res(am->getResources());

    // Now lock down the resource object and start pulling stuff from it.
    res.lock();

    ssize_t block = -1;
    Res_value value;

    const ResTable::bag_entry* entry = NULL;
    uint32_t typeSpecFlags;
    //熟悉的代码,拿到所有的bag
    ssize_t entryCount = res.getBagLocked(ident, &entry, &typeSpecFlags);
    //从这些bag中选择对应的QuantityCode的bag
    for (ssize_t i=0; i<entryCount; i++) {
        if (((uint32_t)bagEntryId) == entry->map.name.ident) {
            block = entry->stringBlock;
            value = entry->map.value;
        }
        entry++;
    }

    res.unlock();

    if (block < 0) {
        return static_cast<jint>(block);
    }

    uint32_t ref = ident;
    //resolve为true,所以这里还需要对引用做解析
    //但是要不要先解析动态引用呢?不需要!还记得getBagLocked方法已经解析过动态引用了吗?
    if (resolve) {
        block = res.resolveReference(&value, block, &ref, &typeSpecFlags);
#if THROW_ON_BAD_ID
        if (block == BAD_INDEX) {
            jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
            return 0;
        }
#endif
    }
    //copy到输出参数
    if (block >= 0) {
        return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags);
    }
    //return block值
    return static_cast<jint>(block);
}

        plurals资源的获取,也就是getQuantityText的实现相对简单得多,需要说明的也就是plurals资源的bag中,key就是QuantityCode,value则是我们每一个Quantity的具体值。另外大家不知道有没有发现,QuantityCode也是形如0xpptteeee的资源ID,并且其tt部分是0x00,总算知道为啥我们平常用到的资源的type index都是从0x01开始的了,原来0x00被plurals的QuantityCode占用啦!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值