这篇文章主要介绍Android加载资源的主要流程
我们从一个简单的函数入口进行分析
getResources().getString(resId);
看下源码是怎么实现的(Resources.java)
public String getString(int id) throws NotFoundException {
CharSequence res = getText(id);
if (res != null) {
return res.toString();
}
throw new NotFoundException("String resource ID #0x"
+ Integer.toHexString(id));
}
public CharSequence getText(int id) throws NotFoundException {
CharSequence res = mAssets.getResourceText(id);
if (res != null) {
return res;
}
throw new NotFoundException("String resource ID #0x"
+ Integer.toHexString(id));
}
可以看到最后调用的是mAssets的getResourceText方法,mAssets为一个AssetManager,那我们进去这个AssetManager里面的getResourceText看下究竟做了什么
AssetManager.java:
/*package*/ final CharSequence getResourceText(int ident) {
synchronized (this) {
TypedValue tmpValue = mValue;
int block = loadResourceValue(ident, (short) 0, tmpValue, true);
if (block >= 0) {
if (tmpValue.type == TypedValue.TYPE_STRING) {
return mStringBlocks[block].get(tmpValue.data);
}
return tmpValue.coerceToString();
}
}
return null;
}
这段函数主要涉及了两个类,TypedValue和StringBlock。
TypedValue.java :
/**
* Container for a dynamically typed data value. Primarily used with
* {@link android.content.res.Resources} for holding resource values.
*/
public class TypedValue {
...
/** The type held by this value, as defined by the constants here.
* This tells you how to interpret the other fields in the object. */
public int type;
/** If the value holds a string, this is it. */
public CharSequence string;
/** Basic data in the value, interpreted according to {@link #type} */
public int data;
/** Additional information about where the value came from; only
* set for strings. */
public int assetCookie;
/** If Value came from a resource, this holds the corresponding resource id. */
public int resourceId;
/** If Value came from a resource, these are the configurations for which
* its contents can change. */
public int changingConfigurations = -1;
/**
* If the Value came from a resource, this holds the corresponding pixel density.
* */
public int density;
...
}
可以看到这个类主要是用来存和资源相关的数据的,而StringBlock Conveniences for retrieving data out of a compiled string resource。这个类主要是用来做string类的资源检索和存储的。每一个资源表都对应着一个资源type-value的字符串资源池,StringBlock就是用来保存这种字符串的。那么可以知道getResourceText的主要流程是
1)调用loadResourceValue函数,把资源信息加载到TypedValue里面。
2)loadResourceValue返回的block从StringBlock内去拿内容。那么这里面遗留一个问题,这里面的block是什么意思呢?
下面我们着重看下loadResourceValue这个函数,恩,这个函数是一个native函数,具体的实现在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* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return 0;
}
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) {
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
}
return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config) : block;
和这段代码相关的类是ResTable,在doc上面是这样描述的Convenience class for accessing data in a ResTable resource。这个类和资源表是有关系的,具体的关系我们之后再阐明,那么上面这个函数主要做的事情就是从ResTable中查找资源,我们接着追一下里面的getResource函数。
ResourceTypes.cpp:
ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag, uint16_t density,
uint32_t* outSpecFlags, ResTable_config* outConfig) const
{
if (mError != NO_ERROR) {
return mError;
}
const ssize_t p = getResourcePackageIndex(resID);
const int t = Res_GETTYPE(resID);
const int e = Res_GETENTRY(resID);
if (p < 0) {
if (Res_GETPACKAGE(resID)+1 == 0) {
ALOGW("No package identifier when getting value for resource number 0x%08x", resID);
} else {
ALOGW("No known package when getting value for resource number 0x%08x", resID);
}
return BAD_INDEX;
}
if (t < 0) {
ALOGW("No type identifier when getting value for resource number 0x%08x", resID);
return BAD_INDEX;
}
const Res_value* bestValue = NULL;
const Package* bestPackage = NULL;
ResTable_config bestItem;
memset(&bestItem, 0, sizeof(bestItem)); // make the compiler shut up
if (outSpecFlags != NULL) *outSpecFlags = 0;
// Look through all resource packages, starting with the most
// recently added.
const PackageGroup* const grp = mPackageGroups[p];
if (grp == NULL) {
ALOGW("Bad identifier when getting value for resource number 0x%08x", resID);
return BAD_INDEX;
}
// Allow overriding density
const ResTable_config* desiredConfig = &mParams;
ResTable_config* overrideConfig = NULL;
if (density > 0) {
overrideConfig = (ResTable_config*) malloc(sizeof(ResTable_config));
if (overrideConfig == NULL) {
ALOGE("Couldn't malloc ResTable_config for overrides: %s", strerror(errno));
return BAD_INDEX;
}
memcpy(overrideConfig, &mParams, sizeof(ResTable_config));
overrideConfig->density = density;
desiredConfig = overrideConfig;
}
ssize_t rc = BAD_VALUE;
size_t ip = grp->packages.size();
while (ip > 0) {
ip--;
int T = t;
int E = e;
const Package* const package = grp->packages[ip];
if (package->header->resourceIDMap) {
uint32_t overlayResID = 0x0;
status_t retval = idmapLookup(package->header->resourceIDMap,
package->header->resourceIDMapSize,
resID, &overlayResID);
if (retval == NO_ERROR && overlayResID != 0x0) {
// for this loop iteration, this is the type and entry we really want
ALOGV("resource map 0x%08x -> 0x%08x\n", resID, overlayResID);
T = Res_GETTYPE(overlayResID);
E = Res_GETENTRY(overlayResID);
} else {
// resource not present in overlay package, continue with the next package
continue;
}
}
const ResTable_type* type;
const ResTable_entry* entry;
const Type* typeClass;
ssize_t offset = getEntry(package, T, E, desiredConfig, &type, &entry, &typeClass);
if (offset <= 0) {
// No {entry, appropriate config} pair found in package. If this
// package is an overlay package (ip != 0), this simply means the
// overlay package did not specify a default.
// Non-overlay packages are still required to provide a default.
if (offset < 0 && ip == 0) {
ALOGW("Failure getting entry for 0x%08x (t=%d e=%d) in package %zd (error %d)\n",
resID, T, E, ip, (int)offset);
rc = offset;
goto out;
}
continue;
}
if ((dtohs(entry->flags)&entry->FLAG_COMPLEX) != 0) {
if (!mayBeBag) {
ALOGW("Requesting resource %p failed because it is complex\n",
(void*)resID);
}
continue;
}
TABLE_NOISY(aout << "Resource type data: "
<< HexDump(type, dtohl(type->header.size)) << endl);
if ((size_t)offset > (dtohl(type->header.size)-sizeof(Res_value))) {
ALOGW("ResTable_item at %d is beyond type chunk data %d",
(int)offset, dtohl(type->header.size));
rc = BAD_TYPE;
goto out;
}
const Res_value* item =
(const Res_value*)(((const uint8_t*)type) + offset);
ResTable_config thisConfig;
thisConfig.copyFromDtoH(type->config);
if (outSpecFlags != NULL) {
if (typeClass->typeSpecFlags != NULL) {
*outSpecFlags |= dtohl(typeClass->typeSpecFlags[E]);
} else {
*outSpecFlags = -1;
}
}
if (bestPackage != NULL &&
(bestItem.isMoreSpecificThan(thisConfig) || bestItem.diff(thisConfig) == 0)) {
// Discard thisConfig not only if bestItem is more specific, but also if the two configs
// are identical (diff == 0), or overlay packages will not take effect.
continue;
}
bestItem = thisConfig;
bestValue = item;
bestPackage = package;
}
TABLE_NOISY(printf("Found result: package %p\n", bestPackage));
if (bestValue) {
outValue->size = dtohs(bestValue->size);
outValue->res0 = bestValue->res0;
outValue->dataType = bestValue->dataType;
outValue->data = dtohl(bestValue->data);
if (outConfig != NULL) {
*outConfig = bestItem;
}
TABLE_NOISY(size_t len;
printf("Found value: pkg=%d, type=%d, str=%s, int=%d\n",
bestPackage->header->index,
outValue->dataType,
outValue->dataType == bestValue->TYPE_STRING
? String8(bestPackage->header->values.stringAt(
outValue->data, &len)).string()
: "",
outValue->data));
rc = bestPackage->header->index;
goto out;
}
out:
if (overrideConfig != NULL) {
free(overrideConfig);
}
return rc;
}
好吧,我也承认我看到这个函数的时候也很头痛,但是这个函数应该是整个资源加载里面最核心的部分了,让我们来慢慢分析下这167行的函数里面到底写了什么吧。
1)resId的解析。
#define Res_GETTYPE(id) (((id>>16)&0xFF)-1)
#define Res_GETENTRY(id) (id&0xFFFF)
const ssize_t p = getResourcePackageIndex(resID);
const int t = Res_GETTYPE(resID);
const int e = Res_GETENTRY(resID);
这个函数首先解析了resId,在android内resId分为三个部分,package,type和entry,这三部分是通过位偏移值来保存在id信息里面的,resId有四个字节,最高字节保存package信息,第二个字节保存type信息,最后两个字节保存entry的信息。其中具体的分析可以参考老罗的这篇文章 点击打开链接
2)找到相应的PackageGroup。关于PackageGroup的描述如下
// A group of objects describing a particular resource package.
// The first in ‘package’ is always the root object (from the resource
// table that defined the package); the ones after are skins on top of it.
可以知道是用来描述一系列的资源资源文件的,且第一个package是系统的资源。
3)循环PackageGroup,判断是否是overlay包
这里会去循环PackageGroup里面的每一个Package,首先判断这个Package是不是overlay package,overlay package的意思是该package是用来覆盖资源的,如果是overlay package,则对id进行相应的转换。关于overlay机制可以参考 安卓overlay机制
4)根据资源的type和entry判断该package内是否有相应的资源。这里面主要表现在getEntry这个函数里面,这里需要了解resources.arsc的资源结构,可以参考 安卓资源打包过程 这篇文章了解一二。
5)如果该包内无要找的资源,表现在getEntry的返回值<=0,则查找下一个包,否则进行资源的最优匹配。每一个资源有相应的配置信息,配置的内容保存在ResTable_config结构体内,如果当前匹配到的资源比之前匹配的资源要优,则采用当前匹配的资源,恩,就是下面的这段代码了
if (bestPackage != NULL &&
(bestItem.isMoreSpecificThan(thisConfig) || bestItem.diff(thisConfig) == 0)) {
// Discard thisConfig not only if bestItem is more specific, but also if the two configs
// are identical (diff == 0), or overlay packages will not take effect.
continue;
}
bestItem = thisConfig;
bestValue = item;
bestPackage = package;
6)返回。终于分析到最后一步啦。上面的步骤已经把能够匹配到的最优资源保存在
const Res_value* bestValue = NULL;
const Package* bestPackage = NULL;
ResTable_config bestItem;
这三个变量里面了,最后将这三个变量信息拷贝到要输出的Res_value* outValue变量内,并且返回资源所在的package的索引给调用方,也就是android_content_AssetManager_loadResourceValue这里面的block了。到这里getResource这个函数就分析完成了。
回到上面的android_content_AssetManager_loadResourceValue 函数,我们获取了返回值block,那么这个函数还剩最后一步
if (resolve) {
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
}
在我们getText的这个场景,resolve这个参数为true,也就是我们最后还需要对资源做解析的操作,我们来看下这个函数吧。
ResourceTypes.cpp:
ssize_t ResTable::resolveReference(Res_value* value, ssize_t blockIndex,
uint32_t* outLastRef, uint32_t* inoutTypeSpecFlags,
ResTable_config* outConfig) const
{
int count=0;
while (blockIndex >= 0 && value->dataType == value->TYPE_REFERENCE
&& value->data != 0 && count < 20) {
if (outLastRef) *outLastRef = value->data;
uint32_t lastRef = value->data;
uint32_t newFlags = 0;
const ssize_t newIndex = getResource(value->data, value, true, 0, &newFlags,
outConfig);
if (newIndex == BAD_INDEX) {
return BAD_INDEX;
}
TABLE_THEME(ALOGI("Resolving reference %p: newIndex=%d, type=0x%x, data=%p\n",
(void*)lastRef, (int)newIndex, (int)value->dataType, (void*)value->data));
//printf("Getting reference 0x%08x: newIndex=%d\n", value->data, newIndex);
if (inoutTypeSpecFlags != NULL) *inoutTypeSpecFlags |= newFlags;
if (newIndex < 0) {
// This can fail if the resource being referenced is a style...
// in this case, just return the reference, and expect the
// caller to deal with.
return blockIndex;
}
blockIndex = newIndex;
count++;
}
return blockIndex;
}
这个函数的作用是解析引用类型的资源,一个资源的引用也有可能是引用,如果这个资源是引用类型的话(value->dataType == value->TYPE_REFERENCE),那么就调用getResource继续进行解析,直到getResource解析的资源无效或者超过20次循环为止。