这是Android资源系列专题第二篇,主要分析运行时资源的访问流程。
资源系列更新计划,欢迎持续关注?:
平时开发中访问资源最常见的形式:
getContext().getResources().getColor(R.id.text_color)
接下来以android 6.0源码分析此过程
对Context
不太熟悉的可以参考之前的文章[Android Context]解析,
我们先来看getContext().getResources()
得到Resources对象的过程,
//-------------------Context.java--------------------
/** Return a Resources instance for your application's package. */
//具体实现在对应的ContextImpl
public abstract Resources getResources();
//------------------ContextImpl.java-----------------
@Override
public Resources getResources() {
//mResource通过setResources()
return mResources;
}
//我们需要关注context的setResources何时被调用
void setResources(Resources r) {
mResources = r;
}
/*
* 从[Android Context解析]中我们知道Application级别的Context是
* ActivityThread main中设置完成的
*/
//--------------------ActivityThread.java-----------------
public static void main(String[] args) {
//...
ActivityThread thread = new ActivityThread();
thread.attach(false);
//...
}
private void attach(boolean system) {
//...
try {
mInstrumentation = new Instrumentation();
//分析创建Application context----->ContextImpl.createAppContext
ContextImpl context = ContextImpl.createAppContext(this, getSystemContext().mPackageInfo);
// new application, attachBaseContext
mInitialApplication = context.mPackageInfo.makeApplication(true, null);
//onCreate
mInitialApplication.onCreate();
} catch (Exception e) {
throw new RuntimeException(
"Unable to instantiate Application():" + e.toString(), e);
}
}
//------------------ContextImpl.java-----------------
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
ContextImpl context = new ContextIml(null, mainThread, packageInfo, null, null, null, null);
//!!!这里设置了Resources----> LoadedApk.getResources()
context.setResources(packageInfo.getResources);
}
//----------LoadedApk.java (简单理解这个类用来描述已安装的apk)---
public Resources getResources() {
if (mResources == null) {
//注意传入mResDir /data/app/com.....xxx/base.apk ....
mResources = ResourcesManager.getInstance().getResources(null, mResDir,
splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
getClassLoader());
}
return mResources();
}
//---------------------ResourcesManager.java---------------
public Resources getResources(@Nullable IBinder activityToken,
@Nullable String resDir,
@Nullable String[] splitResDirs,
@Nullable String[] overlayDirs,
@Nullable String[] libDirs,
int displayId,
@Nullable Configuration overrideConfig,
@NonNull CompatibilityInfo compatInfo,
@Nullable ClassLoader classLoader) {
//...
//传入resDir构造一个ResoucesKey, ,
final ResourcesKey key = new ResourcesKey(
resDir,..);
return getOrCreateResources(activityToken, key, classLoader);
...
}
/*
* 通过ResourceKey看缓存map中是否有,如果没有就生成新的Resources
*/
private Resources getOrCreateResources(IBinder activityToken, ResourcesKey key, ClassLoader classloader) {
//....
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
//通过key创建一个ResourceImpl!!! 后续分析ResourcesImpl的构造
ResourcesImpl resourcesImpl = createResourcesImpl(key);
synchronized (this) {
ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
if (existingResourcesImpl != null) {
resourcesImpl.getAssets().close();
resourcesImpl = existingResourcesImpl;
} else {
// 加到cache里面.
mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
}
final Resources resources;
//传入构造出来的resoucesImpl 生成一个Resources
resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
//...
return resources;
}
}
private Resources getOrCreateResourcesForActivityLocked(IBinder activityToken, ClassLoader classLoader, ResourcesImpl impl, CompatibilityInfo compatInfo) {
//...
Resources resources = new Resources(classloader);
//resources设入resourcesImpl并持有
resources.setImpl(impl);
return resources;
}
综上,可以看到,在应用application初始化的时候会创建对应的Context,然后在context的创建过程中会以当前应用的安装目录为参数构造出一个ResourcesImpl对象(并缓存起来),然后构造一个Resources对象持有它。大概流程下图所示,
目前为止,我们已经可以通过Context得到可以访问当前应用资源的Resources和对应ResourcesImpl对象,接下来我们来分析,对应的Resources是如何在资源访问中发挥作用的。
经过上面的流程我们已经得到了访问资源的Resources对象,我们来分析getResource().getColor(R.color.text_color)
流程
//---------------------Resources.java----------------
public int getColor(@ColorRes int id) throws NotFoundException
{
return getColor(id, null);
}
public int getColor(@ColorRes int id, @Nullable Theme theme) throws NotFountException {
final TypedValue value = obtainTempTypedValue();
try {
//mResourcesImpl 通过前面分析的setImpl()设入
final ResourcesImpl impl = mResourcesImpl;
//最终通过ResourcesImpl getValue()
impl.getValue(id, value, true);
} finally {
//...
}
}
/*
* 通过分析Resources.java中其他方法可以看到Resources中的getXXX()
* 方法最终都是调用ResourcesImpl中相关关方法完成
*/
//-------------------ResourcesImpl.java----------------
void getValue(int id, TypedValue outValue, boolean resolveRefs) {
//调用AssetManager getResourceValue
boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
if (found) {
return;
}
throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
}
/*
* 分析源码可以看出ResourcesImpl的相关调用最终调用的是其成员变
* mAsset的相关方法,mAssets是在ResourcesImpl构造时传入
*/
public ResourcesImpl(AssetManager assets, ...) {
//...
mAssets = assets;
mAssets.ensureStringBlocks();
//...
}
/*
* 回到前面ResourcesManager中ApplicationContext对
* ResourcesImpl的构造
*/
//------------------ResourcesManager.java----------------
private ResourcesImpl createResourcesImpl(ResourcesKye key) {
//...通过ResourcesKey构造一个AssetManager
final AssetManager assets = createAssetManager(key);
//将assetManager用于 构造 resourceImpl
final ResourceImpl impl = new ResourcesImpl(assets, dm, config, daj);
return impl;
}
protected AssetManager createAssetManager(final ResourcesKey key) {
//构造一个AssetManager
AssetManager assets = new AssetManager();
//传入apk安装路径
if(key.mResDir != null) {
//调用addAssetPath
if (assets.addAssetPath(key.mResdir) == 0) {
return null;
}
}
//...
}
/*
* 接下来看AssetManager的初始化与对应addAssetPath
*/
//----------------------AssetManager.java---------------
public AssetManager() {
synchronized (this) {
// isSystem = false -> native init
init(false)
ensureSystemAssets();
}
}
//native init
private native final void init(boolean isSystem);
/*
* 此方法主要是为了给当前AssetManager赋值一个可以访问system资源能力的
* AssetManager
*/
private static void ensureSystemAssets() {
synchronized (sSync) {
if (sSystem == null) {
AssetManager system = new AssetManager(true);
system.makeStringBlocks(null);
sSystem = system;
}
}
}
public final int addAssetPath(String path) {
return addAssetPathINternal(path, false);
}
public final int addAssetPathInternal(String path, booean appAsLib) {
synchronized(this) {
//调用Native方法
int res = addAssetPathNative(path, appAsLib);
makeStringBlocks(mStringBlocks);
return res;
}
}
//调用native
private native final int addAssetPathNative(String path, boolean appAsLib);
可以看到最终Java层的AssetManager的初始化和资源访问最终都是调用到Native层,下面看Native层相关代码
//---------------android_util_AssetManager.cpp-----------
/*
* AssetManager.java init()对应Native方法
*/
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem) {
//传入init(isSystem)
if (isSystem) {
verifySystemIdmaps();
}
//构造一个Native层的AssetManager
AssetManager* am = new AssetManager();
//...
am->addDefaultAssets();
//将Native层AssetManager赋值给Java层AssetManager的mObject变量
env->SetLongFiled(clazz, gAssetManagerOffsets.mObject, reinterpret_cast(am));
}
//Java层AssetManager addAssetPathNative对应Native实现
static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz, jstring) {
ScopedUtfChars path8(env, path);
//通过当前Java层的AssetManager对象中的mObject得到native assetManager
AssetManager* am = assetManagerForJavaObject(env, clazz);
bool res = am->addAssetPath(String8(path8.c_str()), &cookie);
//添加成功,返回 !=0
return (res) ? static_cast(cookie) : 0
}
/*
* Java层AssetManager 的init和addAssetPath均是通过Native层
* AssetManager来实现,接下来分析AssetManager.cpp的相关实现
*/
//--------------------AssetManager.cpp-----------------
AssetManager::AssetManager(CacheMode cacheMode)
: mLocale(NULL), mVendor(NULL),
mResources(NULL), mConfig(new ResTable_config),
mCacheMode(cacheMode), mCacheValid(false)
{
memset(mConfig, 0, sizeof(ResTable_config));
}
//前面native init时调用此方法,添加系统资源路径
bool AssetManager::addDefaultAssets()
{
//得到系统资源路径 /system/framework/framework-res.apk
const char* root = getenv("ANDROID_ROOT");
LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");
String8 path(root);
path.appendPath(kSystemAssets);
//还是调用addAssetPath
return addAssetPath(path, NULL);
}
//addAssetPath
bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
{
AutoMutex _l(mLock);
asset_path ap;
//当前传入Path
String8 realPath(path);
...
//检查添加路径中是否包含AndroidManifest.xml文件
Asset* manifestAsset = const_cast(this)->openNonAssetInPathLocked(
kAndroidManifest, Asset::ACCESS_BUFFER, ap);
if (manifestAsset == NULL) {
// This asset path does not contain any resources.
delete manifestAsset;
return false;
}
delete manifestAsset;
//见AssetManager.h Vector mAssetPaths;用于保存add进来的asset_path
mAssetPaths.add(ap);
//...
//见AssetManager.h mutable ResTable* mResources
if (mResources != NULL) {
appendPathToResTable(ap);
}
return true;
}
//添加path到ResTable
bool AssetManager::appendPathToResTable(const asset_path& ap) const {
//...
//构造Asset
Asset* ass = NULL;
ResTable* sharedRes = NULL;
bool shared = true;
bool onlyEmptyResources = true;
if (ap.type != kFileTypeDirectory) {
...
if (sharedRes == NULL) {
//第一次未解析过,为null
ass = const_cast(this)->
mZipSet.getZipResourceTableAsset(ap.path);
if (ass == NULL) {
//读取解析resouces.arsc
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) {
sharedRes = new ResTable();
sharedRes->add(ass, idmap, nextEntryIdx + 1, false);
//...
sharedRes = const_cast(this)->
mZipSet.setZipResourceTable(ap.path, sharedRes);
}
}
} else {
//解析对应path下的resources.arsc得到Asset
ass = const_cast(this)->
openNonAssetInPathLocked("resources.arsc",
Asset::ACCESS_BUFFER,
ap);
shared = false;
}
//最终ResTable add(Asset)
if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
if (sharedRes != NULL) {
mResources->add(sharedRes);
} else {
mResources->add(ass, idmap, nextEntryIdx + 1, !shared);
}
onlyEmptyResources = false;
if (!shared) {
delete ass;
}
} else {
mResources->addEmpty(nextEntryIdx + 1);
}
return onlyEmptyResources;
}
/*
* 我们先来看如何通过path中的resources.arsc构造出对应Asset对象
* 再来看如何将解析得到的asset对象add到ResTable对象中
*/
//解析ap路径下的resources.arsc文件
Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode mode,
const asset_path& ap)
{
Asset* pAsset = NULL;
//区分是压缩包还是目录
/* look at the filesystem on disk */
if (ap.type == kFileTypeDirectory) {
String8 path(ap.path);
path.appendPath(fileName);
//open File
pAsset = openAssetFromFileLocked(path, mode);
if (pAsset == NULL) {
/* try again, this time with ".gz" */
path.append(".gz");
pAsset = openAssetFromFileLocked(path, mode);
}
//asset source赋值
if (pAsset != NULL) {
//printf("FOUND NA '%s' on disk\n", fileName);
pAsset->setAssetSource(path);
}
} else {
String8 path(fileName);
//open zip: 对应base.apk这种情况
/* check the appropriate Zip file */
ZipFileRO* pZip = getZipFileLocked(ap);
if (pZip != NULL) {
ZipEntryRO entry = pZip->findEntryByName(path.string());
if (entry != NULL) {
pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
pZip->releaseEntry(entry);
}
}
if (pAsset != NULL) {
/* create a "source" name, for debug/display */
pAsset->setAssetSource(
createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()), String8(""),
String8(fileName)));
}
}
return pAsset;
}
//我们以打开zip中的resources.arsc为例
Asset* AssetManager::openAssetFromZipLocked(const ZipFileRO* pZipFile,
const ZipEntryRO entry, AccessMode mode, const String8& entryName)
{
Asset* pAsset = NULL;
//解压文件得到dataMap
//...
pAsset = Asset::createFromCompressedMap(dataMap,
static_cast(uncompressedLen), mode);
return pAsset;
}
//-------------------Asset.cpp, Asset.h------------------
void setAssetSource(const String8& path) { mAssetSource = path; }
/*
* Create a new Asset from compressed data in a memory mapping.
*/
Asset* Asset::createFromCompressedMap(FileMap* dataMap,
size_t uncompressedLen, AccessMode mode)
{
_CompressedAsset* pAsset;
status_t result;
pAsset = new _CompressedAsset;
//读取resources.arsc data
result = pAsset->openChunk(dataMap, uncompressedLen);
//...
pAsset->mAccessMode = mode;
return pAsset;
}
//读取resources.arsc
status_t _FileAsset::openChunk(const char* fileName, int fd, off64_t offset, size_t length)
{
//根据文件描述符打开文件
/* after fdopen, the fd will be closed on fclose() */
mFp = fdopen(fd, "rb");
if (mFp == NULL)
return UNKNOWN_ERROR;
mStart = offset;
mLength = length;
assert(mOffset == 0);
// fseek
/* seek the FILE* to the start of chunk */
if (fseek(mFp, mStart, SEEK_SET) != 0) {
assert(false);
}
mFileName = fileName != NULL ? strdup(fileName) : NULL;
return NO_ERROR;
}
/*
* 以上得到一个打开了path 下 resources.arsc文件的Asset对象, 然后将
* 其add到ResTable结构中去
*/
//------------------ResourcesTypes.cpp-------------
status_t ResTable::add(Asset* asset, const int32_t cookie, bool copyData) {
//Asset getBuffer
const void* data = asset->getBuffer(true);
if (data == NULL) {
ALOGW("Unable to get buffer of resource asset file");
return UNKNOWN_ERROR;
}
//调用自身addInternal
return addInternal(data, static_cast(asset->getLength()), NULL, 0, cookie, copyData);
}
//解析具体resources.arsc中的data,构造Header, ResChunk_header
status_t ResTable::addInternal(const void* data, size_t dataSize, const void* idmapData, size_t idmapDataSize,
const int32_t cookie, bool copyData)
{
//...
Header* header = new Header(this);
header->index = mHeaders.size();
header->cookie = cookie;
//...
mHeaders.add(header);
const bool notDeviceEndian = htods(0xf0) != 0xf0;
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);
//...
header->dataEnd = ((const uint8_t*)header->header) + header->size;
// Iterate through all chunks.
size_t curPackage = 0;
//ResChunk_header
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);
}
//....
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) {
...
}
} else if (ctype == RES_TABLE_PACKAGE_TYPE) {
if (curPackage >= dtohl(header->header->packageCount)) {
...
}
//解析package
if (parsePackage((ResTable_package*)chunk, header) != NO_ERROR) {
return mError;
}
curPackage++;
} else {
}
chunk = (const ResChunk_header*)
(((const uint8_t*)chunk) + csize);
}
if (curPackage < dtohl(header->header->packageCount)) {
...
}
return mError;
}
综上,可以看到ResourcesImpl最终访问资源均是通过其成员变量mAsset来实现的,而mAsset对资源的访问均是通过其成员变量mObject
所对应的Native AssetManager对象,每当调用其addAssetPath(String path)
就会解析其path下的resources.arsc文件得到一个Asset对象,然后将其add进当前应用对应的ResTable对象中(进一步解析得到ResChunkHeader等的过程)。示意图如下:
通过上述流程,已经持有一个可以访问资源的assetManager对象了,并且native层也构造好了可供查找的数据结构(ResTable);接下来我们以
getColor(R.color.text_color)
为例,分析相关实现:
//---------------------R.java------------------
/*
* 由之前的《Android资源初探(一)资源打包》可以知道打包过程中生成
* R.java中保存着不同Type的资源对应的id,格式为0xPPTTEEEE
*/
public final class R {
public static final class color {
public static final int text_color=0x7f030000;
}
}
/*
* 由之前ResourcesImpl.java的分析,getColor最终调
* mAssets.getResourceValue()
*/
//-------------------AssetManager.java------------------
synchronized (this) {
//native 方法, return true,更改传入的outValue值
final int block = loadResourceValue(resId, (short) densityDpi, outValue, resolveRefs);
if (block < 0) {
return false;
}
//TODO: 注意TypedValue类,自带方法对取得的值进行了转化 if (outValue.type == TypedValue.TYPE_STRING) {
outValue.string = mStringBlocks[block].get(outValue.data);
}
return true;
}
private native final int loadResourceValue(int ident, short density, TypedValue outValue,
boolean resolve);
//----------------android_util_AssetManager.cpp--------
//java loadResouceValue
static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz,
jint ident,
jshort density,
jobject outValue,
jboolean resolve)
{
//通过当前Java层AssetManager对象 mObject拿到对应Native AssetManager
AssetManager* am = assetManagerForJavaObject(env, clazz);
//拿到当前ResTable:持有add了一堆Path的Asset
const ResTable& res(am->getResources());
Res_value value;
ResTable_config config;
uint32_t typeSpecFlags;
//通过id查看对应的block--->ResTable
ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config);
//...
uint32_t ref = ident;
if (resolve) {
block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config);
}
//将值赋值给outValue
if (block >= 0) {
return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config);
}
//返回给java层
return static_cast(block);
}
/*
* 还记得之前Native AssetManager初始化时通过解析Resources.arsc生
* 的ResTable对象嘛,它持有解析resources.arsc后的ResChunk_header
* 等数据
*/
//----------------------ResourceTypes.cpp--------------
//查找资源, 先通过PackageId,找到对应PackageGroup,然后通
// typeId,找到type数组,然后在其中找entry
ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag, uint16_t density,
uint32_t* outSpecFlags, ResTable_config* outConfig) const
{
//packageId: 0x7f, typeId 03, entryId:0000
const ssize_t p = getResourcePackageIndex(resID);
const int t = Res_GETTYPE(resID);
const int e = Res_GETENTRY(resID);
//通过packageId对应的PackageGroup ==> 注意mPackageGroup的构造
const PackageGroup* const grp = mPackageGroups[p];
Entry entry;
status_t err = getEntry(grp, t, e, &desiredConfig, &entry);
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;
}
//得到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);
if (grp->dynamicRefTable.lookupResourceValue(outValue) != NO_ERROR) {
return BAD_VALUE;
}
if (outSpecFlags != NULL) {
*outSpecFlags = entry.specFlags;
}
if (outConfig != NULL) {
*outConfig = entry.config;
}
//package header index
return entry.package->header->index;
}
综上,整个访问机制,就是通过0xPPTTEEEE
的id去Native层通过其PackageId, TypeId, EntryId最终得到值得过程。
上述流程一张图就可以表示清楚:
在打包流程和访问机制中都是着重梳理流程和源码思路,未过多涉及细节,这里尤其resources.arsc文件是比较复杂和重要的,限于篇幅,这两篇中没有深入介绍,我们在后续第四篇《资源的插件化和热修复》中再详细介绍resources.arsc的文件格式。