Android AssetManager的創建
本文基于Android 6.0源碼分析
AssetManager的類圖
我們以一個"Hello World" APK(包名:com.jackyperf.assetmanagerdemo)為例。

-
ContextImpl:為Activity以及其他應用組件提供基礎上下文,常用的Context API的實現
都在這里- mPackageInfo:ContextImpl關聯的組件所在Package信息
- mResourcesManager:單列對象,管理應用內部多個Resources包
-
LoadedApk:管理一個加載的apk包
- mResources:apk包對應的Resources對象
- mResDir:資源存放路徑
/data/app/com.jackyperf.assetmanagerdemo-1/base.apk
-
ResourcesManager
- mActiveResources:應用使用的Resources包的緩存
-
Resources:提供高級別的訪問應用的資源的API
- mSystem:系統Resources對象
- mAssets:AssetManager對象
-
AssetManager:提供低級別的訪問應用資源的API
- sSystem:系統AssetMananger對象
- mObject:指向Native層AssetManager
-
AssetManager(Native層)
Every application that uses assets needs one instance of this. A
single instance may be shared across multiple threads, and a single
thread may have more than one instance (the latter is discouraged).The purpose of the AssetManager is to create Asset objects. To do
this efficiently it may cache information about the locations of
files it has seen. This can be controlled with the "cacheMode"
argument.The asset hierarchy may be examined like a filesystem, using
AssetDir objects to peruse a single directory.- mAssetPath:
- mResources:代表APK中的資源表
- mConfig:
AssetManager的創建流程

我們從ActivityThread的performLaunchActivity開始分析。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
...
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
...
Context appContext = createBaseContextForActivity(r, activity, r.displayId);
...
}
代碼位于/android/frameworks/base/core/java/android/app/ActivityThread.java
- 在performLaunchActivity中首先通過反射創建Activity對象
- 調用LoadedApk對象的makeApplication(),獲取Activity所屬應用的Application對象
- 為Activity創建Base Context也就是ContextImpl對象,AssetManager對象就是
在這里創建的。
接下來,我們分析createBaseContextForActivity()的實現。
private Context createBaseContextForActivity(ActivityClientRecord r,
final Activity activity, int activityDisplayId) {
...
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, displayId, r.overrideConfig/* { Multi-Window */, r.token/* Multi-Window } */);
appContext.setOuterContext(activity);
...
}
代碼位于/android/frameworks/base/core/java/android/app/ActivityThread.java
- 調用ContextImpl的靜態方法createActivityContext()創建ContextImpl對象,然后將Activity
保存到ContextImpl的mOuterContext中。
createActivityContext()的實現計較簡單,就是調用ContextImpl的構造函數,在
ContextImpl的構造函數函數中會調用LoadedApk對象的getResouces()創建Resources對象,注意LoadedApk有
兩個構造函數,一個用于系統包,一個用于普通的應用包。在getResources()中最終調用ResourcesManager的getTopLevelResources()
。
接下來分析ResourcesManager的getTopLevelResources()。
private final ArrayMap<ResourcesKey, WeakReference<Resources> > mActiveResources =
new ArrayMap<>();
...
/**
* Creates the top level Resources for applications with the given compatibility info.
*
* @param resDir the resource directory.
* @param splitResDirs split resource directories.
* @param overlayDirs the resource overlay directories.
* @param libDirs the shared library resource dirs this app references.
* @param displayId display Id.
* @param overrideConfiguration override configurations.
* @param compatInfo the compatibility info. Must not be null.
*/
Resources getTopLevelResources(String resDir, String[] splitResDirs,
String[] overlayDirs, String[] libDirs, int displayId,
Configuration overrideConfiguration, CompatibilityInfo compatInfo) {
...
ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfigCopy, scale);
Resources r;
synchronized (this) {
// Resources is app scale dependent.
if (DEBUG) Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
WeakReference<Resources> wr = mActiveResources.get(key);
r = wr != null ? wr.get() : null;
//if (r != null) Log.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
if (r != null && r.getAssets().isUpToDate()) {
if (DEBUG) Slog.w(TAG, "Returning cached resources " + r + " " + resDir
+ ": appScale=" + r.getCompatibilityInfo().applicationScale
+ " key=" + key + " overrideConfig=" + overrideConfiguration);
return r;
}
}
...
AssetManager assets = new AssetManager();
if (resDir != null) {
if (assets.addAssetPath(resDir) == 0) {
return null;
}
}
...
r = new Resources(assets, dm, config, compatInfo);
...
synchronized (this) {
WeakReference<Resources> wr = mActiveResources.get(key);
Resources existing = wr != null ? wr.get() : null;
if (existing != null && existing.getAssets().isUpToDate()) {
r.getAssets().close();
return existing;
}
mActiveResources.put(key, new WeakReference<>(r));
return r;
}
}
代碼位于/frameworks/base/core/java/android/app/ResourcesManager.java
- mActiveResources是一個ArrayMap用于存放應用內部ResourcesKey到Resources的映射,ResoucesKey實際上是
資源路徑、應用縮放、userId等信息封裝的key。 - 首先根據應用資源路徑、縮放、userId等信息創建ResourcesKey,在mActiveResources中查找對應的Resources對象
如果已經創建,并且Resources內部AssetManager的狀態與資源文件一致,直接返回。 - 否則,重建AssetManager對象,將應用資源路徑添加AssetManager
- 利用新的AssetManager、配置、兼容信息創建Resources對象
- 最后將新創建的Resources對象以及ResourcesKey保存到mActiveResources,此時要考慮
并發的問題,如果在locked之前已經有其他線程創建好了Resources對象,并且狀態是最新的,直接
返回已有的Resources對象。
下面重點分析AssetManager以及Resources的構造函數,先看AssetManager的構造函數。
public AssetManager() {
synchronized (this) {
...
init(false);
ensureSystemAssets();
}
}
- 調用native方法init()來創建并初始化Native層的AssetManager對象。
- 調用ensureSystemAssets()來確保系統資源管理對象已經創建并初始化。
應用內部應該使用Resources的getAssets()獲取AssetManager對象。
下面分析Native層的AssetManager的創建以及初始化。
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
{
...
AssetManager* am = new AssetManager();
...
am->addDefaultAssets();
env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am));
}
代碼位于/frameworks/base/core/jni/android_util_AssetManager.cpp
- 在android_content_AssetManager_init()中首先創建Native層AssetManager對象
- 調用AssetManager對象的addDefaultAssets添加系統資源
- 最后將Native層的AssetManager對象地址保存在相應的Java對象的mObject中
下面分析addDefaultAssets()的實現。
bool AssetManager::addDefaultAssets()
{
const char* root = getenv("ANDROID_ROOT");
String8 path(root);
path.appendPath(kSystemAssets);
return addAssetPath(path, NULL);
}
bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
{
...
asset_path ap;
...
for (size_t i=0; i<mAssetPaths.size(); i++) {
if (mAssetPaths[i].path == ap.path) {
if (cookie) {
*cookie = static_cast<int32_t>(i+1);
}
return true;
}
}
...
mAssetPaths.add(ap);
// new paths are always added at the end
if (cookie) {
*cookie = static_cast<int32_t>(mAssetPaths.size());
}
...
if (mResources != NULL) {
appendPathToResTable(ap);
}
return true;
}
- 在addDefaultAssets()中首先創建系統資源路徑,一般ANDROID_ROOT環境變量為"/system",kSystemAssets為
"framework/framework-res.apk",所以系統資源路徑為"/system/framework/framework-res.apk"。然后調用addAssetPath()。 - 在addAssetPath()中,首先檢查mAssetPaths中是否已經包含了當前資源路徑對應的asset_path對象,如果已經存在,返回asset_path在
mAssetPaths中的索引值+1,所以*cookie的值從1開始。 - 否則,將asset_path添加到mAssetPaths中,同時給*cookie賦值。
- 如果資源表不為NULL,將asset_path添加到資源表。
最后我們再來分析下Resources的構造函數。
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config,
CompatibilityInfo compatInfo) {
mAssets = assets;
...
updateConfiguration(config, metrics);
assets.ensureStringBlocks();
}
- 在Reosurces的構造函數中,首先將之前創建的AssetManager對象保存到mAssets中。
- 調用updateConfiguration()更新設備配置信息,如設備屏幕信息、國家地區網絡信息以及鍵盤配置信息等,最終會將這些信息
保存到Native層的AssetManager對象中去。 - 調用ensureStringBlocks將系統資源表以及應用資源表中的字符串資源池地址保存到AssetManager的mStringBlocks中。