application中常用的关于Context的api:
片段1
#ContextWrapper.java
@Override
public Context getApplicationContext() {
return mBase.getApplicationContext();
}
@Override
public Resources getResources() {
return mBase.getResources();
}
/**
#ContextImpl.java
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
if (packageInfo == null) throw new IllegalArgumentException(“packageInfo”);
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
null);
context.setResources(packageInfo.getResources());
return context;
}
#Instrumentation.java
public Application newApplication(ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
Application app = getFactory(context.getPackageName())
.instantiateApplication(cl, className);//反射创建Application对象
app.attach(context);
return app;
}
#Application.java
final void attach(Context context) {
attachBaseContext(context);
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException(“Base context already set”);
}
mBase = base;
}
makeApplication方法是在ActivityThread收到创建application消息后调用handleBindApplication方法触发的,可以看到在makeApplication方法中主要就是创建ContextImpl和Application对象,并将创建的COntextImpl对象通过attach方法塞给了Application,也就是mBase变量。
而Activity的相关的api如下:
片段3
#ContextThemeWrapper.java
@Override
public Resources getResources() {
return getResourcesInternal();
}
private Resources getResourcesInternal() {
if (mResources == null) {
if (mOverrideConfiguration == null) {
mResources = super.getResources();
} else {
final Context resContext = createConfigurationContext(mOverrideConfiguration);
mResources = resContext.getResources();
}
}
return mResources;
}
#ContextWrapper.java
@Override
public Context getApplicationContext() {
return mBase.getApplicationContext();
}
/**
@return the base context as set by the constructor or setBaseContext
*/
public Context getBaseContext() {
return mBase;
}
可以发现除了getResource方法外,其他两个api是一样的,都是委托给了mBase变量,同样的问题,mBase是在什么时候创建和设置的呢?思索一下可以发现是在调用ContextWraper对象的attachBaseContext时设置的,那Activity对象继承的attahBaseContext方法是在什么时候调用的呢?
片段4
#Activity.java
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
…
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
…
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mIdent = ident;
mApplication = application;
…
}
可以看到是在Activity的attach方法中调用的,同时Application对象也是在此时设置的,继续追源码,看一下attach方法是在哪里调用的,其context参数是什么?
片段5
#ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
…
ContextImpl appContext = createBaseContextForActivity®;
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
…
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
…
appContext.setOuterContext(activity);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
…
return activity;
}
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
…
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
…
return appContext;
}
查看源码发现Activity的attach方法是在创建Activity之后调用的,而传递的context参数是createBaseContextForActivity方法通过ContextImpl的createActivityContext方法返回的
片段6
#ContextImpl.java
static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
Configuration overrideConfiguration) {
…
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
activityToken, null, 0, classLoader);
…
final ResourcesManager resourcesManager = ResourcesManager.getInstance();
context.setResources(resourcesManager.createBaseActivityResources(activityToken,
packageInfo.getResDir(),
splitDirs,
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
classLoader));
return context;
}
从上述代码我们可以看出,Activity的mBase是在Activity创建之后,onCreate方法被调用之前,由ContextImpl静态方法新创建的ContextImpl类的实力对象,所以Activity的Context和Application的Context不是同一个对象。
2.Resource、Context、ResourceManager 和 AssetsManger的关系?
我们常常使用getResource方法来获取Resource对象,之后再调用相关获取资源的Api,如下:
片段7
getResources().getString(resId);
实际上Activity的getResouce方法和Application的getResource方法不是同一个实现,其具体实现如上述代码片段1和片段3,这里我们先看一下Application的getResource方法,其委托给了mBase,也就是Comtextimpl对象去处理:
片段8
#ContextImpl.java
@Override
public Resources getResources() {
return mResources;
}
void setResources(Resources r) {
if (r instanceof CompatResources) {
((CompatResources) r).setContext(this);
}
mResources = r;
}
mResources成员仅可以通过set方法设置,所以这里仅需要关注setResources方法被调用的地方,具体位置在上述代码片段2的createAppContext方法,可以发现设置Resource来自LoadedApk对象的getResource方法。那么LoadedApk对象是哪里产生?其getResource方法干了什么是事情呢?
片段9
#LoadedApk.java
public Resources getResources() {
if (mResources == null) {
final String[] splitPaths;
try {
splitPaths = getSplitPaths(null);
} catch (NameNotFoundException e) {
// This should never fail.
throw new AssertionError(“null split not found”);
}
mResources = ResourcesManager.getInstance().getResources(null, mResDir,
splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
getClassLoader());
}
return mResources;
}
#ActivityThread.java
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
boolean registerPackage) {
final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
synchronized (mResourcesManager) {
WeakReference ref;
…
else if (includeCode) {
ref = mPackages.get(aInfo.packageName);
}
LoadedApk packageInfo = ref != null ? ref.get() : null;
if (packageInfo == null || (packageInfo.mResources != null
&& !packageInfo.mResources.getAssets().isUpToDate())) {
packageInfo =
new LoadedApk(this, aInfo, compatInfo, baseLoader,
securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
...
if (differentUser) {
// Caching not supported across users
} else if (includeCode) {
mPackages.put(aInfo.packageName,
new WeakReference(packageInfo));
} else {
mResourcePackages.put(aInfo.packageName,
new WeakReference(packageInfo));
}
}
return packageInfo;
}
}
从ActivityThread的getPackageInfo方法(该方法第一次调用是在创建Application对象时)可以看出,LoadedApk对象的获取首先从缓存集合中获取,如果没有再创建该对象并将其保存到集合中,而集合的key是包名,所以我们可以理解为一个apk对应一个LoadedApk对象.
Application的getResource最后委托给了ResourcesManager这个单例类来获取Resouce对象:
片段10
#ResourcesManager.java
public @Nullable Resources getResources(@Nullable IBinder activityToken,
@Nullable String resDir,
@Nullable String[] splitResDirs,
@Nullable String[] overlayDirs,
@Nullable String[] libDirs,
int displayId,
@Nullable Configuration overrideConfig,
@NonNull CompatibilityInfo compatInfo,
@Nullable ClassLoader classLoader) {
try {
final ResourcesKey key = new ResourcesKey(
resDir,
splitResDirs,
overlayDirs,
libDirs,
displayId,
overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
compatInfo);
return getOrCreateResources(activityToken, key, classLoader);
}
}
//获取缓存的Resource对象或者创建一个新的对象
private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
synchronized (this) {
if (activityToken != null) {
//Activity中的getResource方法走这里
final ActivityResources activityResources =
getOrCreateActivityResourcesStructLocked(activityToken);
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
}
} else {
//Application中的getResource方法走这里
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
}
ResourcesImpl resourcesImpl = createResourcesImpl(key);
if (resourcesImpl == null) {
return null;
}
mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
final Resources resources;
if (activityToken != null) {
resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
} else {
resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
return resources;
}
}
//从缓存中读取ResourcesImpl并校验有效性
private @Nullable ResourcesImpl findResourcesImplForKeyLocked(@NonNull ResourcesKey key) {
WeakReference weakImplRef = mResourceImpls.get(key);
ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
if (impl != null && impl.getAssets().isUpToDate()) {
return impl;
}
return null;
}
上述代码首先创建了一个ResoucesKey对象,该对象用于缓存Resouces集合的key,组装用到的resDir变量就是我们data/app/package/目录下的.apk文件.之后尝试去获取Resource对象。获取原理就是先尝试去mResourceImpls中获取ResourcesImpl,如果没有,则会尝通过createResourcesImpl创建一个新的ResourcesImpl对象并将其缓存在mResourceImpls中.再通过getOrCreateResourcesLocked方法获取Resources对象。接下来看下Resouce对象的获取过程:
片段11
#ResourcesManager.java
private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
@NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
final int refCount = mResourceReferences.size();
for (int i = 0; i < refCount; i++) {
WeakReference weakResourceRef = mResourceReferences.get(i);
Resources resources = weakResourceRef.get();
if (resources != null &&
Objects.equals(resources.getClassLoader(), classLoader) &&
resources.getImpl() == impl) {
if (DEBUG) {
Slog.d(TAG, “- using existing ref=” + resources);
}
return resources;
}
}
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);
resources.setImpl(impl);
mResourceReferences.add(new WeakReference<>(resources));
return resources;
}
private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
daj.setCompatibilityInfo(key.mCompatInfo);
final AssetManager assets = createAssetManager(key);
if (assets == null) {
return null;
}
final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
final Configuration config = generateConfig(key, dm);
final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
return impl;
}
从代码来看,Resources对象获取过程就是先去集合中获取缓存,如果没有就创建一个Resources对象并设置其实现为ResourcesImpl对象。而ResourcesImpl对象则来源于createResourcesImpl方法。到这里我们基本上可以看到Application中Resources对象的来源了。Activity中的Resources对象来源和Application差不多,只是缓存的地方不一样。接下来我们看一下常使用的Resource的api:
片段12
#Resources.java
public String getString(@StringRes int id) throws NotFoundException {
return getText(id).toString();
}
public CharSequence getText(@StringRes int id) throws NotFoundException {
CharSequence res = mResourcesImpl.getAssets().getResourceText(id);
if (res != null) {
return res;
}
}
#ResourcesImpl.java
public AssetManager getAssets() {
return mAssets;
}
以获取字符串为例,可以看到其最终是委托给了成员变量mResourcesImp去处理,而该成员就是我们之前调用setImpl方法设置的ResourcesImpl对象。之后通过ResourcesImpl的成员变量mAssets的getResourceText方法去获取字符串资源。这个mAssets就是创建ResourcesImpl对象时,从构造方法传入的。回到该ResourcesImpl对象创建的createResourcesImpl方法,会发现其asstes是通过createAssetManager方法创建的。
片段13
#ResourcesManager.java
protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
AssetManager assets = new AssetManager();
// resDir can be null if the 'android' package is creating a new Resources object.
// This is fine, since each AssetManager automatically loads the 'android' package
// already.
if (key.mResDir != null) {
if (assets.addAssetPath(key.mResDir) == 0) {
Log.e(TAG, "failed to add asset path " + key.mResDir);
return null;
}
}
…后续还有一些添加资源目录的处理
return assets;
}
createAssetManager方法主要就是创建一个AssetManager对象并将资源目录传递给它,后续通过该AssetManager加载的资源都是对应资源目录下的资源,而且这里传递的资源目录只是一个地址,不管该地址对应的apk文件是否被安装,都可以获取到的。
3.activiity之间的Resource和assetManager是同一个吗?
这个问题我们要先回到Activity创建Context的地方,首先要明确Activity之间的context是否相同?回到代码片段6,可以发现,每一个新创建的Activity 的Context对象都是直接通过new关键字创建的,所以我们可以确定Activity之间的Context实例也不一样,且创建成功之后使用了resourcesManager.createBaseActivityResources()来创建对应的Resource对象。
片段14
#ResourcesManager.java
public @Nullable Resources createBaseActivityResources(@NonNull IBinder activityToken,
@Nullable String resDir,
@Nullable String[] splitResDirs,
@Nullable String[] overlayDirs,
@Nullable String[] libDirs,
int displayId,
@Nullable Configuration overrideConfig,
@NonNull CompatibilityInfo compatInfo,
@Nullable ClassLoader classLoader) {
try {
//创建资源key
final ResourcesKey key = new ResourcesKey(
resDir,
splitResDirs,
overlayDirs,
libDirs,
displayId,
overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
compatInfo);
synchronized (this) {
// 创建Activity的资源描述对象
getOrCreateActivityResourcesStructLocked(activityToken);
}
// Update any existing Activity Resources references.
updateResourcesForActivity(activityToken, overrideConfig, displayId,
false /* movedToDifferentDisplay */);
//获取一个Resource对象
return getOrCreateResources(activityToken, key, classLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
private ActivityResources getOrCreateActivityResourcesStructLocked(
@NonNull IBinder activityToken) {
ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
if (activityResources == null) {
activityResources = new ActivityResources();
mActivityResourceReferences.put(activityToken, activityResources);
}
return activityResources;
}
private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
synchronized (this) {
if (activityToken != null) {
final ActivityResources activityResources =
getOrCreateActivityResourcesStructLocked(activityToken);
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
}
}
// If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
ResourcesImpl resourcesImpl = createResourcesImpl(key);
if (resourcesImpl == null) {
return null;
}
synchronized (this) {
ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
if (existingResourcesImpl != null) {
if (DEBUG) {
Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
+ " new impl=" + resourcesImpl);
}
resourcesImpl.getAssets().close();
resourcesImpl = existingResourcesImpl;
} else {
// Add this ResourcesImpl to the cache.
mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
}
final Resources resources;
if (activityToken != null) {
resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
} else {
resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
return resources;
}
}
在创建Resource对象的过程中,大致分为以下步骤:
1.创建ResourcesKey对象作为key
2.以activityToken为key,获取ActivityResource对象,如果没有则创建并保存
3.以ResourcesKey作为key,查找Resourceimpl对象,如果没有则创建
4.获取ActivityResource,检查其中的Resource对象是否存在和有效,如果有效则直接返回,如果无效则创建Resources对象并将ResourceImpl对象设置给Resource
这里的activityToken是ActivityRecord.Token对象,在创建ActivityRecord对象时创建,每个Activity在AMS对应的key,每个Activity不一样,所以我们可以确认,这里不同Activity对应的Resource对象也是不一样的。而Resources对象中的ResourcesImpl对象则是通过Resourceskey来获取和保存的,如果ACtivity配置一样,这里ResourcesKey就会相等。而AssetsManager对象和ResourcesImpl对象是一一对应的。
资源替换:https://zhuanlan.zhihu.com/p/34346219