Android源代码分析-资源载入机制
转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/23387079 (来自singwhatiwanna的csdn博客)
前言
我们知道,在activity内部訪问资源(字符串,图片等)是非常easy的,仅仅要getResources然后就能够得到Resources对象,有了Resources对象就能够訪问各种资源了,这非常easy,只是本文不是介绍这个的,本文主要介绍在这套逻辑之下的资源载入机制
资源载入机制
非常明白,不同的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中。有一点须要说明的是为什么会有多个资源对象,原因非常easy,由于res下可能存在多个适配不同设备、不同分辨率、不同系统版本号的文件夹,依照android系统的设计,不同设备在訪问同一个应用的时候訪问的资源能够不同,比方drawable-hdpi和drawable-xhdpi就是典型的样例。
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;
}
}依据上述代码中资源的请求机制,再加上ResourcesManager採用单例模式,这样就保证了不同的ContextImpl訪问的是同一套资源,注意,这里说的同一套资源未必是同一个资源,由于资源可能位于不同的文件夹,但它一定是我们的应用的资源,也许这样来描写叙述更准确,在设备參数和显示參数不变的情况下,不同的ContextImpl訪问到的是同一份资源。设备參数不变是指手机的屏幕和android版本号不变,显示參数不变是指手机的分辨率和横竖屏状态。也就是说,虽然Application、Activity、Service都有自己的ContextImpl,而且每一个ContextImpl都有自己的mResources成员,可是由于它们的mResources成员都来自于唯一的ResourcesManager实例,所以它们看似不同的mResources事实上都指向的是同一块内存(C语言的概念),因此,它们的mResources都是同一个对象(在设备參数和显示參数不变的情况下)。在横竖屏切换的情况下且应用中为横竖屏状态提供了不同的资源,处在横屏状态下的ContextImpl和处在竖屏状态下的ContextImpl訪问的资源不是同一个资源对象。代码:单例模式的ResourcesManager类
public static ResourcesManager getInstance() {
synchronized (ResourcesManager.class) {
if (sResourcesManager == null) {
sResourcesManager = new ResourcesManager();
}
return sResourcesManager;
}
}Resources对象的创建过程
通过阅读Resources类的源代码能够知道,Resources对资源的訪问实际上是通过AssetManager来实现的,那么怎样创建一个Resources对象呢,有人会问,我为什么要去创建一个Resources对象呢,直接getResources不就能够了吗?我要说的是在某些特殊情况下你的确须要去创建一个资源对象,比方动态载入apk。非常easy,首先看一下它的几个构造方法:
/**
* 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();
}除了这两个构造方法另一个私有的无參方法,由于是私有的,所以没法訪问。上面两个构造方法,从简单起见,我们应该採用第一个public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config)
它接受3个參数,第一个是AssetManager,后面两个是和设备相关的配置參数,我们能够直接用当前应用的配置就好,所以,问题的关键在于怎样创建AssetManager,以下请看分析,为了创建一个我们自己的AssetManager,我们先去看看系统是怎么创建的。还记得getResources的底层实现吗,在ResourcesManager的getTopLevelResources方法中有这么两句:
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}keyword,这意味着即使它是public的,可是外界仍然无法訪问它,由于android sdk导出的时候会自己主动忽略隐藏的api,因此仅仅能通过反射来调用。
/**
* 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对象了,代码例如以下:
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());有了Resources对象,我们就能够通过Resources对象来訪问里面的各种资源了,通过这种方法,我们能够完毕一些特殊的功能,比方换肤、换语言包、动态载入apk等,欢迎大家交流。

浙公网安备 33010602011771号