基于之前对插件化的了解,大部分的插件对于资源都是合并式的,通过addAssetPath将插件的资源加入到assetManager中,这种方式会带来一个问题:resource id冲突。当然目前已经有方案去解决:aapt或者修改arsc文件。
VirtualApp中可以安装任意的第三方APK,如果采用合并式资源加载方案,那肯定也会有resource id冲突问题,但上面说的解决方案都需要第三方APK去配合修改,明显不能满足任意安装。
VirtualApp中可以安装任意的第三方APK,如果采用合并式资源加载方案,那肯定也会有resource id冲突问题,但上面说的解决方案都需要第三方APK去配合修改,明显不能满足任意安装。
VirtualApp采用的是独立式资源加载方案,这样就不会有resource id冲突的问题。
在分析VirtualApp是怎么做到独立式资源加载前,需要先了解一下应用获取resources大概的流程。在自定义Application中,我们可以通过getResources去获取当前的资源,下面看看getResources调用流程:
最终会走到ContextWrapper中:
具体调用流程分析参考: Android应用程序窗口(Activity)的运行上下文环境(Context)的创建过程分析
基于上面调用时序图,看看上述几个方法的实现
LoadedApk.makeApplication方法:
Instrumentation newApplication方法:
现在转移到分析ContextImpl类
看看ContextImpl.createAppContext方法:
packageInfo是在ActivityThread的handleBindApplication方法中被创建的
最终resources是调用ActivityThread.getTopLevelResources方法创建的:
ResourceManager根据APK的相关路径信息,读取到资源,最终生成Resources。
小结一下:Application调用getResources方法,最终获取到的是LoadedApk对象中根据APK相关路径创建的Resources对象。LoadedApk对象最早的创建是在ActivityThread.handleBindApplication时。
handleBindApplication是在绑定自定义Application时被调用。
分析了半天和VirtualApp的独立式资源加载方案有啥关系???
看段VirtualApp的代码
在分析VirtualApp是怎么做到独立式资源加载前,需要先了解一下应用获取resources大概的流程。在自定义Application中,我们可以通过getResources去获取当前的资源,下面看看getResources调用流程:
最终会走到ContextWrapper中:
public Resources getResources() {
return mBase.getResources();
}
发现是从mBase中拿到resources,那mBase从哪里来?也是在ContextWrapper类中:
/**
* Set the base context for this ContextWrapper. All calls will then be
* delegated to the base context. Throws
* IllegalStateException if a base context has already been set.
*
* @param base The new base context for this wrapper.
*/
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
ContextWrapper的attachBaseContext方法是在哪里被调用的呢?base context几时被创建的?
具体调用流程分析参考: Android应用程序窗口(Activity)的运行上下文环境(Context)的创建过程分析
基于上面调用时序图,看看上述几个方法的实现
LoadedApk.makeApplication方法:
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
}
Instrumentation newApplication方法:
public Application newApplication(ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return newApplication(cl.loadClass(className), context);
}
static public Application newApplication(Class<?> clazz, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
Application app = (Application)clazz.newInstance();
app.attach(context);
return app;
}
Application attach方法:
/* package */ final void attach(Context context) {
attachBaseContext(context);
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
最终发现原来ContextWrapper 中的mBase是在LoadedApk.makeApplication创建的,
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
attachBaseContext方法中的base参数其实ContextImpl的实例。
现在转移到分析ContextImpl类
看看ContextImpl.createAppContext方法:
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
return new ContextImpl(null, mainThread,
packageInfo, null, null, 0, null, null, Display.INVALID_DISPLAY);
}
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
Display display, Configuration overrideConfiguration, int createDisplayWithId) {
mOuterContext = this;
Resources resources = packageInfo.getResources(mainThread);
mResources = resources;
}
public Resources getResources() {
return mResources;
}
发现resources来自packageInfo,packageInfo是LoadedApk的一个实例,
packageInfo是在ActivityThread的handleBindApplication方法中被创建的
private void handleBindApplication(AppBindData data) {
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
}
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
CompatibilityInfo compatInfo) {
return getPackageInfo(ai, compatInfo, null, false, true, false);
}
来看看LoadedApk中的getResources方法是怎么实现的:
public Resources getResources(ActivityThread mainThread) {
if (mResources == null) {
mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, this);
}
return mResources;
}
public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
CompatibilityInfo compatInfo, ClassLoader baseLoader,
boolean securityViolation, boolean includeCode, boolean registerPackage) {
setApplicationInfo(aInfo);
}
private void setApplicationInfo(ApplicationInfo aInfo) {
final int myUid = Process.myUid();
mAppDir = aInfo.sourceDir;
mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir;
mSplitAppDirs = aInfo.splitSourceDirs;
mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs;
mOverlayDirs = aInfo.resourceDirs;
mSharedLibraries = aInfo.sharedLibraryFiles;
mDataDir = aInfo.dataDir;
mLibDir = aInfo.nativeLibraryDir;
}
最终resources是调用ActivityThread.getTopLevelResources方法创建的:
/**
* Creates the top level resources for the given package. Will return an existing
* Resources if one has already been created.
*/
Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
String[] libDirs, int displayId, LoadedApk pkgInfo) {
return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs,
displayId, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader());
}
ResourceManager根据APK的相关路径信息,读取到资源,最终生成Resources。
小结一下:Application调用getResources方法,最终获取到的是LoadedApk对象中根据APK相关路径创建的Resources对象。LoadedApk对象最早的创建是在ActivityThread.handleBindApplication时。
handleBindApplication是在绑定自定义Application时被调用。
分析了半天和VirtualApp的独立式资源加载方案有啥关系???
看段VirtualApp的代码
private AppBindData mBoundApplication;
private final class AppBindData {
String processName;
ApplicationInfo appInfo;
List<ProviderInfo> providers;
Object info;
}
private void bindApplicationNoCheck(String packageName, String processName, ConditionVariable lock) {
AppBindData data = new AppBindData();
mBoundApplication = data;
Context context = createPackageContext(data.appInfo.packageName);
mBoundApplication.info = ContextImpl.mPackageInfo.get(context);
mInitialApplication = LoadedApk.makeApplication.call(data.info, false, null);
mirror.android.app.ActivityThread.mInitialApplication.set(mainThread, mInitialApplication);
}
VirtualApp在运行第三方App时,会执行bindApplicationNoCheck去将第三方自定义Application绑定起来,
mInitialApplication = LoadedApk.makeApplication.call(data.info, false, null);
dataInfo.makeApplication,这里就是开始调用创建Application, 那data.info几时被创建的?继续看上面贴出来的bindApplicationNoCheck,
mBoundApplication = data;
Context context = createPackageContext(data.appInfo.packageName);
mBoundApplication.info = ContextImpl.mPackageInfo.get(context);
data.info 是 mBoundApplication.info, mBoundApplication.info是从context读取出来的,而context是调用createPackageContext方法,这个方法参数是packageName->第三方应用包名。
@Override
public Context createPackageContext(String packageName, int flags)
throws NameNotFoundException {
return createPackageContextAsUser(packageName, flags,
mUser != null ? mUser : Process.myUserHandle());
}
@Override
public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
throws NameNotFoundException {
LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),
flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());
if (pi != null) {
ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
user, flags, mDisplay, null, Display.INVALID_DISPLAY);
if (c.mResources != null) {
return c;
}
}
}
context其实是ContextImpl的一个实例,在创建context时,LoadedApk对象也被创建出来。
小结:VirtualApp在bindApplication时会先根据第三方应用包名创建context,在创建context时,LoadedApk对象也被创建出来,然后基于创建的LoadedApk对象执行makeApplication方法,又回到前文分析如和attachBaseContext。
总结: VirtualApp通过调用createPackageContext创建ConextImpl实例,在创建ConextImpl实例时创建LoadedApk实例,LoadedApk.getResources根据第三方应用相关信息生成Resources实例。自定义Application通过loadedApk.getResources去查找资源。