转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/23387079 (来自singwhatiwanna的csdn博客)
java
咱们知道,在activity内部访问资源(字符串,图片等)是很简单的,只要getResources而后就能够获得Resources对象,有了Resources对象就能够访问各类资源了,这很简单,不过本文不是介绍这个的,本文主要介绍在这套逻辑之下的资源加载机制android
很明确,不一样的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就是典型的例子。
api
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访问的资源不是同一个资源对象。
public static ResourcesManager getInstance() { synchronized (ResourcesManager.class) { if (sResourcesManager == null) { sResourcesManager = new ResourcesManager(); } return sResourcesManager; } }
经过阅读Resources类的源码能够知道,Resources对资源的访问其实是经过AssetManager来实现的,那么如何建立一个Resources对象呢,有人会问,我为何要去建立一个Resources对象呢,直接getResources不就能够了吗?我要说的是在某些特殊状况下你的确须要去建立一个资源对象,好比动态加载apk。很简单,首先看一下它的几个构造方法:cookie
/** * 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)app
它接受3个参数,第一个是AssetManager,后面两个是和设备相关的配置参数,咱们能够直接用当前应用的配置就好,因此,问题的关键在于如何建立AssetManager,下面请看分析,为了建立一个咱们本身的AssetManager,咱们先去看看系统是怎么建立的。还记得getResources的底层实现吗,在ResourcesManager的getTopLevelResources方法中有这么两句:ide
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,所以只能经过反射来调用。this
/** * 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对象了,代码以下:.net
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等,欢迎你们交流。