上一篇我们着重分析了资源是如何编译和打包到apk里的,重点在于resources.arsc和R.java两个文件,还不了解的朋友可以先看上一篇的内容:Android资源编译和打包过程分析。熟悉了apk里资源是如何分布的之后,我们就可以来分析apk读取的时候是如何获取到最适合这个设备的资源的过程了。
注意:这里源码都是看的Android 8.0的源码,其他系统版本原理基本类似。
首先我们来提出问题:
Question1:比如一个图片资源被放在drawable-xhdpi和drawable-xxhdpi两个文件夹里,apk读取的时候是如何通过设备信息来获取到最合适的那个图片资源的?
一般情况下,我们在java代码里获取图片,获取string字符串,都是用到如下代码:
//设置背景drawable
tvAdvancedTask.setBackground(getResources().getDrawable(R.drawable.learning_task_unselected));
//设置字体颜色
tvAdvancedTask.setTextColor(getResources().getColor(R.color.color_9B6D00));
我们就从这个入口开始一部一部进行剖析。这里我们主要剖析刚才问题中的图片资源,string,layout等读取过程在底层基本相同,只是在上层解析获取的时候稍微有些不同而已,都是小问题,首先来看下我理的整个流程图,接下来我们会按照流程图来一步一步剖析。
我们先来看getResource()方法:
@Override
public Resources getResources() {
if (mResources == null && VectorEnabledTintResources.shouldBeUsed()) {
mResources = new VectorEnabledTintResources(this, super.getResources());
}
return mResources == null ? super.getResources() : mResources;
}
mResources如果为空的话则调用父类的getResources()来创建,跟进去到父类ContextTheme.java:
@Override
public Resources getResources() {
return getResourcesInternal();
}
private Resources getResourcesInternal() {
if (mResources == null) {
if (mOverrideConfiguration == null) {
mResources = super.getResources();
} else {
final Context resContext = createConfigurationContext(mOverrideConfiguration);
mResources = resContext.getResources();
}
}
return mResources;
}
mOverrideConfiguration是Configuration类的对象,主要负责描述可能影响应用程序检索的资源的所有设备配置信息,包括屏幕大小,屏幕方向等。如果这个对象为空的话,则通过ContextTheme的父类也就是ContextWrapper的getResources()方法获取,不然则创建一个Context对象直接获取。接下来我们进ContextWrapper的getResources()方法:
@Override
public Resources getResources() {
return mBase.getResources();
}
这里其实也就是调用了Context对象的getResources()方法,接着进Context类:
public abstract Resources getResources();
getResources()方法在Context类中是一个抽象函数,那我们就进他的实现类ContextImpl.java中去看:
@Override
public Resources getResources() {
return mResources;
}
这里我们就获取到了已经init好了的Resources实例,其实熟悉Activity创建过程的朋友应该清楚,ActivityThread类的成员函数performLaunchActivity在应用程序进程中会创建一个Activity实例,创建实例的同时会为它设置运行上下文也就是Context实例,而Activity的父类ContextThemeWrapper的父类ContextWrapper类里有一个ContextImpl对象mBase,两者就关联起来了。Activity通过这个mBase对象装饰上了很多ContextImpl类的操作,比如上面一直在讲的getResources()方法,还有getAssets(),startService()等操作,这是个很典型的装饰者模式。
public class ContextWrapper extends Context {
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
/**
* Set the base context for this ContextWrapper. All calls will then be
* delegated to the base context. Throws
* IllegalStateException if a base context has already been set.
*
* @param base The new base context for this wrapper.
*/
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
//...............................
}
这里我们就获取到了Resources类的实例,获取到实例以后我们就可以来分析getDrawable方法了:
@Deprecated
public Drawable getDrawable(@DrawableRes int id) throws NotFoundException {
final Drawable d = getDrawable(id, null);
if (d != null && d.canApplyTheme()) {
Log.w(TAG, "Drawable " + getResourceName(id) + " has unresolved theme "
+ "attributes! Consider using Resources.getDrawable(int, Theme) or "
+ "Context.getDrawable(int).", new RuntimeException());
}
return d;
}
这里传进来的id其实就是该资源对应的那个id,也就是上文中Android资源编译和打包过程分析中第8步给资源生成资源id中的那个id。接着跟下去:
public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)
throws NotFoundException {
return getDrawableForDensity(id, 0, theme);
}
之后进入getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme)方法:
public Drawable getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme) {
final TypedValue value = obtainTempTypedValue();
try {
final ResourcesImpl impl = mResourcesImpl;
impl.getValueForDensity(id, density, value, true);
return impl.loadDrawable(this, value, id, density, theme);
} finally {
releaseTempTypedValue(value);
}
}
这里我们看到新建了一个ResourcesImpl对象,然后调用了该对象的getValueForDensity方法,然后return该对象的loadDrawable方法取加载获取到的Drawable对象。loadDrawable我们稍后再讲,这已经是整个过程的最后收尾了,接下来我们进ResourcesImpl.java的getValueForDensity方法,根据字面意思这个方法就是根据设备取获取资源。
void getValueForDensity(@AnyRes int id, int density, TypedValue outValue,
boolean resolveRefs) throws NotFoundException {
boolean found = mAssets.getResourceValue(id, density, outValue, resolveRefs);
if (found) {
return;
}
throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
}
这个方法调用了AssetManager的getResourceValue方法去判断是否能根据id可以读取到,如果读取不到,则抛出异常。AssetManager这个类也就是Android应用程序资源管理器,也就是我们这篇文章真正的主角,主要负责读取资源文件。在刚才分析Resources对象创建过程中讲到ContextImpl类里有一个getResources()方法,这里的AssetManager对象也是一样,通过ContextImpl里的getAssets()方法获取实例。
接下来来看AssetManager.java的getResourceValue方法
final boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
boolean resolveRefs) {
synchronized (this) {
final int block = loadResourceValue(resId, (short) densityDpi, outValue, resolveRefs);
if (block < 0) {
return false;
}
// Convert the changing configurations flags populated by native code.
outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
outValue.changingConfigurations);
if (outValue.type == TypedValue.TYPE_STRING) {
outValue.string = mStringBlocks[block].get(outValue.data);
}
return true;
}
}
.这里可以看到AssetManager通过调用loadResourceValue方法去加载相对应资源id的资源,并且获取到一个int整型的block值。如果这个block大于等于0的话,就表示加载成功,并且会把结果保存在参数outValue所描述的一个TypedValue对象中。
接着我们再进loadResourceValue方法:
private native final int loadResourceValue(int ident, short density, TypedValue outValue,
boolean resolve);
我们发现这是一个jni方法,所以我们就需要去看对应的c++方法了,源码地址在
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 (kThrowOnBadId) {
if (block == BAD_INDEX) {
jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
return 0;
}
}
uint32_t ref = ident;
if (resolve) {
block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config);
if (kThrowOnBadId) {
if (block == BAD_INDEX) {
jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
return 0;
}
}
}
if (block >= 0) {
return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config);
}
return static_cast(block);
}
根据jni的抛出异常我们可以看出这个方法里的逻辑就是整个的核心逻辑过程,这里我们一步一步慢慢来分析整个过程,上面的代码大致可以分为以下几步:
1、首先调用assetManagerForJavaObject方法把java层AssetManager对象转换为C++层的AssetManager对象。
2、然后调用他的getResources方法获取到一个ResourceTable对象res,ResourceTable对象其实就是上篇资源打包文章中提到的资源表,它包括了所有的资源信息。
3、创建Res_value对象value,ResTable_config对象config,uint32_t类型typeSpecFlags,调用res的getResource方法获取相对应id的资源选项值以及它的配置信息,并保存下来
4、如果resolve是true的话,调用res的resolveReference方法来解析第3部中获取到的资源选项值和配置信息,如果获取到的block标记项为BAD_INDEX的话则抛出异常。
5、如果block大于等于0的话,则调用copyValue方法返回最终的资源内容。
这基本上就是C++层资源文件读取的核心逻辑过程,第1步创建过程就不详述了,主要来看2-5步,接下来让我们深入2-5的代码方法里来看apk是如何找到最适合当前自己的想要的资源选项的。
先来看第2步getResource方法:
const ResTable& AssetManager::getResources(bool required) const
{
const ResTable* rt = getResTable(required);
return *rt;
}
这里我们看到调用了getResTable方法来获取ResTable对象的,我们再进getResTable方法:
const ResTable* AssetManager::getResTable(bool required) const
{
ResTable* rt = mResources;
if (rt) {
return rt;
}
// Iterate through all asset packages, collecting resources from each.
AutoMutex _l(mLock);
if (mResources != NULL) {
return mResources;
}
if (required) {
LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
}
mResources = new ResTable();
updateResourceParamsLocked();
bool onlyEmptyResources = true;
const size_t N = mAssetPaths.size();
for (size_t i=0; i
这里我们看到首先创建了一个ResTable对象rt,指向了mResources,如果不为空则直接返回,这就说明该资源包里面的资源索引表resources.arsc已经被解析过了,如果为空,那么就会去创建一个自动的互斥锁,在Android的native源代码里你会经常看到AutoMutex _l(mLock);,这是什么意思呢?AutoMutex其实就是Thread的一种自动的互斥锁,在函数代码中使用 AutoMutex 就可以锁定对象,而代码执行完AutoMutex所在的代码域之后,就自动释放锁。这里用互斥锁锁上的目的是为了防止其他线程也去解析当前资源包里的resources.arsc资源索引表,所以接下来会再次进行判断,是否为空的情况。
如果还是为空的话,那么接下来就会去解析该资源包里的resources.arsc了。
在上一篇将资源文件打包到apk的文章中,我们已经知道一个apk不仅要去访问自己的资源文件,还要去访问系统的资源文件,所以一个应用程序要使用的资源包一般是2个,一个是系统资源包,一个是自己的apk文件,当然这里也有特殊情况,阿里sophix在资源替换上的方案是创建了一个新的资源包,后面我们会分析到,这里先不提。而这些资源包的路径都会保存在AssetManager的成员变量mAssetPaths里。
这里对mAssetPaths里的每个资源包循环调用appendPathToResTable方法获取一个布尔型的empty值,接下来我们就进appendPathToResTable方法看一下:
bool AssetManager::appendPathToResTable(const asset_path& ap, bool appAsLib) const {
// skip those ap's that correspond to system overlays
if (ap.isSystemOverlay) {
return true;
}
Asset* ass = NULL;
ResTable* sharedRes = NULL;
bool shared = true;
bool onlyEmptyResources = true;
ATRACE_NAME(ap.path.string());
Asset* idmap = openIdmapLocked(ap);
size_t nextEntryIdx = mResources->getTableCount();
ALOGV("Looking for resource asset in '%s'\n", ap.path.string());
if (ap.type != kFileTypeDirectory) {
if (nextEntryIdx == 0) {
// The first item is typically the framework resources,
// which we want to avoid parsing every time.
sharedRes = const_cast(this)->
mZipSet.getZipResourceTable(ap.path);
if (sharedRes != NULL) {
// skip ahead the number of system overlay packages preloaded
nextEntryIdx = sharedRes->getTableCount();
}
}
if (sharedRes == NULL) {
ass = const_cast(this)->
mZipSet.getZipResourceTableAsset(ap.path);
if (ass == NULL) {
ALOGV("loading resource table %s\n", ap.path.string());
ass = const_cast(this)->
openNonAssetInPathLocked("resources.arsc",
Asset::ACCESS_BUFFER,
ap);
if (ass != NULL && ass != kExcludedAsset) {
ass = const_cast(this)->
mZipSet.setZipResourceTableAsset(ap.path, ass);
}
}
if (nextEntryIdx == 0 && ass != NULL) {
// If this is the first resource table in the asset
// manager, then we are going to cache it so that we
// can quickly copy it out for others.
ALOGV("Creating shared resources for %s", ap.path.string());
sharedRes = new ResTable();
sharedRes->add(ass, idmap, nextEntryIdx + 1, false);
#ifdef __ANDROID__
const char* data = getenv("ANDROID_DATA");
LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set");
String8 overlaysListPath(data);
overlaysListPath.appendPath(kResourceCache);
overlaysListPath.appendPath("overlays.list");
addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx);
#endif
sharedRes = const_cast(this)->
mZipSet.setZipResourceTable(ap.path, sharedRes);
}
}
} else {
ALOGV("loading resource table %s\n", ap.path.string());
ass = const_cast(this)->
openNonAssetInPathLocked("resources.arsc",
Asset::ACCESS_BUFFER,
ap);
shared = false;
}
if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
ALOGV("Installing resource asset %p in to table %p\n", ass, mResources);
if (sharedRes != NULL) {
ALOGV("Copying existing resources for %s", ap.path.string());
mResources->add(sharedRes, ap.isSystemAsset);
} else {
ALOGV("Parsing resources for %s", ap.path.string());
mResources->add(ass, idmap, nextEntryIdx + 1, !shared, appAsLib, ap.isSystemAsset);
}
onlyEmptyResources = false;
if (!shared) {
delete ass;
}
} else {
ALOGV("Installing empty resources in to table %p\n", mResources);
mResources->addEmpty(nextEntryIdx + 1);
}
if (idmap != NULL) {
delete idmap;
}
return onlyEmptyResources;
}
这个方法比较长,让我们一点点来分析,前面都是一些初始化的操作,先判断是否是系统资源包,如果是系统资源包,则直接调用AssetManager对象的成员变量mZipSet的方法getZipResourceTable方法直接获取对应的ResTable对象,为了避免重复加载解析系统资源包的情况。
如果不是系统资源包的话,则会先去调用AssetManager对象的成员变量mZipSet的方法getZipResourceTableAsset先获取到一个Asset对象,当然这是针对已经资源包下的resources.arsc已经提取出来的情况,如果没有提取过的话那么ass就为空,就会去加载该资源包路径下的resources.arsc,通过AssetManager对象的openNonAssetInPathLocked方法读取到rsources.arsc保存到Asset对象ass中。
之后的if判断又回到系统资源包,如果是系统资源包的话,也就是nextEntryIndx等于0的情况下,则会进行一系列的缓存操作,以便能够快速地将其复制出来供其他地方使用。这里需要注意系统资源包有一个overlay机制,也就是资源覆盖机制,手机厂商可以利用这个机制来自定义的系统资源以覆盖系统默认的系统资源,达到个性化系统界面的目的。这里会通过addSystemOverlays方法来更新系统资源包,这里就不详述了。
这之后就是把asset对象加载到ResTable对象mResources,这里如果sharedRes不等于null的话,也就是这是一个系统资源包,会通过复制的方式调用add方法把asset对象加载到ResTable的mResources对象中,如果不是的话则会通过解析的方式add进去。接下来我们跟下面的add方法进去看一看,代码在ResourceTypes.cpp里:
status_t ResTable::add(
Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData,
bool appAsLib, bool isSystemAsset) {
const void* data = asset->getBuffer(true);
if (data == NULL) {
ALOGW("Unable to get buffer of resource asset file");
return UNKNOWN_ERROR;
}
size_t idmapSize = 0;
const void* idmapData = NULL;
if (idmapAsset != NULL) {
idmapData = idmapAsset->getBuffer(true);
if (idmapData == NULL) {
ALOGW("Unable to get buffer of idmap asset file");
return UNKNOWN_ERROR;
}
idmapSize = static_cast(idmapAsset->getLength());
}
return addInternal(data, static_cast(asset->getLength()),
idmapData, idmapSize, appAsLib, cookie, copyData, isSystemAsset);
}
status_t ResTable::addInternal(const void* data, size_t dataSize, const void* idmapData, size_t idmapDataSize,
bool appAsLib, const int32_t cookie, bool copyData, bool isSystemAsset)
{
if (!data) {
return NO_ERROR;
}
if (dataSize < sizeof(ResTable_header)) {
ALOGE("Invalid data. Size(%d) is smaller than a ResTable_header(%d).",
(int) dataSize, (int) sizeof(ResTable_header));
return UNKNOWN_ERROR;
}
Header* header = new Header(this);
header->index = mHeaders.size();
header->cookie = cookie;
if (idmapData != NULL) {
header->resourceIDMap = (uint32_t*) malloc(idmapDataSize);
if (header->resourceIDMap == NULL) {
delete header;
return (mError = NO_MEMORY);
}
memcpy(header->resourceIDMap, idmapData, idmapDataSize);
header->resourceIDMapSize = idmapDataSize;
}
mHeaders.add(header);
const bool notDeviceEndian = htods(0xf0) != 0xf0;
if (kDebugLoadTableNoisy) {
ALOGV("Adding resources to ResTable: data=%p, size=%zu, cookie=%d, copy=%d "
"idmap=%p\n", data, dataSize, cookie, copyData, idmapData);
}
if (copyData || notDeviceEndian) {
header->ownedData = malloc(dataSize);
if (header->ownedData == NULL) {
return (mError=NO_MEMORY);
}
memcpy(header->ownedData, data, dataSize);
data = header->ownedData;
}
header->header = (const ResTable_header*)data;
header->size = dtohl(header->header->header.size);
if (kDebugLoadTableSuperNoisy) {
ALOGI("Got size %zu, again size 0x%x, raw size 0x%x\n", header->size,
dtohl(header->header->header.size), header->header->header.size);
}
if (kDebugLoadTableNoisy) {
ALOGV("Loading ResTable @%p:\n", header->header);
}
if (dtohs(header->header->header.headerSize) > header->size
|| header->size > dataSize) {
ALOGW("Bad resource table: header size 0x%x or total size 0x%x is larger than data size 0x%x\n",
(int)dtohs(header->header->header.headerSize),
(int)header->size, (int)dataSize);
return (mError=BAD_TYPE);
}
if (((dtohs(header->header->header.headerSize)|header->size)&0x3) != 0) {
ALOGW("Bad resource table: header size 0x%x or total size 0x%x is not on an integer boundary\n",
(int)dtohs(header->header->header.headerSize),
(int)header->size);
return (mError=BAD_TYPE);
}
header->dataEnd = ((const uint8_t*)header->header) + header->size;
// Iterate through all chunks.
size_t curPackage = 0;
const ResChunk_header* chunk =
(const ResChunk_header*)(((const uint8_t*)header->header)
+ dtohs(header->header->header.headerSize));
while (((const uint8_t*)chunk) <= (header->dataEnd-sizeof(ResChunk_header)) &&
((const uint8_t*)chunk) <= (header->dataEnd-dtohl(chunk->size))) {
status_t err = validate_chunk(chunk, sizeof(ResChunk_header), header->dataEnd, "ResTable");
if (err != NO_ERROR) {
return (mError=err);
}
if (kDebugTableNoisy) {
ALOGV("Chunk: type=0x%x, headerSize=0x%x, size=0x%x, pos=%p\n",
dtohs(chunk->type), dtohs(chunk->headerSize), dtohl(chunk->size),
(void*)(((const uint8_t*)chunk) - ((const uint8_t*)header->header)));
}
const size_t csize = dtohl(chunk->size);
const uint16_t ctype = dtohs(chunk->type);
if (ctype == RES_STRING_POOL_TYPE) {
if (header->values.getError() != NO_ERROR) {
// Only use the first string chunk; ignore any others that
// may appear.
status_t err = header->values.setTo(chunk, csize);
if (err != NO_ERROR) {
return (mError=err);
}
} else {
ALOGW("Multiple string chunks found in resource table.");
}
} else if (ctype == RES_TABLE_PACKAGE_TYPE) {
if (curPackage >= dtohl(header->header->packageCount)) {
ALOGW("More package chunks were found than the %d declared in the header.",
dtohl(header->header->packageCount));
return (mError=BAD_TYPE);
}
if (parsePackage(
(ResTable_package*)chunk, header, appAsLib, isSystemAsset) != NO_ERROR) {
return mError;
}
curPackage++;
} else {
ALOGW("Unknown chunk type 0x%x in table at %p.\n",
ctype,
(void*)(((const uint8_t*)chunk) - ((const uint8_t*)header->header)));
}
chunk = (const ResChunk_header*)
(((const uint8_t*)chunk) + csize);
}
if (curPackage < dtohl(header->header->packageCount)) {
ALOGW("Fewer package chunks (%d) were found than the %d declared in the header.",
(int)curPackage, dtohl(header->header->packageCount));
return (mError=BAD_TYPE);
}
mError = header->values.getError();
if (mError != NO_ERROR) {
ALOGW("No string values found in resource table!");
}
if (kDebugTableNoisy) {
ALOGV("Returning from add with mError=%d\n", mError);
}
return mError;
}
这里我们可以看到add方法进去以后职责基本上就是Asset对象所描述的resources.arsc文件的内容进行解析,解析最后会得到一个系列的Package信息。每一个Package又包含了一个资源类型字符串资源池和一个资源项名称字符串资源池,以及一系列的资源类型规范数据块和一系列的资源项数据块。解析过程这里就不详述了,整个resources.arsc的解析过程在上一篇Android资源编译和打包过程分析已经有详述了,不清楚的朋友可以先去看看上一篇。
ok这里我们就已经把上面的AssetManager的getResource方法弄通了,得到了一个资源表ResTable对象。我们回上去,回到android_content_AssetManager_loadResourceValue方法,我们看到这一步之后创建了Res_value对象value,ResTable_config对象config,uint32_t类型typeSpecFlags,调用刚获取到的RestTable的对象res的getResource方法获取相对应id的资源选项值以及它的配置信息,并保存下来。
我们进res的getResource方法继续探究:
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 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
ResTable_config desiredConfig = mParams;
if (density > 0) {
desiredConfig.density = density;
}
Entry entry;
status_t err = getEntry(grp, t, e, &desiredConfig, &entry);
if (err != NO_ERROR) {
// Only log the failure when we're not running on the host as
// part of a tool. The caller will do its own logging.
#ifndef STATIC_ANDROIDFW_FOR_TOOLS
ALOGW("Failure getting entry for 0x%08x (t=%d e=%d) (error %d)\n",
resID, t, e, err);
#endif
return err;
}
if ((dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) != 0) {
if (!mayBeBag) {
ALOGW("Requesting resource 0x%08x failed because it is complex\n", resID);
}
return BAD_VALUE;
}
const Res_value* value = reinterpret_cast(
reinterpret_cast(entry.entry) + entry.entry->size);
outValue->size = dtohs(value->size);
outValue->res0 = value->res0;
outValue->dataType = value->dataType;
outValue->data = dtohl(value->data);
// The reference may be pointing to a resource in a shared library. These
// references have build-time generated package IDs. These ids may not match
// the actual package IDs of the corresponding packages in this ResTable.
// We need to fix the package ID based on a mapping.
if (grp->dynamicRefTable.lookupResourceValue(outValue) != NO_ERROR) {
ALOGW("Failed to resolve referenced package: 0x%08x", outValue->data);
return BAD_VALUE;
}
if (kDebugTableNoisy) {
size_t len;
printf("Found value: pkg=%zu, type=%d, str=%s, int=%d\n",
entry.package->header->index,
outValue->dataType,
outValue->dataType == Res_value::TYPE_STRING ?
String8(entry.package->header->values.stringAt(outValue->data, &len)).string() :
"",
outValue->data);
}
if (outSpecFlags != NULL) {
*outSpecFlags = entry.specFlags;
}
if (outConfig != NULL) {
*outConfig = entry.config;
}
return entry.package->header->index;
}
先来看传进来的参数,resId是要去寻找资源的资源id,uint32_t* outSpecFlags, ResTable_config* outConfig都是之前新建的两个对象,density往上看过去应该是getDrawableForDensity方法里传进来的0。
首先分别调用getResourcePackageIndex方法,Res_GETTYPE方法,Res_GETENTRY方法获取到该资源id的packageId,typeId和entryId。然后根据这个packageid获取到了一个packageGroup,然后拿这些数据去调用getEntry方法,接下来我们来深究下getEntry方法:
status_t ResTable::getEntry(
const PackageGroup* packageGroup, int typeIndex, int entryIndex,
const ResTable_config* config,
Entry* outEntry) const
{
const TypeList& typeList = packageGroup->types[typeIndex];
if (typeList.isEmpty()) {
ALOGV("Skipping entry type index 0x%02x because type is NULL!\n", typeIndex);
return BAD_TYPE;
}
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));
// Iterate over the Types of each package.
const size_t typeCount = typeList.size();
for (size_t i = 0; i < typeCount; i++) {
const Type* const typeSpec = typeList[i];
int realEntryIndex = entryIndex;
int realTypeIndex = typeIndex;
bool currentTypeIsOverlay = false;
// Runtime overlay packages provide a mapping of app resource
// ID to package resource ID.
if (typeSpec->idmapEntries.hasEntries()) {
uint16_t overlayEntryIndex;
if (typeSpec->idmapEntries.lookup(entryIndex, &overlayEntryIndex) != NO_ERROR) {
// No such mapping exists
continue;
}
realEntryIndex = overlayEntryIndex;
realTypeIndex = typeSpec->idmapEntries.overlayTypeId() - 1;
currentTypeIsOverlay = true;
}
// Check that the entry idx is within range of the declared entry count (ResTable_typeSpec).
// Particular types (ResTable_type) may be encoded with sparse entries, and so their
// entryCount do not need to match.
if (static_cast(realEntryIndex) >= typeSpec->entryCount) {
ALOGW("For resource 0x%08x, entry index(%d) is beyond type entryCount(%d)",
Res_MAKEID(packageGroup->id - 1, typeIndex, entryIndex),
entryIndex, static_cast(typeSpec->entryCount));
// We should normally abort here, but some legacy apps declare
// resources in the 'android' package (old bug in AAPT).
continue;
}
// Aggregate all the flags for each package that defines this entry.
if (typeSpec->typeSpecFlags != NULL) {
specFlags |= dtohl(typeSpec->typeSpecFlags[realEntryIndex]);
} else {
specFlags = -1;
}
const Vector* candidateConfigs = &typeSpec->configs;
std::shared_ptr> filteredConfigs;
if (config && memcmp(&mParams, config, sizeof(mParams)) == 0) {
// Grab the lock first so we can safely get the current filtered list.
AutoMutex _lock(mFilteredConfigLock);
// This configuration is equal to the one we have previously cached for,
// so use the filtered configs.
const TypeCacheEntry& cacheEntry = packageGroup->typeCacheEntries[typeIndex];
if (i < cacheEntry.filteredConfigs.size()) {
if (cacheEntry.filteredConfigs[i]) {
// Grab a reference to the shared_ptr so it doesn't get destroyed while
// going through this list.
filteredConfigs = cacheEntry.filteredConfigs[i];
// Use this filtered list.
candidateConfigs = filteredConfigs.get();
}
}
}
const size_t numConfigs = candidateConfigs->size();
for (size_t c = 0; c < numConfigs; c++) {
const ResTable_type* const thisType = candidateConfigs->itemAt(c);
if (thisType == NULL) {
continue;
}
ResTable_config thisConfig;
thisConfig.copyFromDtoH(thisType->config);
// Check to make sure this one is valid for the current parameters.
if (config != NULL && !thisConfig.match(*config)) {
continue;
}
const uint32_t* const eindex = reinterpret_cast(
reinterpret_cast(thisType) + dtohs(thisType->header.headerSize));
uint32_t thisOffset;
// Check if there is the desired entry in this type.
if (thisType->flags & ResTable_type::FLAG_SPARSE) {
// This is encoded as a sparse map, so perform a binary search.
const ResTable_sparseTypeEntry* sparseIndices =
reinterpret_cast(eindex);
const ResTable_sparseTypeEntry* result = std::lower_bound(
sparseIndices, sparseIndices + dtohl(thisType->entryCount), realEntryIndex,
keyCompare);
if (result == sparseIndices + dtohl(thisType->entryCount)
|| dtohs(result->idx) != realEntryIndex) {
// No entry found.
continue;
}
// Extract the offset from the entry. Each offset must be a multiple of 4
// so we store it as the real offset divided by 4.
thisOffset = dtohs(result->offset) * 4u;
} else {
if (static_cast(realEntryIndex) >= dtohl(thisType->entryCount)) {
// Entry does not exist.
continue;
}
thisOffset = dtohl(eindex[realEntryIndex]);
}
if (thisOffset == ResTable_type::NO_ENTRY) {
// There is no entry for this index and configuration.
continue;
}
if (bestType != NULL) {
// Check if this one is less specific than the last found. If so,
// we will skip it. We check starting with things we most care
// about to those we least care about.
if (!thisConfig.isBetterThan(bestConfig, config)) {
if (!currentTypeIsOverlay || thisConfig.compare(bestConfig) != 0) {
continue;
}
}
}
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;
}
}
}
if (bestType == NULL) {
return BAD_INDEX;
}
bestOffset += dtohl(bestType->entriesStart);
if (bestOffset > (dtohl(bestType->header.size)-sizeof(ResTable_entry))) {
ALOGW("ResTable_entry at 0x%x is beyond type chunk data 0x%x",
bestOffset, dtohl(bestType->header.size));
return BAD_TYPE;
}
if ((bestOffset & 0x3) != 0) {
ALOGW("ResTable_entry at 0x%x is not on an integer boundary", bestOffset);
return BAD_TYPE;
}
const ResTable_entry* const entry = reinterpret_cast(
reinterpret_cast(bestType) + bestOffset);
if (dtohs(entry->size) < sizeof(*entry)) {
ALOGW("ResTable_entry size 0x%x is too small", dtohs(entry->size));
return BAD_TYPE;
}
if (outEntry != NULL) {
outEntry->entry = entry;
outEntry->config = bestConfig;
outEntry->type = bestType;
outEntry->specFlags = specFlags;
outEntry->package = bestPackage;
outEntry->typeStr = StringPoolRef(&bestPackage->typeStrings, actualTypeIndex - bestPackage->typeIdOffset);
outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, dtohl(entry->key.index));
}
return NO_ERROR;
}
getEntry方法很长,简而言之就是为了从一个packageGroup里找到一个指定typeId,entryId和最符合当前配置信息的资源项。
首先从packageGroup中获取到对应的typeList,因为上面获取到的packgroup可能含有多个package,所以这里有一个for循环在每一个package中查找最合适的资源选项。
接着来看这个for循环的内容。for循环获取到每个package的type,因为在一个packageGroup中,第一个永远都是一开始的基package,后面的都是overlay package,如果这个资源类型规范type指向的是idmapEntries的话,就会把realEntryntryIndex,realTypeIndex指向overlay package下提供的index,在运行的时候overlay package是提供应用程序资源ID到包资源ID的映射的。
在上一篇中我们知道ResTable_type结构体中会有一个类型为unit32_t的偏移数组。而这个偏移数组的第entryIndex个元素的值就表示Entry ID等于entryIndex的资源项的具体内容相对于ResTable_type结构体的偏移值。所以这里我们根据realEntryIndex就可以获得这个资源项的具体内容相对于ResTable_type结构体的偏移值。
之后就会去比较ResTable_type里的配置信息和config参数里配置的信息哪个更匹配一点,以此去拿到那个最合适的资源,然后保存在bestType,bestOffset,bsetConfig,bestPackage字段中,并返回给上一层。这样就通过getEntry方法获取到了最匹配现有配置信息的资源了。
这之后,我们再回上去,回到android_content_AssetManager_loadResourceValue方法,这之后就是调用resolveReference方法来解析上面获取到的资源选项值和配置信息了,那让我们来看resolveReference方法:
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 == Res_value::TYPE_REFERENCE
&& value->data != 0 && count < 20) {
if (outLastRef) *outLastRef = 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;
}
if (kDebugTableTheme) {
ALOGI("Resolving reference 0x%x: newIndex=%d, type=0x%x, data=0x%x\n",
value->data, (int)newIndex, (int)value->dataType, 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;
}
这里我们看到我们把获取到的Res_value对象不断的循环嗲用ResTable的getResource方法来进行解析,getResource方法干嘛的上面我们已经讲过了。不断循环的条件是如果这个value的dataType值等于TYPE_REFERENCE的话,这个意思就是这个value它只是一个引用。我们也看到了这里限制了循环20次,防止无限循环。
通过resolveReference方法获取到了blockIndex值后,会进行判定,如果blockIndex 大于等于0的话,则会调用copyValue方法返回最终的资源内容:
jint copyValue(JNIEnv* env, jobject outValue, const ResTable* table,
const Res_value& value, uint32_t ref, ssize_t block,
uint32_t typeSpecFlags, ResTable_config* config)
{
env->SetIntField(outValue, gTypedValueOffsets.mType, value.dataType);
env->SetIntField(outValue, gTypedValueOffsets.mAssetCookie,
static_cast(table->getTableCookie(block)));
env->SetIntField(outValue, gTypedValueOffsets.mData, value.data);
env->SetObjectField(outValue, gTypedValueOffsets.mString, NULL);
env->SetIntField(outValue, gTypedValueOffsets.mResourceId, ref);
env->SetIntField(outValue, gTypedValueOffsets.mChangingConfigurations,
typeSpecFlags);
if (config != NULL) {
env->SetIntField(outValue, gTypedValueOffsets.mDensity, config->density);
}
return block;
}
接下来我们可以回到getDrawableForDensity方法了,在分析完ResourcesImpl的getValueForDensity方法的整个流程以后,return调用的是ResourcesImpl对象的loadDrawable方法去获得Drawable对象,根据我们获取到的资源内容,去根据他的type去获取对应的drawable。
ok,到这里我们总算把这个过程全部理完了,上面的问题我们也基本理通了。。。。
在理清了资源文件打包的时候是如何打包到apk里和apk运行的时候是如何读取资源文件的过程以后,我们就可以做之后的探索了,根据我们理的整个过程,我们怎么样操作下才可以实现资源线上热修复的功能。。。
本系列目录:
Android热修复原理简要介绍和学习计划
个人微信公共账号已上线,欢迎关注: