Android資源加載過程分析

涉及源碼(Android 4.4.2):
/frameworks/base/core/java/android/app/ResourcesManager.java
/frameworks/base/core/jni/android_util_AssetManager.cpp
/core/java/android/content/res/AssetManager.java
/frameworks/base/libs/androidfw/AssetManager.cpp

我們知道,如果我們希望使用或者得到某些資源粹排,我們通常的做法是使用Context的getXXX方法,例如:

public final Drawable getDrawable(int id) {
    return getResources().getDrawable(id, getTheme());
}

可以看到它首先需要獲取到Resources資源對象款筑,從資源對象中得到想要的資源。當(dāng)我們使用Context的getResources方法來獲取Resources對象的時(shí)候腾么,最終調(diào)用的是ResourcesManager的getTopLevelResources方法來獲取到對應(yīng)的Resources對象奈梳。

ResourcesManager采用的是單例模式,這就保證了所有的Context調(diào)用的都是同一個(gè)ResourcesManager對象的getTopLevelResources方法解虱,所以不同Context的getResources方法獲取是同一套資源對象攘须。

/frameworks/base/core/java/android/app/ResourcesManager.java

// 單例模式獲取ResourcesManager對象
public static ResourcesManager getInstance() {  
    synchronized (ResourcesManager.class) {  
        if (sResourcesManager == null) {  
            sResourcesManager = new ResourcesManager();  
        }  
        return sResourcesManager;  
    }  
} 

下面來看看ResourcesManager的getTopLevelResources方法

/frameworks/base/core/java/android/app/ResourcesManager.java

// 資源對象存放在ArrayMap的集合中,并且對象使用的是弱引用
final ArrayMap<ResourcesKey, WeakReference<Resources> > mActiveResources
            = new ArrayMap<ResourcesKey, WeakReference<Resources> >();
            
public Resources getTopLevelResources(String resDir, int displayId,
        Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
    final float scale = compatInfo.applicationScale;
    // 1殴泰、通過傳入的參數(shù)確定資源對象的key值
    ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale,
            token);
    Resources r;
    synchronized (this) {
        // 2阻课、通過key值,從mActiveResources Map集合中獲取對應(yīng)的資源對象
        WeakReference<Resources> wr = mActiveResources.get(key);
        r = wr != null ? wr.get() : null;
        // 3艰匙、如果資源對象不為空限煞,直接將其返回,否則執(zhí)行步驟4
        if (r != null && r.getAssets().isUpToDate()) {
            return r;
        }
    }

    // 4员凝、創(chuàng)建AssetManager對象署驻,并且將資源目錄(實(shí)際為apk文件路徑)加入資源路徑
    AssetManager assets = new AssetManager();
    if (assets.addAssetPath(resDir) == 0) {
        return null;
    }

    DisplayMetrics dm = getDisplayMetricsLocked(displayId);
    Configuration config;
    boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
    final boolean hasOverrideConfig = key.hasOverrideConfiguration();
    if (!isDefaultDisplay || hasOverrideConfig) {
        config = new Configuration(getConfiguration());
        if (!isDefaultDisplay) {
            applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);
        }
        if (hasOverrideConfig) {
            config.updateFrom(key.mOverrideConfiguration);
        }
    } else {
        config = getConfiguration();
    }
    
    // 5、創(chuàng)建資源對象
    r = new Resources(assets, dm, config, compatInfo, token);
    
    // 6健霹、如果mActiveResources Map集合沒有該資源對象旺上,則將其加入,并將資源對象返回
    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<Resources>(r));
        return r;
    }
}

從上面可以總結(jié)以下幾點(diǎn):
1糖埋、Resources對象使用了一個(gè)ArrayMap對象進(jìn)行緩存宣吱,因此表明其內(nèi)部可能包含多個(gè)Resources對象。
2瞳别、Resources對象中包含一個(gè)AssetManager對象征候,資源的添加工作是通過該對象的addAssetPath完成的。

下面就來看看具體的資源資源添加的過程

AssetManager的創(chuàng)建

/core/java/android/content/res/AssetManager.java

public AssetManager() {
    synchronized (this) {
        // 1祟敛、調(diào)用init初始化方法
        init();
        // 2疤坝、保證系統(tǒng)資源對象的存在
        ensureSystemAssets();
    }
}
1、調(diào)用init初始化方法

init方法是一個(gè)native方法馆铁,它最終調(diào)用的是android_util_AssetManager.cpp中的android_content_AssetManager_init方法跑揉,下面就進(jìn)入native層進(jìn)行操作了,前面的操作都是在java層處理的。

/frameworks/base/core/jni/android_util_AssetManager.cpp

static void android_content_AssetManager_init(JNIEnv* env, jobject clazz)
{   
    // 1历谍、創(chuàng)建一個(gè)AssetManager對象
    AssetManager* am = new AssetManager();
    // 2现拒、添加默認(rèn)的資源
    am->addDefaultAssets();
    // 3、將AssetManager對象(C++對象)的引用保存在Java層AssetManager對象的mObject中
    env->SetIntField(clazz, gAssetManagerOffsets.mObject, (jint)am);
}
(1) 添加系統(tǒng)默認(rèn)資源操作

/frameworks/base/libs/androidfw/AssetManager.cpp

static const char* kSystemAssets = "framework/framework-res.apk";  
bool AssetManager::addDefaultAssets()
{
    // 1望侈、得到系統(tǒng)目錄/system/
    const char* root = getenv("ANDROID_ROOT");
    String8 path(root);
    // 2具练、得到系統(tǒng)資源的完整路徑/system/framework/framework-res.apk
    path.appendPath(kSystemAssets);
    // 3、將系統(tǒng)資源添加到資源路徑
    return addAssetPath(path, NULL);
}

從上面可以看到甜无,默認(rèn)會(huì)將系統(tǒng)資源添加到資源路徑,這也是我們應(yīng)用可以訪問到系統(tǒng)資源的原因哥遮。

(2)添加AssetManager對象(C++對象)引用到Java層AssetManager對象的mObject上的操作

其實(shí)就是要弄清楚gAssetManagerOffsets.mObject的什么東西岂丘?

下面來看看android_util_AssetManager.cpp中register_android_content_AssetManager方法。

/frameworks/base/core/jni/android_util_AssetManager.cpp

int register_android_content_AssetManager(JNIEnv* env)
{
    // 1眠饮、獲取到j(luò)ava層的AssetManager類
    jclass assetManager = env->FindClass("android/content/res/AssetManager");
    // 2奥帘、獲取java層的AssetManager類的mObject字段,并將其保存在gAssetManagerOffsets.mObject中
    gAssetManagerOffsets.mObject
        = env->GetFieldID(assetManager, "mObject", "I");
    return AndroidRuntime::registerNativeMethods(env,
            "android/content/res/AssetManager", gAssetManagerMethods, NELEM(gAssetManagerMethods));
}

從上面就可以知道仪召,gAssetManagerOffsets.mObject對應(yīng)就是java層的AssetManager類的mObject字段寨蹋。

2、保證系統(tǒng)資源對象的存在

/core/java/android/content/res/AssetManager.java

private static void ensureSystemAssets() {  
    synchronized (sSync) {  
        if (sSystem == null) {  
            AssetManager system = new AssetManager(true);  
            system.makeStringBlocks(false);  
            sSystem = system;  
        }  
    }  
} 

sSystem是一個(gè)靜態(tài)的AssetManager對象扔茅,在Zygote啟動(dòng)時(shí)已經(jīng)賦值了已旧,主要就是初次啟動(dòng)的時(shí)候會(huì)執(zhí)行,供系統(tǒng)使用召娜,上面創(chuàng)建AssetManager對象运褪,創(chuàng)建過程跟前面相同。

上面類之間的關(guān)系如下圖所示:

resourceload.png

資源路徑的添加

從上圖可以看到玖瘸,在java層的AssetManager類中秸讹,addAssetPath方法會(huì)調(diào)用addAssetPathNative方法,addAssetPathNative方法是一個(gè)native方法雅倒,它對應(yīng)的就是android_util_AssetManager.cpp中的android_content_AssetManager_addAssetPath方法璃诀。

/frameworks/base/core/jni/android_util_AssetManager.cpp

static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz,
                                                       jstring path)
{
    ScopedUtfChars path8(env, path);
    if (path8.c_str() == NULL) {
        return 0;
    }
    // 1、得到C++對象AssetManager對象
    AssetManager* am = assetManagerForJavaObject(env, clazz);
    if (am == NULL) {
        return 0;
    }

    void* cookie;
    // 2蔑匣、執(zhí)行addAssetPath方法
    bool res = am->addAssetPath(String8(path8.c_str()), &cookie);

    return (res) ? (jint)cookie : 0;
}

assetManagerForJavaObject方法就是拿到前面存放在java層的AssetManager類的mObject字段的值劣欢,它就是一個(gè)AssetManager的C++對象引用

AssetManager* assetManagerForJavaObject(JNIEnv* env, jobject obj)
{
    AssetManager* am = (AssetManager*)env->GetIntField(obj, gAssetManagerOffsets.mObject);
    if (am != NULL) {
        return am;
    }
    jniThrowException(env, "java/lang/IllegalStateException", "AssetManager has been finalized!");
    return NULL;
}

/frameworks/base/libs/androidfw/AssetManager.cpp

// 資源路徑存放在一個(gè)Vector集合中
Vector<asset_path> mAssetPaths;

// 存放資源路徑的結(jié)構(gòu)體
struct asset_path
{
    String8 path;  // 路徑名
    FileType type; // 文件類型
    String8 idmap;
};
bool AssetManager::addAssetPath(const String8& path, void** cookie)
{
    AutoMutex _l(mLock);

    asset_path ap;

    // 1、構(gòu)造一個(gè)asset_path結(jié)構(gòu)體裁良,其實(shí)就是確定路徑名和文件類型氧秘,并保存在結(jié)構(gòu)體中
    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;
        }
    }

    // 2、如果該路徑已經(jīng)添加到集合中趴久,則直接設(shè)置cookie為(索引+1)并返回丸相,否則進(jìn)入步驟3
    for (size_t i=0; i<mAssetPaths.size(); i++) {
        if (mAssetPaths[i].path == ap.path) {
            if (cookie) {
                *cookie = (void*)(i+1);
            }
            return true;
        }
    }
    // 3、將路徑結(jié)構(gòu)體添加到集合中
    mAssetPaths.add(ap);

    // 4彼棍、將該路徑的集合size(索引+1)作為cookie的值
    if (cookie) {
        *cookie = (void*)mAssetPaths.size();
    }

    // 5灭忠、資源替換的過程膳算,這個(gè)暫不關(guān)注
    // (Java) package manager
    if (strncmp(path.string(), "/system/framework/", 18) == 0) {
        // 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)) == 0) {
            asset_path oap;
            oap.path = overlayPath;
            oap.type = ::getFileType(overlayPath.string());
            bool addOverlay = (oap.type == kFileTypeRegular); // only .apks supported as overlay
            if (addOverlay) {
                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;
}

如下圖所示:

resourcesadd.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市弛作,隨后出現(xiàn)的幾起案子涕蜂,更是在濱河造成了極大的恐慌,老刑警劉巖映琳,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件机隙,死亡現(xiàn)場離奇詭異,居然都是意外死亡萨西,警方通過查閱死者的電腦和手機(jī)有鹿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谎脯,“玉大人葱跋,你說我怎么就攤上這事≡此螅” “怎么了娱俺?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長废麻。 經(jīng)常有香客問我荠卷,道長,這世上最難降的妖魔是什么烛愧? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任僵朗,我火速辦了婚禮,結(jié)果婚禮上屑彻,老公的妹妹穿的比我還像新娘验庙。我一直安慰自己,他們只是感情好社牲,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布粪薛。 她就那樣靜靜地躺著,像睡著了一般搏恤。 火紅的嫁衣襯著肌膚如雪违寿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天熟空,我揣著相機(jī)與錄音藤巢,去河邊找鬼。 笑死息罗,一個(gè)胖子當(dāng)著我的面吹牛掂咒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼绍刮,長吁一口氣:“原來是場噩夢啊……” “哼温圆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起孩革,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤岁歉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后膝蜈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锅移,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年饱搏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了非剃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡窍帝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出诽偷,到底是詐尸還是另有隱情坤学,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布报慕,位于F島的核電站深浮,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏眠冈。R本人自食惡果不足惜飞苇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蜗顽。 院中可真熱鬧布卡,春花似錦、人聲如沸雇盖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽崔挖。三九已至贸街,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間狸相,已是汗流浹背薛匪。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留脓鹃,地道東北人逸尖。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親冷溶。 傳聞我的和親對象是個(gè)殘疾皇子渐白,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容