前文我们以Integer为例介绍了最简单的资源的获取,说它是最简单的主要是基于以下两个点:第一,它没有Bag;第二,它是values类型的资源,资源索引表里存放的是最终值,而不是资源路径等资源相关的信息,我们不需要再根据这些信息去取得最终值(比如去加载图片,然后创建drawable对象等等)。我们在这篇文章里主要针对第一点,也就是介绍一下带有Bag的资源,android是怎么去获取这些资源相关的信息的(对于非values类型的资源,我们从resources.arsc里只能获取到资源信息,而不是资源本身)。
我们经常用到的带有Bag的资源主要包括:各种array(array、string-array、integer-array)、plurals、style等,我们一个一个分开说。
我们知道各种array是一种非常典型的带有Bag的资源,它的Bag的key是^index_%d
,其中%d
表示0,1,2,3…等索引号;它的value就是我们在item标签中的值了。假设我们在apk的资源文件中有如下的array:
<string-array name="camera_modes" translatable="false">
<item>photoitem>
<item>videoitem>
<item>refocusitem>
<item>photosphereitem>
<item>panoramaitem>
<item>@android:string/gcmaitem>
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
这三个方法的区别是什么呢?首先,getTextArray
和getStringArray
方法只能用来获取字符串类型的资源。也就是说,他们所对应的资源必须是string-array
或者array
(是array
的时候要注意,它的每一个item都必须是一个字符串,或者是一个字符串的引用即一个指向字符串资源的id,形如@android:string/cance
这种)。至于getTextArray
和getStringArray
方法的区别,和Resources
类中getText
与getString
方法的区别一样:前者获取到的字符串是带style的(比如加粗、斜体等),而后者则是纯文本的。前者返回的是一个CharSequence
数组,后者返回的是一个String
数组。其实我们知道在底层的ResStringPool
中是有存储一个字符串的style信息的,所以我们可以通过getTextArray
将其返回。另外,多说一句String
本身也是CharSequence
接口的实现类,getTextArray
方法返回的CharSequence
接口的实现类到底是哪个呢?是android自己实现的android.text.SpannedString
类,这个跟本文关系不大,就不展开说了。
相比前两者,obtainTypedArray
方法则灵活得多,它可以用来获取各种类型的资源数组,比如CharSequence
、String
、Integer
、Boolean
、Float
、Color
、ColorList
、Drawable
、Dimension
、Fraction
或者资源Id等等,与它对应的xml中资源可以是array
、integer-array
、string-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
,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也是一种典型的带有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-------之实现(二)中已经做过详细的介绍,这里不再赘述,其核心依然是要调用ResTable
的getBagLocked
方法,获取所有的Bag,再将这些Bag的Res_value
中的字段封装到TypedArray
中返回。
还记得我们在前面抛出的那个问题吗,也就是bag_entry
的那个stringBlock
字段是否多余,它存在的意义是什么?这里就不卖关子了,直接给出答案:不多余。在不考虑parent的情况下这个字段确实没有什么意义,但是考虑到parent的时候,就不一样了,看下面的这个例子:
假设系统资源包中有这么个style:
<style name="Widget.CompoundButton.Switch">
- "textOn"
>on
- "textOff">off
style>
假设我们的APK中有这么个style:
<style name="Widget.CompoundButton.Switch.DarkSwitch">
- "android:background"
>@color/dark_grey
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资源的获取,则主要是指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占用啦!