转载请注明出处: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());
|