Android资源访问机制

        Android经常使用getResources()方法获取app的一些资源,getResource()方法是Context接口的方法,具体是有ContextImpl类实现的,Activity、Service、Application都是继承自Context接口。

        资源获取的方式是context.getResources,而真正的实现位于ContextImpl中的getResources方法,在ContextImpl中有一个私有成员Resources mResources,getResources方法返回的结果就是该对象成员,mResources的赋值则是在ContextImpl的构造函数中:

private ContextImpl(ContextImpl container, ActivityThread mainThread,
2225
            LoadedApk packageInfo, IBinder activityToken, UserHandle userboolean restricted,
2226
            Display display, Configuration overrideConfiguration) {
                              .......

2254
        Resources resources = packageInfo.getResources(mainThread);
2255
        if (resources != null) {
2256
            if (activityToken != null
2257
                    || displayId != Display.DEFAULT_DISPLAY
2258
                    || overrideConfiguration != null
2259
                    || (compatInfo != null && compatInfo.applicationScale
2260
                            != resources.getCompatibilityInfo().applicationScale)) {
2261
                resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),
2262
                        packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),
2263
                        packageInfo.getApplicationInfo().sharedLibraryFilesdisplayId,
2264
                        overrideConfigurationcompatInfoactivityToken);
2265
            }
2266
        }
2267
        mResources = resources;

而mPackageInfo是LoadedAPK类,所以就是调用到LoadedAPK的getResource(ActivityThread)方法中:

531
     public Resources  getResources(ActivityThread mainThread) {
532
         if (mResources == null) {
533
             mResources = mainThread.getTopLevelResources(mResDirmSplitResDirsmOverlayDirs,
534
                     mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAYnullthis);
535
         }
536
         return mResources;
537
     }

而ActivityThread类型的mainThread一个应用中只有一个,函数调用如下

Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
1623
            String[] libDirsint displayId, Configuration overrideConfiguration,
1624
            LoadedApk pkgInfo) {
1625
        return mResourcesManager.getTopLevelResources(resDirsplitResDirsoverlayDirslibDirs,
1626
                displayIdoverrideConfigurationpkgInfo.getCompatibilityInfo(), null);
1627
    }

下面看一下ResourcesManager的getTopLevelResources方法,

 public Resources getTopLevelResources(String resDir, String[] splitResDirs,
153
            String[] overlayDirs, String[] libDirsint displayId,
154
            Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
155
        final float scale = compatInfo.applicationScale;
156
        ResourcesKey key = new ResourcesKey(resDirdisplayIdoverrideConfigurationscaletoken);
157
        Resources r;
158
        synchronized (this) {
159
            // Resources is app scale dependent.
160
            if (false) {
161
                Slog.w(TAG"getTopLevelResources: " + resDir + " / " + scale);
162
            }
163
            WeakReference wr = mActiveResources.get(key);
164
            r = wr != null ? wr.get() : null;
165
            //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
166
            if (r != null && r.getAssets().isUpToDate()) {
167
                if (false) {
168
                    Slog.w(TAG"Returning cached resources " + r + " " + resDir
169
                            + ": appScale=" + r.getCompatibilityInfo().applicationScale);
170
                }
171
                return r;
172
            }
173
        }
174
 
  
175
        //if (r != null) {
176
        //    Slog.w(TAG, "Throwing away out-of-date resources!!!! "
177
        //            + r + " " + resDir);
178
        //}
179
 
  
180
        AssetManager assets = new AssetManager();
181
        // resDir can be null if the 'android' package is creating a new Resources object.
182
        // This is fine, since each AssetManager automatically loads the 'android' package
183
        // already.
184
        if (resDir != null) {
185
            if (assets.addAssetPath(resDir) == 0) {
186
                return null;
187
            }
188
        }
189
 
  
190
        if (splitResDirs != null) {
191
            for (String splitResDir : splitResDirs) {
192
                if (assets.addAssetPath(splitResDir) == 0) {
193
                    return null;
194
                }
195
            }
196
        }
197
 
  
198
        if (overlayDirs != null) {
199
            for (String idmapPath : overlayDirs) {
200
                assets.addOverlayPath(idmapPath);
201
            }
202
        }
203
 
  
204
        if (libDirs != null) {
205
            for (String libDir : libDirs) {
206
                if (assets.addAssetPath(libDir) == 0) {
207
                    Slog.w(TAG"Asset path '" + libDir +
208
                            "' does not exist or contains no resources.");
209
                }
210
            }
211
        }
212
 
  
213
        //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
214
        DisplayMetrics dm = getDisplayMetricsLocked(displayId);
215
        Configuration config;
216
        boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
217
        final boolean hasOverrideConfig = key.hasOverrideConfiguration();
218
        if (!isDefaultDisplay || hasOverrideConfig) {
219
            config = new Configuration(getConfiguration());
220
            if (!isDefaultDisplay) {
221
                applyNonDefaultDisplayMetricsToConfigurationLocked(dmconfig);
222
            }
223
            if (hasOverrideConfig) {
224
                config.updateFrom(key.mOverrideConfiguration);
225
            }
226
        } else {
227
            config = getConfiguration();
228
        }
229
        r = new Resources(assetsdmconfigcompatInfotoken);
230
        if (false) {
231
            Slog.i(TAG"Created app resources " + resDir + " " + r + ": "
232
                    + r.getConfiguration() + " appScale="
233
                    + r.getCompatibilityInfo().applicationScale);
234
        }
235
 
  
236
        synchronized (this) {
237
            WeakReference wr = mActiveResources.get(key);
238
            Resources existing = wr != null ? wr.get() : null;
239
            if (existing != null && existing.getAssets().isUpToDate()) {
240
                // Someone else already created the resources while we were
241
                // unlocked; go ahead and use theirs.
242
                r.getAssets().close();
243
                return existing;
244
            }
245
 
  
246
            // XXX need to remove entries when weak references go away
247
            mActiveResources.put(keynew WeakReference(r));
248
            return r;
249
        }
250
    }
这个方法的思想是这样的:在ResourcesManager中,所有的资源对象都被存储在ArrayMap中,首先根据当前的请求参数去查找资源,如果找到了就返回;否则就创建一个资源对象放到ArrayMap中。为什么会有多个资源对象,因为res下可能存在多个适配不同设备、不同分辨率、不同系统版本的目录,按照android系统的设计,不同设备在访问同一个应用的时候访问的资源可以不同,比如drawable-hdpi和drawable-xhdpi就是典型的例子。

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

 public static ResourcesManager getInstance() {
57
         synchronized (ResourcesManager.class) {
58
             if (sResourcesManager == null) {
59
                 sResourcesManager = new ResourcesManager();
60
             }
61
             return sResourcesManager;
62
         }
63
     }


Resources对象的创建过程

通过阅读Resources类的源码可以知道,Resources对资源的访问实际上是通过AssetManager来实现的,那么如何创建一个Resources对象呢,有人会问,我为什么要去创建一个Resources对象呢,直接getResources不就可以了吗?我要说的是在某些特殊情况下你的确需要去创建一个资源对象,比如动态加载apk。很简单,首先看一下它的几个构造方法:

		public Resources
( AssetManager   assets DisplayMetrics   metrics Configuration   config ) {
209
         this(assetsmetricsconfig, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFOnull);
210
     }

 		public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config,
225
             CompatibilityInfo compatInfo, IBinder token) {
226
         mAssets = assets;
227
         mMetrics.setToDefaults();
228
         if (compatInfo != null) {
229
             mCompatibilityInfo = compatInfo;
230
         }
231
         mToken = new WeakReference(token);
232
         updateConfiguration(configmetrics);
233
         assets.ensureStringBlocks();
234
     }

               从简单起见,我们应该采用第一个 public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config)

它接受3个参数,第一个是AssetManager,后面两个是和设备相关的配置参数,我们可以直接用当前应用的配置就好,所以,问题的关键在于如何创建AssetManager,下面请看分析,为了创建一个我们自己的AssetManager,我们先去看看系统是怎么创建的。还记得getResources的底层实现吗,在ResourcesManager的getTopLevelResources方法中有这么两句:


  1. AssetManager assets = new AssetManager();  
  2. if (assets.addAssetPath(resDir) == 0) {  
  3.     return null;  
  4. }  
          这两句就是创建一个AssetManager对象,后面会用这个对象来创建Resources对象,AssetManager就是这么创建的,assets.addAssetPath(resDir)这句话的意思是把资源目录里的资源都加载到AssetManager对象中,具体的实现在jni中,大家感兴趣自己去了解下。而资源目录就是我们的res目录,当然resDir可以是一个目录也可以是一个zip文件。有没有想过,如果我们把一个未安装的apk的路径传给这个方法,那么apk中的资源是不是就被加载到AssetManager对象里面了呢?事实证明,的确是这样 。addAssetPath方法的定义如下,注意到它的注释里面有一个{@hide}关键字,这意味着即使它只是给Framework自己使用的,因此只能通过反射来调用。

  	       public final int addedAssetPath(String path) {
613
        synchronized (this) {
614
            int res = addAssetPathNative(path);
615
            makeStringBlocks(mStringBlocks);
616
            return res;
617
        }
618
    }
有了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等。



另外,除了通过Context的getResource()方法访问android应用的资源外,还可以通过PackageManger来获取。

PackageManager本身只是一个抽象类,具体是由ApplicationPackageManager实现的,获取了PackageManager以后就可以调用PacakageManager的getResourcesForApplication(ApplicationInfo app)来获取Resources对象了,代码如下:

public Resources getResourcesForApplication(
924
         ApplicationInfo appthrows NameNotFoundException {
925
         if (app.packageName.equals("system")) {
926
             return mContext.mMainThread.getSystemContext().getResources();
927
         }
928
         final boolean sameUid = (app.uid == Process.myUid());
929
         Resources r = mContext.mMainThread.getTopLevelResources(
930
                 sameUid ? app.sourceDir : app.publicSourceDir,
931
                 sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs,
932
                 app.resourceDirsapp.sharedLibraryFiles, Display.DEFAULT_DISPLAY,
933
                 nullmContext.mPackageInfo);
934
         if (r != null) {
935
             return r;
936
         }
937
         throw new NameNotFoundException("Unable to open " + app.publicSourceDir);
938
     }
其实PackageManager方式同Context方式最终还是一致的,都是通过mMainThread.getTopLevelResource()方法来获取Resources的。


你可能感兴趣的:(Android开发)