资源管理机制

来源:互联网 发布:手机sdr软件无线电 编辑:程序博客网 时间:2024/05/11 16:21

想要获取资源最关键的是把APK中的资源转为Resources对象。

然后根据不同的资源类型调用Resources对象的对应的方法,比如getString获得字符串资源

在一个Acitvity或者一个Service中,我们可以直接调用getResources()方法,就可以获得Reousrces对象。因为Acitivity或者Service本质上就是一个Context,getResources()方法是Context中的抽象的方法,而真正的实现是在ContextImpl类,所以调用的实际上是ContextImpl类的getResources()方法。

Context继承关系

Context 里面有一个mBase的对象,指向的是ContextImpl的实例


ContextImpl源码分析

在ContextImpl类中有一个成员

private Resources mResources,它就是getResources方法返回的结果,

mResources的赋值代码为:

// 第一个参数是apk的路径,第二个参数应该是屏幕分辨率。

mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(),
                    Display.DEFAULT_DISPLAY, null, compatInfo, activityToken);

这个方法的思想是这样的:

因为res下可能存在多个适配不同设备、不同分辨率、不同系统版本的目录,按照Android系统的设计,不同的参数在访问同一个应用的时候访问的资源可以不同,比如drawable-hdpi和drawable-xhdpi就是对应2个资源。所有的资源对象都被存储在mActiveResources中,mActiveResources对象内部保存了该应用程序所使用到的所有Resources对象,其类型为Hash<ResourcesKey,WeakReference<Resourcces>>,可以看出这些Resources对象都是以一个弱引用的方式保存,以便在内存紧张时可以释放Resources所占内存。

首先根据当前的设备请求参数屏幕分辨率,横竖屏等去hash里查找对应的资源,如果找到了就返回,

否则就创建一个对应的资源对象保存到mActiveResources中。


  1. public Resources getTopLevelResources(String resDir, int displayId,  
  2.         Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {  
  3.     final float scale = compatInfo.applicationScale; 
  4.     //  根据参数获取对应的ResourcesKey,不同分辨率对应不同的ResourcesKey
  5.     ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale,  
  6.             token);  
  7.     Resources r;  
  8.     synchronized (this) {  
  9.        
            //  根据ResourcesKey获取对应的Resources对象,找到了就直接返回
  10.         WeakReference<Resources> wr = mActiveResources.get(key);  
  11.         r = wr != null ? wr.get() : null;  
  12.         if (r != null && r.getAssets().isUpToDate()) {  
  13.             if (false) {  
  14.                
  15.             }  
  16.             return r;  
  17.         }  
  18.     } 
  19.   
  20.    //  如果没有对应的Resources对象,则生成对应的Resources对象
  21.    //  创建Resources对象需要先创建AssetManager对象,动态加载里有介绍。
  22.     AssetManager assets = new AssetManager();  
  23.     if (assets.addAssetPath(resDir) == 0) {  
  24.         return null;  
  25.     }  
  26.   
  27.     DisplayMetrics dm = getDisplayMetricsLocked(displayId);  
  28.     Configuration config;  
  29.     boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);  
  30.     final boolean hasOverrideConfig = key.hasOverrideConfiguration();  
  31.     if (!isDefaultDisplay || hasOverrideConfig) {  
  32.         config = new Configuration(getConfiguration());  
  33.         if (!isDefaultDisplay) {  
  34.             applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);  
  35.         }  
  36.         if (hasOverrideConfig) {  
  37.             config.updateFrom(key.mOverrideConfiguration);  
  38.         }  
  39.     } else {  
  40.         config = getConfiguration();  
  41.     }  
  42.     r = new Resources(assets, dm, config, compatInfo, token);  
  43.     if (false) {  
  44.      
  45.     }  
  46.   
  47.     synchronized (this) { 
  48.        //  把创建的Resources对象保存到hash中以便以后直接使用
  49.         WeakReference<Resources> wr = mActiveResources.get(key);  
  50.         Resources existing = wr != null ? wr.get() : null;  
  51.         if (existing != null && existing.getAssets().isUpToDate()) {  
  52.  
  53.             r.getAssets().close();  
  54.             return existing;  
  55.         }  
  56.   
  57.         // XXX need to remove entries when weak references go away  
  58.         mActiveResources.put(key, new WeakReference<Resources>(r));  
  59.         return r;  
  60.     }  

再看下ResourcesManager类的实现:

单例模式的ResourcesManager类

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. public static ResourcesManager getInstance() {  
  2.     synchronized (ResourcesManager.class) {  
  3.         if (sResourcesManager == null) {  
  4.             sResourcesManager = new ResourcesManager();  
  5.         }  
  6.         return sResourcesManager;  
  7.     }  

因为ResourcesManager采用单例模式,再加上获取资源时的参数,这样就保证了不同的ContextImpl访问的是同一套资源,注意,这里说的同一套资源未必是同一个资源,因为资源可能位于不同的目录,但它一定是我们的应用的资源,或许这样来描述更准确,在设备参数和显示参数不变的情况下,不同的ContextImpl访问到的是同一份资源。设备参数不变是指手机的屏幕和android版本不变,显示参数不变是指手机的分辨率和横竖屏状态。(构建Resources对象的时候第二和第三个参数应该就是这2部分参数,所以参数不一样的话,构造出来的Resources对象也是不一样的) 也就是说,尽管Application、Activity、Service都有自己的ContextImpl,并且每个ContextImpl都有自己的mResources成员,但是由于它们的mResources成员都来自于唯一的ResourcesManager实例,所以它们看似不同的mResources其实都指向的是同一块内存,因此,它们的mResources都是同一个对象(在设备参数和显示参数不变的情况下)。在横竖屏切换的情况下且应用中为横竖屏状态提供了不同的资源,处在横屏状态下的ContextImpl和处在竖屏状态下的ContextImpl访问的资源不是同一个资源对象。


Resources对象的创建

动态加载中介绍过,根据构造函数public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config)

我们需要先构造一个AssetManager对象

可以看到上面源码中的构造如下:

  1. AssetManager assets = new AssetManager();  
  2. if (assets.addAssetPath(resDir) == 0) {  
  3.     return null;  
  4. }

为什么这里不是用的反射呢?动态加载中是用的反射

看下addAssetPath函数的定义

  1. /** 
  2.  * Add an additional set of assets to the asset manager.  This can be 
  3.  * either a directory or ZIP file.  Not for use by applications.  Returns 
  4.  * the cookie of the added asset, or 0 on failure. 
  5.  * {@hide} 
  6.  */  
  7. public final int addAssetPath(String path) {  
  8.     int res = addAssetPathNative(path);  
  9.     return res;  
  10. }

注意到它的注释里面有一个{@hide}关键字,这意味着即使它是public的,但是外界仍然无法访问它,因为android sdk导出的时候会自动忽略隐藏的api,因此只能通过反射来调用。可见sdk导出的时候才不能直接使用这个api,那么在sdk里面是可以直接使用的。


有了Resources对象,我们就可以通过Resources对象来访问APK的各种资源了,通过这种方法,我们可以完成一些特殊的功能,比如换肤、换语言包、动态加载apk等


0 0
原创粉丝点击