Android AssetManager的創(chuàng)建
本文基于Android 6.0源碼分析
AssetManager的類圖
我們以一個(gè)"Hello World" APK(包名:com.jackyperf.assetmanagerdemo)為例梧疲。
-
ContextImpl:為Activity以及其他應(yīng)用組件提供基礎(chǔ)上下文,常用的Context API的實(shí)現(xiàn)
都在這里- mPackageInfo:ContextImpl關(guān)聯(lián)的組件所在Package信息
- mResourcesManager:單列對象再来,管理應(yīng)用內(nèi)部多個(gè)Resources包
-
LoadedApk:管理一個(gè)加載的apk包
- mResources:apk包對應(yīng)的Resources對象
- mResDir:資源存放路徑
/data/app/com.jackyperf.assetmanagerdemo-1/base.apk
-
ResourcesManager
- mActiveResources:應(yīng)用使用的Resources包的緩存
-
Resources:提供高級別的訪問應(yīng)用的資源的API
- mSystem:系統(tǒng)Resources對象
- mAssets:AssetManager對象
-
AssetManager:提供低級別的訪問應(yīng)用資源的API
- sSystem:系統(tǒng)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的創(chuàng)建流程
我們從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中首先通過反射創(chuàng)建Activity對象
- 調(diào)用LoadedApk對象的makeApplication(),獲取Activity所屬應(yīng)用的Application對象
- 為Activity創(chuàng)建Base Context也就是ContextImpl對象蕉朵,AssetManager對象就是
在這里創(chuàng)建的耿战。
接下來巴帮,我們分析createBaseContextForActivity()的實(shí)現(xiàn)晶密。
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
- 調(diào)用ContextImpl的靜態(tài)方法createActivityContext()創(chuàng)建ContextImpl對象,然后將Activity
保存到ContextImpl的mOuterContext中鱼喉。
createActivityContext()的實(shí)現(xiàn)計(jì)較簡單秀鞭,就是調(diào)用ContextImpl的構(gòu)造函數(shù),在
ContextImpl的構(gòu)造函數(shù)函數(shù)中會調(diào)用LoadedApk對象的getResouces()創(chuàng)建Resources對象扛禽,注意LoadedApk有
兩個(gè)構(gòu)造函數(shù)锋边,一個(gè)用于系統(tǒng)包,一個(gè)用于普通的應(yīng)用包编曼。在getResources()中最終調(diào)用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是一個(gè)ArrayMap用于存放應(yīng)用內(nèi)部ResourcesKey到Resources的映射掐场,ResoucesKey實(shí)際上是
資源路徑往扔、應(yīng)用縮放、userId等信息封裝的key熊户。 - 首先根據(jù)應(yīng)用資源路徑萍膛、縮放、userId等信息創(chuàng)建ResourcesKey嚷堡,在mActiveResources中查找對應(yīng)的Resources對象
如果已經(jīng)創(chuàng)建蝗罗,并且Resources內(nèi)部AssetManager的狀態(tài)與資源文件一致,直接返回。 - 否則串塑,重建AssetManager對象沼琉,將應(yīng)用資源路徑添加AssetManager
- 利用新的AssetManager、配置拟赊、兼容信息創(chuàng)建Resources對象
- 最后將新創(chuàng)建的Resources對象以及ResourcesKey保存到mActiveResources刺桃,此時(shí)要考慮
并發(fā)的問題,如果在locked之前已經(jīng)有其他線程創(chuàng)建好了Resources對象吸祟,并且狀態(tài)是最新的瑟慈,直接
返回已有的Resources對象。
下面重點(diǎn)分析AssetManager以及Resources的構(gòu)造函數(shù)屋匕,先看AssetManager的構(gòu)造函數(shù)葛碧。
public AssetManager() {
synchronized (this) {
...
init(false);
ensureSystemAssets();
}
}
- 調(diào)用native方法init()來創(chuàng)建并初始化Native層的AssetManager對象。
- 調(diào)用ensureSystemAssets()來確保系統(tǒng)資源管理對象已經(jīng)創(chuàng)建并初始化过吻。
應(yīng)用內(nèi)部應(yīng)該使用Resources的getAssets()獲取AssetManager對象进泼。
下面分析Native層的AssetManager的創(chuàng)建以及初始化。
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()中首先創(chuàng)建Native層AssetManager對象
- 調(diào)用AssetManager對象的addDefaultAssets添加系統(tǒng)資源
- 最后將Native層的AssetManager對象地址保存在相應(yīng)的Java對象的mObject中
下面分析addDefaultAssets()的實(shí)現(xiàn)纤虽。
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()中首先創(chuàng)建系統(tǒng)資源路徑乳绕,一般ANDROID_ROOT環(huán)境變量為"/system",kSystemAssets為
"framework/framework-res.apk"逼纸,所以系統(tǒng)資源路徑為"/system/framework/framework-res.apk"洋措。然后調(diào)用addAssetPath()。 - 在addAssetPath()中杰刽,首先檢查mAssetPaths中是否已經(jīng)包含了當(dāng)前資源路徑對應(yīng)的asset_path對象菠发,如果已經(jīng)存在,返回asset_path在
mAssetPaths中的索引值+1,所以*cookie的值從1開始贺嫂。 - 否則滓鸠,將asset_path添加到mAssetPaths中,同時(shí)給*cookie賦值第喳。
- 如果資源表不為NULL糜俗,將asset_path添加到資源表。
最后我們再來分析下Resources的構(gòu)造函數(shù)曲饱。
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config,
CompatibilityInfo compatInfo) {
mAssets = assets;
...
updateConfiguration(config, metrics);
assets.ensureStringBlocks();
}
- 在Reosurces的構(gòu)造函數(shù)中吩跋,首先將之前創(chuàng)建的AssetManager對象保存到mAssets中。
- 調(diào)用updateConfiguration()更新設(shè)備配置信息,如設(shè)備屏幕信息渔工、國家地區(qū)網(wǎng)絡(luò)信息以及鍵盤配置信息等,最終會將這些信息
保存到Native層的AssetManager對象中去桥温。 - 調(diào)用ensureStringBlocks將系統(tǒng)資源表以及應(yīng)用資源表中的字符串資源池地址保存到AssetManager的mStringBlocks中引矩。