Android AssetManager

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中。

參考

  1. http://blog.csdn.net/luoshengyang/article/details/8791064
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內容