android程序中,不管是普通的drawable、anim、color、layout、value等类型的资源文件,还是raw类型的资源文件,或者asset类型的资源文件,所有的资源文件都是通过AssetManager管理的。其中asset资源文件,在打包apk时,不会有任何的改动,会被原封不动的打包进apk内,可以通过文件名和路径访问;而raw资源文件打包时也会原封不动的打包,只不过使用资源id访问;而其它类型的资源文件,在打包的时候,可能会被优化(bitmap),使用资源id访问。

以下,我们通过代码分析资源文件的管理。
1、Resources.java
位于目录/frameworks/base/core/java/android/content/res/Resources.java。Resources的代码逻辑很简单,基本就是直接调用了AssetManager的方法。需要注意的是有2个方法startPreloading()和finishPreloading()。这两个方法仅在ZygoteInit.java(Zygote进程)中调用,专门用于加载需要预加载的资源文件(在com.android.internal.R.array.preloaded_drawables中定义),并将这些资源文件存储在Resources的全局变量中。在应用进程启动(由Zygote进程fork)时,这些数据将从Zygote进程复制到应用进程中。
/**
* Start preloading of resource data using this Resources object. Only
* for use by the zygote process for loading common system resources.
* {@hide}
*/
public final void startPreloading() {
synchronized (mSync) {
if (sPreloaded) {
throw new IllegalStateException("Resources already preloaded");
}
sPreloaded = true;
mPreloading = true;
sPreloadedDensity = DisplayMetrics.DENSITY_DEVICE;
mConfiguration.densityDpi = sPreloadedDensity;
// 最终会调用AssetManager.setConfiguration(),已经缓存的数据可能会被删除
updateConfiguration(null, null);
}
}
/**
* Called by zygote when it is done preloading resources, to change back
* to normal Resources operation.
*/
public final void finishPreloading() {
if (mPreloading) {
mPreloading = false;
flushLayoutCache(); // 刷新数据,config不匹配的数据将被移除
}
}
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 {
// 调用了AssetManager的getResourceText()方法
CharSequence res = mAssets.getResourceText(id);
if (res != null) {
return res;
}
throw new NotFoundException("String resource ID #0x"
+ Integer.toHexString(id));
}
2、AssetManager.java
位于/frameworks/base/core/java/android/content/res/目录。其内部通过一个StringBlock类管理所有string的资源。对应的还有一个XmlBlock类,管理所有的xml资源文件。但是,XmlBlock却是直接在Resource.java中缓存的,并且只能缓存最近加载的4个,是出于内存占用的考虑么???。另外,AssetManager还有一个比较重要的方法int addAssetPath(String path),用于导入zip资源文件或者资源目录。例如,可以使用该方法导入apk自带资源文件,或者导入theme apk等。在Activity初始化的时候,就是用了该方法导入的apk自带资源文件。有兴趣的话,可以自行查阅ActivityThread.java文件(搜索getTopLevelResources()方法,该方法在ContextImpl.java对象init时调用)。
public AssetManager() {
synchronized (this) {
init(); // jni方法,创建native asset manager,并将其指针存放在mObject字段
ensureSystemAssets();
}
}
private static void ensureSystemAssets() { // 创建system asset manager
synchronized (sSync) {
if (sSystem == null) {
AssetManager system = new AssetManager(true);
// 初始化system asset manager的StringBlocks
system.makeStringBlocks(false);
sSystem = system;
}
}
}
// 初始化system string blocks 或者 复制system string blocks.
/*package*/ final void makeStringBlocks(boolean copyFromSystem) {
final int sysNum = copyFromSystem ? sSystem.mStringBlocks.length : ;
final int num = getStringBlockCount();
mStringBlocks = new StringBlock[num];
if (localLOGV) Log.v(TAG, "Making string blocks for " + this
+ ": " + num);
for (int i=; i<num; i++) {
if (i < sysNum) {
// 浅拷贝system string blocks
mStringBlocks[i] = sSystem.mStringBlocks[i];
} else {
// getNativeStringBlock()是一个jni方法
mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true);
}
}
}
/**
* Retrieve the string value associated with a particular resource
* identifier for the current configuration / skin.
*/
/*package*/ final CharSequence getResourceText(int ident) {
synchronized (this) {
TypedValue tmpValue = mValue;
// jni方法,猜想:多个StringBlocks,每个StringBlocks管理一种类型的资源
int block = loadResourceValue(ident, (short) , tmpValue, true);
if (block >= ) {
if (tmpValue.type == TypedValue.TYPE_STRING) {
return mStringBlocks[block].get(tmpValue.data);
}
return tmpValue.coerceToString();
}
}
return null;
}
/**
* Add an additional set of assets to the asset manager. This can be
* either a directory or ZIP file. Not for use by applications. Returns
* the cookie of the added asset, or 0 on failure.
* {@hide}
*/
public native final int addAssetPath(String path);
3、StringBlocks.java
位于/frameworks/base/core/java/android/content/res/目录。该对象主要是从底层的一个类似数组的结构体中使用index获取数据,并将数据缓存在java层的string数组或者SparseArray中。
// 该构造方法只在system asset manager创建的时候调用
// obj:对应底层的一个类似数组的结构体;useSparse:是否使用SparseArray缓存数据
StringBlock(int obj, boolean useSparse) {
mNative = obj;
mUseSparse = useSparse;
mOwnsNative = false;
}
public StringBlock(byte[] data, int offset, int size, boolean useSparse) {
// nativeCreate()实际就是使用data、offset、size做浅复制,并将复制之后的一个结构体
// 指针保存在mNative字段中
mNative = nativeCreate(data, offset, size);
mUseSparse = useSparse;
mOwnsNative = true;
}
public CharSequence get(int idx) {
synchronized (this) {
if (mStrings != null) {
CharSequence res = mStrings[idx];
if (res != null) {
return res;
}
} else if (mSparseStrings != null) {
CharSequence res = mSparseStrings.get(idx);
if (res != null) {
return res;
}
} else {
final int num = nativeGetSize(mNative);
if (mUseSparse && num > ) { // 大于250时使用SparseArray缓存
mSparseStrings = new SparseArray<CharSequence>();
} else {
mStrings = new CharSequence[num];
}
}
String str = nativeGetString(mNative, idx);
CharSequence res = str;
int[] style = nativeGetStyle(mNative, idx);
if (style != null) {
if (mStyleIDs == null) {
mStyleIDs = new StyleIDs();
}
// 。。。。省略,构建style ids
String styleTag = nativeGetString(mNative, styleId);
res = applyStyles(str, style, mStyleIDs);
}
// 缓存资源
if (mStrings != null) mStrings[idx] = res;
else mSparseStrings.put(idx, res);
return res;
4、android_util_AssetManager.cpp
位于/frameworks/base/core/jni/android_util_AssetManager.cpp。对接上层的AssetManager.java,逻辑不多,基本是直接调用到native AssetManager的方法。
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz)
{
AssetManager* am = new AssetManager();
if (am == NULL) {
jniThrowException(env, "java/lang/OutOfMemoryError", "");
return;
}
// 加载/framework/framework-res.apk资源
am->addDefaultAssets();
ALOGV("Created AssetManager %p for Java object %p\n", am, clazz);
// 将native asset manager对象的指针存入java asset manager的mObject字段
env->SetIntField(clazz, gAssetManagerOffsets.mObject, (jint)am);
}
// ident:资源id,density:0,outValue:TypedValue,resolve:true
static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz,
jint ident,
jshort density,
jobject outValue,
jboolean resolve)
{
// 从java层的AssetManager对象的mObject字段中获取native AssetManager对象。
// 该字段在java AssetManager对象init时设置
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return ;
}
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);
uint32_t ref = ident;
if (resolve) {
block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config);
}
// copyValue()将底层的Res_value对象的字段值复制到上层的TypedValue对象中
return block >= ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config) : block;
}
// 加载xml资源文件
static jint android_content_AssetManager_openXmlAssetNative(JNIEnv* env, jobject clazz,
jint cookie,
jstring fileName)
{
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return ;
}
ALOGV("openXmlAsset in %p (Java object %p)\n", am, clazz);
ScopedUtfChars fileName8(env, fileName);
if (fileName8.c_str() == NULL) {
return ;
}
Asset* a = cookie
? am->openNonAsset((void*)cookie, fileName8.c_str(), Asset::ACCESS_BUFFER)
: am->openNonAsset(fileName8.c_str(), Asset::ACCESS_BUFFER);
if (a == NULL) {
jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());
return ;
}
ResXMLTree* block = new ResXMLTree();
status_t err = block->setTo(a->getBuffer(true), a->getLength(), true);
a->close();
delete a;
if (err != NO_ERROR) {
jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file");
return ;
}
return (jint)block;
}
5、AssetManager.cpp
位于/frameworks/base/libs/androidfw/目录。
static const char* kDefaultLocale = "default";
static const char* kDefaultVendor = "default";
static const char* kAssetsRoot = "assets";
static const char* kAppZipName = NULL; //"classes.jar";
static const char* kSystemAssets = "framework/framework-res.apk";
static const char* kIdmapCacheDir = "resource-cache";
bool AssetManager::addDefaultAssets()
{
const char* root = getenv("ANDROID_ROOT"); // 默认是/system路径
LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");
String8 path(root);
path.appendPath(kSystemAssets); // 绝对路径/system/framework/framework-res.apk
return addAssetPath(path, NULL);
}
bool AssetManager::addAssetPath(const String8& path, void** cookie)
{
AutoMutex _l(mLock);
asset_path ap;
String8 realPath(path);
if (kAppZipName) {
realPath.appendPath(kAppZipName);
}
ap.type = ::getFileType(realPath.string());
if (ap.type == kFileTypeRegular) {
ap.path = realPath;
} else {
ap.path = path;
ap.type = ::getFileType(path.string());
if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) {
ALOGW("Asset path %s is neither a directory nor file (type=%d).",
path.string(), (int)ap.type);
return false;
}
}
// Skip if we have it already.
// 从已加载的资源中查找同一cookie的资源文件index,确保资源文件不会被重复加载
for (size_t i=; i<mAssetPaths.size(); i++) {
if (mAssetPaths[i].path == ap.path) {
if (cookie) {
*cookie = (void*)(i+);
}
return true;
}
}
ALOGV("In %p Asset %s path: %s", this,
ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());
mAssetPaths.add(ap);
// new paths are always added at the end
if (cookie) {
*cookie = (void*)mAssetPaths.size();
}
// add overlay packages for /system/framework; apps are handled by the
// (Java) package manager
if (strncmp(path.string(), "/system/framework/", ) == ) { // 匹配/system/framework
// When there is an environment variable for /vendor, this
// should be changed to something similar to how ANDROID_ROOT
// and ANDROID_DATA are used in this file.
String8 overlayPath("/vendor/overlay/framework/");
overlayPath.append(path.getPathLeaf());
if (TEMP_FAILURE_RETRY(access(overlayPath.string(), R_OK)) == ) {
asset_path oap;
oap.path = overlayPath;
oap.type = ::getFileType(overlayPath.string());
bool addOverlay = (oap.type == kFileTypeRegular); // only .apks supported as overlay
if (addOverlay) {
// id map文件路径:将overlayPath中的所有/转换为@
oap.idmap = idmapPathForPackagePath(overlayPath);
if (isIdmapStaleLocked(ap.path, oap.path, oap.idmap)) {
addOverlay = createIdmapFileLocked(ap.path, oap.path, oap.idmap);
}
}
if (addOverlay) {
mAssetPaths.add(oap);
} else {
ALOGW("failed to add overlay package %s\n", overlayPath.string());
}
}
}
return true;
}
// 加载xml资源文件
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);
pAsset = openAssetFromFileLocked(path, mode);
if (pAsset == NULL) {
/* try again, this time with ".gz" */
path.append(".gz");
pAsset = openAssetFromFileLocked(path, mode);
}
if (pAsset != NULL) {
//printf("FOUND NA '%s' on disk\n", fileName);
pAsset->setAssetSource(path);
}
/* look inside the zip file */
} else {
String8 path(fileName);
/* check the appropriate Zip file */
ZipFileRO* pZip;
ZipEntryRO entry;
pZip = getZipFileLocked(ap);
if (pZip != NULL) {
//printf("GOT zip, checking NA '%s'\n", (const char*) path);
entry = pZip->findEntryByName(path.string());
if (entry != NULL) {
//printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon);
pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
}
}
if (pAsset != NULL) {
/* create a "source" name, for debug/display */
pAsset->setAssetSource(
createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()), String8(""),
String8(fileName)));
}
}
return pAsset;
}
注意:AssetManager类的成员函数addAssetPath的最后一个工作是检查刚刚添加的Apk是否是在/system/framework/目录下面。如果是,就会在/vendor/overlay/framework/目录下找到一个同名的Apk文件,并且也会将该Apk文件添加到成员变量mAssetPaths(Vector)中去。这是一种资源覆盖机制,即用自定义的系统资源来覆盖系统默认的系统资源,以达到个性化系统界面的目的。如果要使用这种资源覆盖机制来自定义自己的系统资源,那么还需要提供一个idmap文件,用来说明它在/vendor/overlay/framework/目录下的Apk文件要覆盖系统的哪些资源,使用资源ID来描述。因此,这个idmap文件实际上就是一个资源ID映射文件。这个idmap文件最终保存在/data/resource-cache/目录下,并且按照一定的格式来命名。例如,如果apk为/vendor/overlay/framework/framework-res.apk,那么对应的idmap文件就会以名称为@[email protected]@[email protected]@idmap。
另外:资源解析之后,在底层是存放在一个ResTable中的。当系统的config改变时,最终会通知到ResTable。ResTable会更新缓存数据。
6、总结
- zygote进程初始化时,调用startPreloading()预加载了一部分系统资源
- 应用进程由zygote进程fork启动时,复制了这些资源文件,并保存在一个static system asset manager中的StringBlock数组中
- 应用进程在ActivityThread中调用getTopLevelResources()加载了自身apk的资源文件,存储在底层数据结构ResTable中
- 应用在查找string资源时,如果从StringBlock缓存中无法找到,则从底层ResTable中查找,之后在保存在StringBlock中
- 应用在查找xml资源时,如果从Resource.java的XmlBlock缓存中无法找到,则直接从文件中查找,最终返回XmlBlock的一个内部Parser