获取资源的方式
先通过Context.getResources();
获取Resources对象,有了Resources对象就可以访问各种资源了。
资源加载机制
通过Context获取Resources,实际是通过ContextImpl的getResources()方法;ContextImpl内部有一个成员mResources,它就是getResources()方法返回的结果;
Context.getResources();
ContextImpl
private Resources mResources
mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(), Display.DEFAULT_DISPLAY, null, compatInfo, activityToken);
ResourcesManager.getTopLevelResources()
思想:在ResourcesManager中,所有的资源对象都被存储在ArrayMap中,首先根据当前的请求参数去查找资源,如果找到了就返回,否则就创建一个资源对象放到ArrayMap中。
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;
}
}
为什么会有多个资源对象?
因为res下可能存在多个适配不同设备、不同分辨率、不同系统版本的目录,按照Android系统的设计,不同设备在访问同一个应用的时候访问的资源可以不同,比如drawable-hdpi和drawable-xhdpi就是典型的例子。
在同一应用中不同的ContextImpl获取到的是同一套资源
根据上述代码中(ResourcesManager.getTopLevelResources())资源的请求机制,再加上ResourcesManager采用单例模式,这样就保证了不同的ContextImpl访问的是同一套资源;尽管Application、Activity、Service都有自己的ContextImpl,并且每个ContextImpl都有自己的mResources成员,但是由于它们的mResources成员都来自于唯一的ResourcesManager实例;所以它们看似不同的mResources其实都指向的是同一块内存
public static ResourcesManager getInstance() {
synchronized (ResourcesManager.class) {
if (sResourcesManager == null) {
sResourcesManager = new ResourcesManager();
}
return sResourcesManager;
}
}
Resources对象的创建过程
构造方法:
简单起见,我们应该采用第一个
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config)
它接受3个参数,第一个是AssetManager,后面两个是和设备相关的配置参数
方法一
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
this(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
}
方法二
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();
}
所以创建Resources的关键就是创建AssetManager
系统创建AssetManager的方法;
ResourcesManager.getTopLevelResources()
AssetManager assets = new AssetManager();
if (assets.addAssetPath(resDir) == 0) {
return null;
}
assets.addAssetPath(resDir)这句话的意思是把资源目录里的资源都加载到AssetManager对象中;
但是addAssetPath方法是{@hide},这意味着即使它是public的,但是外界仍然无法访问它,因为android sdk导出的时候会自动忽略隐藏的api;
public final int addAssetPath(String path) {
int res = addAssetPathNative(path);
return res;
}
所以我们要通过反射才能调用 addAssetPath方法;
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());