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 user, boolean 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().sharedLibraryFiles, displayId,
2264
overrideConfiguration, compatInfo, activityToken);
2265
}
2266
}
2267
mResources = resources;
531
public Resources getResources(ActivityThread mainThread) {
532
if (mResources == null) {
533
mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
534
mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this);
535
}
536
return mResources;
537
}
而ActivityThread类型的mainThread一个应用中只有一个,函数调用如下
Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
1623
String[] libDirs, int displayId, Configuration overrideConfiguration,
1624
LoadedApk pkgInfo) {
1625
return mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs,
1626
displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo(), null);
1627
}
public Resources getTopLevelResources(String resDir, String[] splitResDirs,
153
String[] overlayDirs, String[] libDirs, int displayId,
154
Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
155
final float scale = compatInfo.applicationScale;
156
ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, token);
157
Resources r;
158
synchronized (this) {
159
// Resources is app scale dependent.
160
if (false) {
161
Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
162
}
163
WeakReferencewr = 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(dm, config);
222
}
223
if (hasOverrideConfig) {
224
config.updateFrom(key.mOverrideConfiguration);
225
}
226
} else {
227
config = getConfiguration();
228
}
229
r = new Resources(assets, dm, config, compatInfo, token);
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
WeakReferencewr = 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(key, new WeakReference(r));
248
return r;
249
}
250
}
根据上述代码和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对资源的访问实际上是通过AssetManager来实现的,那么如何创建一个Resources对象呢,有人会问,我为什么要去创建一个Resources对象呢,直接getResources不就可以了吗?我要说的是在某些特殊情况下你的确需要去创建一个资源对象,比如动态加载apk。很简单,首先看一下它的几个构造方法:
public Resources( AssetManager assets , DisplayMetrics metrics , Configuration config ) {
209
this(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
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(config, metrics);
233
assets.ensureStringBlocks();
234
}
它接受3个参数,第一个是AssetManager,后面两个是和设备相关的配置参数,我们可以直接用当前应用的配置就好,所以,问题的关键在于如何创建AssetManager,下面请看分析,为了创建一个我们自己的AssetManager,我们先去看看系统是怎么创建的。还记得getResources的底层实现吗,在ResourcesManager的getTopLevelResources方法中有这么两句:
public final int addedAssetPath(String path) {
613
synchronized (this) {
614
int res = addAssetPathNative(path);
615
makeStringBlocks(mStringBlocks);
616
return res;
617
}
618
}
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 app) throws 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.resourceDirs, app.sharedLibraryFiles, Display.DEFAULT_DISPLAY,
933
null, mContext.mPackageInfo);
934
if (r != null) {
935
return r;
936
}
937
throw new NameNotFoundException("Unable to open " + app.publicSourceDir);
938
}