android res加載

轉載請註明出處:http://blog.csdn.net/singwhatiwanna/article/details/23387079 (來自singwhatiwanna的csdn博客)

前言

我們知道,在activity內部訪問資源(字符串,圖片等)是很簡單的,只要getResources然後就可以得到Resources對象,有了Resources對象就可以訪問各種資源了,這很簡單,不過本文不是介紹這個的,本文主要介紹在這套邏輯之下的資源加載機制

資源加載機制

很明確,不同的Context得到的都是同一份資源。這是很好理解的,請看下面的分析
得到資源的方式爲context.getResources,而真正的實現位於ContextImpl中的getResources方法,在ContextImpl中有一個成員 private Resources mResources,它就是getResources方法返回的結果,mResources的賦值代碼爲:
mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(),
Display.DEFAULT_DISPLAY, null, compatInfo, activityToken);
下面看一下ResourcesManager的getTopLevelResources方法,這個方法的思想是這樣的:在ResourcesManager中,所有的資源對象都被存儲在ArrayMap中,首先根據當前的請求參數去查找資源,如果找到了就返回,否則就創建一個資源對象放到ArrayMap中。有一點需要說明的是爲什麼會有多個資源對象,原因很簡單,因爲res下可能存在多個適配不同設備、不同分辨率、不同系統版本的目錄,按照android系統的設計,不同設備在訪問同一個應用的時候訪問的資源可以不同,比如drawable-hdpi和drawable-xhdpi就是典型的例子。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
publicResources getTopLevelResources(String resDir, intdisplayId,
        Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
    finalfloat scale = compatInfo.applicationScale;
    ResourcesKey key = newResourcesKey(resDir, displayId, overrideConfiguration, scale,
            token);
    Resources r;
    synchronized(this) {
        // Resources is app scale dependent.
        if(false) {
            Slog.w(TAG,"getTopLevelResources: " + resDir + " / " + scale);
        }
        WeakReference<resources> wr = mActiveResources.get(key);
        r = wr != null? wr.get() : null;
        //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
        if(r != null&& r.getAssets().isUpToDate()) {
            if(false) {
                Slog.w(TAG,"Returning cached resources " + r + " " + resDir
                        +": appScale=" + r.getCompatibilityInfo().applicationScale);
            }
            returnr;
        }
    }
 
    //if (r != null) {
    //    Slog.w(TAG, "Throwing away out-of-date resources!!!! "
    //            + r + " " + resDir);
    //}
 
    AssetManager assets = newAssetManager();
    if(assets.addAssetPath(resDir) == 0) {
        returnnull;
    }
 
    //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
    DisplayMetrics dm = getDisplayMetricsLocked(displayId);
    Configuration config;
    booleanisDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
    finalboolean hasOverrideConfig = key.hasOverrideConfiguration();
    if(!isDefaultDisplay || hasOverrideConfig) {
        config = newConfiguration(getConfiguration());
        if(!isDefaultDisplay) {
            applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);
        }
        if(hasOverrideConfig) {
            config.updateFrom(key.mOverrideConfiguration);
        }
    }else{
        config = getConfiguration();
    }
    r = newResources(assets, dm, config, compatInfo, token);
    if(false) {
        Slog.i(TAG,"Created app resources " + resDir + " " + r + ": "
                + r.getConfiguration() + " appScale="
                + r.getCompatibilityInfo().applicationScale);
    }
 
    synchronized(this) {
        WeakReference<resources> wr = mActiveResources.get(key);
        Resources existing = wr != null? wr.get() : null;
        if(existing != null&& existing.getAssets().isUpToDate()) {
            // Someone else already created the resources while we were
            // unlocked; go ahead and use theirs.
            r.getAssets().close();
            returnexisting;
        }
 
        // XXX need to remove entries when weak references go away
        mActiveResources.put(key,newWeakReference<resources>(r));
        returnr;
    }
}</resources></resources></resources>
根據上述代碼中資源的請求機制,再加上ResourcesManager採用單例模式,這樣就保證了不同的ContextImpl訪問的是同一套資源,注意,這裏說的同一套資源未必是同一個資源,因爲資源可能位於不同的目錄,但它一定是我們的應用的資源,或許這樣來描述更準確,在設備參數和顯示參數不變的情況下,不同的ContextImpl訪問到的是同一份資源。設備參數不變是指手機的屏幕和android版本不變,顯示參數不變是指手機的分辨率和橫豎屏狀態。也就是說,儘管Application、Activity、Service都有自己的ContextImpl,並且每個ContextImpl都有自己的mResources成員,但是由於它們的mResources成員都來自於唯一的ResourcesManager實例,所以它們看似不同的mResources其實都指向的是同一塊內存(C語言的概念),因此,它們的mResources都是同一個對象(在設備參數和顯示參數不變的情況下)。在橫豎屏切換的情況下且應用中爲橫豎屏狀態提供了不同的資源,處在橫屏狀態下的ContextImpl和處在豎屏狀態下的ContextImpl訪問的資源不是同一個資源對象。

代碼:單例模式的ResourcesManager類
?
1
2
3
4
5
6
7
8
publicstatic ResourcesManager getInstance() {
    synchronized(ResourcesManager.class) {
        if(sResourcesManager == null) {
            sResourcesManager = newResourcesManager();
        }
        returnsResourcesManager;
    }
}

Resources對象的創建過程

通過閱讀Resources類的源碼可以知道,Resources對資源的訪問實際上是通過AssetManager來實現的,那麼如何創建一個Resources對象呢,有人會問,我爲什麼要去創建一個Resources對象呢,直接getResources不就可以了嗎?我要說的是在某些特殊情況下你的確需要去創建一個資源對象,比如動態加載apk。很簡單,首先看一下它的幾個構造方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
 * Create a new Resources object on top of an existing set of assets in an
 * AssetManager.
 *
 * @param assets Previously created AssetManager.
 * @param metrics Current display metrics to consider when
 *                selecting/computing resource values.
 * @param config Desired device configuration to consider when
 *               selecting/computing resource values (optional).
 */
publicResources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
    this(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
}
 
/**
 * Creates a new Resources object with CompatibilityInfo.
 *
 * @param assets Previously created AssetManager.
 * @param metrics Current display metrics to consider when
 *                selecting/computing resource values.
 * @param config Desired device configuration to consider when
 *               selecting/computing resource values (optional).
 * @param compatInfo this resource's compatibility info. Must not be null.
 * @param token The Activity token for determining stack affiliation. Usually null.
 * @hide
 */
publicResources(AssetManager assets, DisplayMetrics metrics, Configuration config,
        CompatibilityInfo compatInfo, IBinder token) {
    mAssets = assets;
    mMetrics.setToDefaults();
    if(compatInfo != null) {
        mCompatibilityInfo = compatInfo;
    }
    mToken = newWeakReference<ibinder>(token);
    updateConfiguration(config, metrics);
    assets.ensureStringBlocks();
}</ibinder>
除了這兩個構造方法還有一個私有的無參方法,由於是私有的,所以沒法訪問。上面兩個構造方法,從簡單起見,我們應該採用第一個

public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config)

它接受3個參數,第一個是AssetManager,後面兩個是和設備相關的配置參數,我們可以直接用當前應用的配置就好,所以,問題的關鍵在於如何創建AssetManager,下面請看分析,爲了創建一個我們自己的AssetManager,我們先去看看系統是怎麼創建的。還記得getResources的底層實現嗎,在ResourcesManager的getTopLevelResources方法中有這麼兩句:

?
1
2
3
4
AssetManager assets = newAssetManager();
if(assets.addAssetPath(resDir) == 0) {
    returnnull;
}

這兩句就是創建一個AssetManager對象,後面會用這個對象來創建Resources對象,ok,AssetManager就是這麼創建的,assets.addAssetPath(resDir)這句話的意思是把資源目錄裏的資源都加載到AssetManager對象中,具體的實現在jni中,大家感興趣自己去了解下。而資源目錄就是我們的res目錄,當然resDir可以是一個目錄也可以是一個zip文件。有沒有想過,如果我們把一個未安裝的apk的路徑傳給這個方法,那麼apk中的資源是不是就被加載到AssetManager對象裏面了呢?事實證明,的確是這樣,具體情況可以參見Android apk動態加載機制的研究(二):資源加載和activity生命週期管理這篇文章。addAssetPath方法的定義如下,注意到它的註釋裏面有一個{@hide}關鍵字,這意味着即使它是public的,但是外界仍然無法訪問它,因爲android sdk導出的時候會自動忽略隱藏的api,因此只能通過反射來調用。

?
1
2
3
4
5
6
7
8
9
10
/**
 * 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}
 */
publicfinal int addAssetPath(String path) {
    intres = addAssetPathNative(path);
    returnres;
}

有了AssetManager對象後,我們就可以創建自己的Resources對象了,代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
try{
    AssetManager assetManager = AssetManager.class.newInstance();
    Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
    addAssetPath.invoke(assetManager, mDexPath);
    mAssetManager = assetManager;
}catch(Exception e) {
    e.printStackTrace();
}
Resources currentRes = this.getResources();
mResources = newResources(mAssetManager, currentRes.getDisplayMetrics(),
        currentRes.getConfiguration());
有了Resources對象,我們就可以通過Resources對象來訪問裏面的各種資源了,通過這種方法,我們可以完成一些特殊的功能,比如換膚、換語言包、動態加載apk等,歡迎大家交流。
發佈了55 篇原創文章 · 獲贊 8 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章