轉載請註明出處: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
|
public Resources
getTopLevelResources(String resDir, int displayId, Configuration
overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) { final float
scale = compatInfo.applicationScale; ResourcesKey
key = new ResourcesKey(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); } return r; } } //if
(r != null) { //
Slog.w(TAG, "Throwing away out-of-date resources!!!! " //
+ r + " " + resDir); //} AssetManager
assets = new AssetManager(); if (assets.addAssetPath(resDir)
== 0 )
{ return null ; } //Slog.i(TAG,
"Resource: key=" + key + ", display metrics=" + metrics); 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(); } r
= new Resources(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(); return existing; } //
XXX need to remove entries when weak references go away mActiveResources.put(key, new WeakReference<resources>(r)); return r; } }</resources></resources></resources> |
代碼:單例模式的ResourcesManager類
1
2
3
4
5
6
7
8
|
public static
ResourcesManager getInstance() { synchronized (ResourcesManager. class )
{ if (sResourcesManager
== null )
{ sResourcesManager
= new ResourcesManager(); } return sResourcesManager; } } |
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). */ public Resources(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 */ public Resources(AssetManager
assets, DisplayMetrics metrics, Configuration config, CompatibilityInfo
compatInfo, IBinder token) { mAssets
= assets; mMetrics.setToDefaults(); if (compatInfo
!= null )
{ mCompatibilityInfo
= compatInfo; } mToken
= new WeakReference<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 = new AssetManager(); if (assets.addAssetPath(resDir)
== 0 )
{ return null ; } |
這兩句就是創建一個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} */ public final
int
addAssetPath(String path) { int res
= addAssetPathNative(path); return res; } |
有了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
= new Resources(mAssetManager,
currentRes.getDisplayMetrics(), currentRes.getConfiguration()); |