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