获取资源的方式

先通过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 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 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(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(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());

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐