这里以 VirtualAPK 0.9.8 版本为例,从三个方面介绍 VirtualAPK 的插件化原理。
VirtualAPK 从外部存储读取到插件 APK 后,调用了 PluginManager 的 loadPlugin 的方法加载插件 APK。
File plugin = new File(pluginPath);
PluginManager.getInstance(this).loadPlugin(plugin);
PluginManager 的 loadPlugin 方法如下:
public void loadPlugin(File apk) throws Exception {
...
LoadedPlugin plugin = createLoadedPlugin(apk);
if (null == plugin) {
throw new RuntimeException("Can't load plugin which is invalid: " + apk.getAbsolutePath());
}
...
}
可以看出loadPlugin 会调用 createLoadedPlugin 方法得到一个 LoadedPlugin。
protected LoadedPlugin createLoadedPlugin(File apk) throws Exception {
return new LoadedPlugin(this, this.mContext, apk);
}
可以看出 createLoadedPlugin 调用了 LoadedPlugin 的构造方法,将 PluginManager、context、插件 APK 作为参数传递给了构造方法。
LoadedPlugin 的构造方法如下:
public LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception {
this.mPluginManager = pluginManager;
...
this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK);
...
this.mResources = createResources(context, getPackageName(), apk);
this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader());
...
// Cache instrumentations
Map<ComponentName, InstrumentationInfo> instrumentations = new HashMap<ComponentName, InstrumentationInfo>();
...
// Cache activities
Map<ComponentName, ActivityInfo> activityInfos = new HashMap<ComponentName, ActivityInfo>();
...
// Cache services
Map<ComponentName, ServiceInfo> serviceInfos = new HashMap<ComponentName, ServiceInfo>();
...
// Cache providers
Map<String, ProviderInfo> providers = new HashMap<String, ProviderInfo>();
...
// Register broadcast receivers dynamically
Map<ComponentName, ActivityInfo> receivers = new HashMap<ComponentName, ActivityInfo>();
...
// try to invoke plugin's application
invokeApplication();
}
可以看出 LoadedPlugin 的构造方法会解析 APK 包,加载插件资源,加载插件类,解析 Instrumentation 和四大组件,最后调用插件的 application。
使用 PackageParserCompat.parsePackage 解析 APK 包。
this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK);
使用 createResources 加载插件资源。
this.mResources = createResources(context, getPackageName(), apk);
使用 createClassLoader 加载插件类。
this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader());
从 PackageParser.Package 解析 Instrumentation 和四大组件。
// Cache activities
Map<ComponentName, ActivityInfo> activityInfos = new HashMap<ComponentName, ActivityInfo>();
for (PackageParser.Activity activity : this.mPackage.activities) {
activity.info.metaData = activity.metaData;
activityInfos.put(activity.getComponentName(), activity.info);
}
this.mActivityInfos = Collections.unmodifiableMap(activityInfos);
this.mPackageInfo.activities = activityInfos.values().toArray(new ActivityInfo[activityInfos.size()]);
最后调用了 invokeApplication 方法运行插件 APK 的 application。
插件类的加载 createClassLoader 方法如下:
protected ClassLoader createClassLoader(Context context, File apk, File libsDir, ClassLoader parent) throws Exception {
File dexOutputDir = getDir(context, Constants.OPTIMIZE_DIR);
String dexOutputPath = dexOutputDir.getAbsolutePath();
DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent);
if (Constants.COMBINE_CLASSLOADER) {
DexUtil.insertDex(loader, parent, libsDir);
}
return loader;
}
可以看出 createClassLoader 构造了 DexCloader 并且把宿主 APK 的 classLoader 传递给了插件 APK 的 DexClassLoader。因此 VirtualAPK 的插件可以调用宿主的类。
DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent);
在 createClassLoader 方法里面也判断了 Constants.COMBINE_CLASSLOADER 是否为 true,如果为 true 就调用 insertDex 方法。
insertDex 方法把插件 APK 的 Dex 和宿主 APK 的 Dex 合并,然后把插件 APK 的 Dex 排在宿主 APK 后面,这样就在宿主 APK 包含了所有的 Dex,宿主可以加载插件 APK 的类。
合并宿主 APK 和 插件 APK 的 dex 的方法如下:
public static void insertDex(DexClassLoader dexClassLoader, ClassLoader baseClassLoader, File nativeLibsDir) throws Exception {
Object baseDexElements = getDexElements(getPathList(baseClassLoader));
Object newDexElements = getDexElements(getPathList(dexClassLoader));
Object allDexElements = combineArray(baseDexElements, newDexElements);
Object pathList = getPathList(baseClassLoader);
Reflector.with(pathList).field("dexElements").set(allDexElements);
insertNativeLibrary(dexClassLoader, baseClassLoader, nativeLibsDir);
}
注意到 Constants.COMBINE_CLASSLOADER 这个常量应该始终为 true。否则宿主 APK 就不会包含插件 APK 的 dex,也就加载不了插件的类。
LoadedPlugin 类的构造方法调用了 createResources 方法来构造资源。
this.mResources = createResources(context, getPackageName(), apk);
createResources 方法如下:
protected Resources createResources(Context context, String packageName, File apk) throws Exception {
if (Constants.COMBINE_RESOURCES) {
return ResourcesManager.createResources(context, packageName, apk);
} else {
Resources hostResources = context.getResources();
AssetManager assetManager = createAssetManager(context, apk);
return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
}
}
注意到如果 Constants.COMBINE_RESOURCES 为 true,那么 createResources 会合并宿主 APK 和插件 APK 的资源,否则只返回插件 APK 的资源。
ResourcesManager 的 createResources 方法如下:
public static synchronized Resources createResources(Context hostContext, String packageName, File apk) throws Exception {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return createResourcesForN(hostContext, packageName, apk);
}
Resources resources = ResourcesManager.createResourcesSimple(hostContext, apk.getAbsolutePath());
ResourcesManager.hookResources(hostContext, resources);
return resources;
}
如果是 N 版本以上 SDK,会调用 createResourcesForN 方法。
/**
* Use System Apis to update all existing resources.
*
* 1. Update ApplicationInfo.splitSourceDirs and LoadedApk.mSplitResDirs
*
* 2. Replace all keys of ResourcesManager.mResourceImpls to new ResourcesKey
*
* 3. Use ResourcesManager.appendLibAssetForMainAssetPath(appInfo.publicSourceDir, "${packageName}.vastub") to update all existing resources.
*
*
* see android.webkit.WebViewDelegate.addWebViewAssetPath(Context)
*/
@TargetApi(Build.VERSION_CODES.N)
private static Resources createResourcesForN(Context context, String packageName, File apk) throws Exception {
...
LoadedApk loadedApk = Reflector.with(context).field("mPackageInfo").get();
Reflector rLoadedApk = Reflector.with(loadedApk).field("mSplitResDirs");
String[] splitResDirs = rLoadedApk.get();
rLoadedApk.set(append(splitResDirs, newAssetPath));
...
// lastly, sync all LoadedPlugin to newResources
for (LoadedPlugin plugin : PluginManager.getInstance(context).getAllLoadedPlugins()) {
plugin.updateResources(newResources);
}
...
return newResources;
}
通过方法注释可以看出 createResourcesForN 方法更新了宿主 ApplicationInfo 的 splitSourceDirs、宿主 LoadedApk 的 mSplitResDirs、ResourcesManager.mResourceImpls 的 key 值、ResourcesManager 的 assetPath,最后把得到的新 Resources 更新给了每个插件的 LoadedPlugin。所以宿主 APK 和 插件 APK 可以相互共享资源。
如果插件资源和宿主资源重复,比如引用了同一个 support 包,那么 virtualAPK 会过滤掉插件 APK 引用的 support 包。
使用 virtualAPK 是有 2 个有关资源的问题:
对于第一个问题,如果宿主出了 2.0 版本,那么插件也会出 2.0 版本,保持一致。
对于第二个问题,同名的资源在构建的时候 gradle 会报错,提示 duplicate resources entry,从 Android 开发的角度来看,资源不能重复,否则 merge 的时候就会报错。因此需要保证资源不同名。
Activity 的支持可以使用“欺上瞒下”来概括。
“欺上”是指启动一个 StubActivity 来欺骗 AMS,让 AMS 认为 Activity 已经在 AndroidManifest 注册,但是实际注册的 VirtualAPK 的 core aar 里面的 AndroidManifest 的占坑 Activity,比如
“瞒下”是指 ActivityThread 在 performLaunchActivity 方法中启动 Activity 会调用 Instrumentation 的 newActivity 创建 Activity,此时创建的不是占坑的 StubActivity,而是真正需要启动的插件 Activity。
VirtualAPK 的“欺上瞒下”是通过 hook 替换原始 Instrumentation,改为自己的 VAInstrumentation。
VAInstrumentation 构造方法如下:
public class VAInstrumentation extends Instrumentation implements Handler.Callback {
...
public VAInstrumentation(PluginManager pluginManager, Instrumentation base) {
this.mPluginManager = pluginManager;
this.mBase = base;
}
可以看出真正的 Instrumentation 作为了 mBase 变量,有点类似 ContextImpl。
VAInstrumentation 主要做 2 件事:
启动时替换原始 intent 为占坑 intent,这一步依靠 injectIntent 来完成,注入 Intent 后还是调用 Instrumentation 类启动 Intent。
@Override
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode) {
injectIntent(intent);
return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode);
}
injectIntent 方法如下:
protected void injectIntent(Intent intent) {
...
// null component is an implicitly intent
if (intent.getComponent() != null) {
...
// resolve intent with Stub Activity if needed
this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
}
}
可以看出调用了 markIntentIfNeeded 来转换为 StubActivity。
markIntentIfNeeded 方法如下:
public void markIntentIfNeeded(Intent intent) {
if (intent.getComponent() == null) {
return;
}
String targetPackageName = intent.getComponent().getPackageName();
String targetClassName = intent.getComponent().getClassName();
// search map and return specific launchmode stub activity
if (!targetPackageName.equals(mContext.getPackageName()) && mPluginManager.getLoadedPlugin(targetPackageName) != null) {
intent.putExtra(Constants.KEY_IS_PLUGIN, true);
intent.putExtra(Constants.KEY_TARGET_PACKAGE, targetPackageName);
intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName);
dispatchStubActivity(intent);
}
}
markIntentIfNeeded 会把原始 intent 的包名和类名存入 intent 的 extra 中,然后调用 dispatchStubActivity 启动 StubActivity。
dispatchStubActivity 方法如下:
private void dispatchStubActivity(Intent intent) {
ComponentName component = intent.getComponent();
String targetClassName = intent.getComponent().getClassName();
...
String stubActivity = mStubActivityInfo.getStubActivity(targetClassName, launchMode, themeObj);
...
intent.setClassName(mContext, stubActivity);
}
可以看出 dispatchStubActivity 根据类名、启动模式、主题来选择合适的占坑 Activity,最后用 setClassName 改写原始 Activity 为占坑 Activity。
VAInstrumentation 的 newActivity 用来构造 Activity ,还原占坑 intent 为原始 intent。
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
try {
cl.loadClass(className);
...
} catch (ClassNotFoundException e) {
ComponentName component = PluginUtil.getComponent(intent);
...
Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
...
return newActivity(activity);
}
return newActivity(mBase.newActivity(cl, className, intent));
}
newActivity 中先判断用宿主的 classLoader 能否加载启动的类,如果能加载说明时宿主的 Activity,直接启动。否则调用 PluginUtil.getComponent 得到原始的插件 Intent,然后使用插件的 ClassLoader 来加载原始的插件 Intent。
Service 的支持也是采用“欺上瞒下”的方法,插件 Service 的启动实际上是通过 VirtualAPK 的代理 Service 启动的。
正常的 Service 启动会调用 ContextImpl 的 startServiceCommon 方法。
startServiceCommon 方法如下:
private ComponentName startServiceCommon(Intent service, boolean requireForeground,
UserHandle user) {
try {
...
ComponentName cn = ActivityManager.getService().startService(
mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
getContentResolver()), requireForeground,
getOpPackageName(), user.getIdentifier());
...
}
可以看出 startServiceCommon 方法调用了 AMS 的 startService。
为了调用 VirtualAPK 的代理 Service,VirtualAPK 的 pluginManager hook 替换了 ActivityManager 为自定义的 ActivityManagerProxy。采用动态代理的方式在 AMS startService 之前修改了 intent。
PluginManager 的构造方法会调用 hookCurrentProcess 方法。
protected PluginManager(Context context) {
...
hookCurrentProcess();
}
protected void hookCurrentProcess() {
hookInstrumentationAndHandler();
hookSystemServices();
hookDataBindingUtil();
}
hookCurrentProcess 的第二步就是 hookSystemServices,它主要 hook 了 ActivityManager。
protected void hookSystemServices() {
...
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
defaultSingleton = Reflector.on(ActivityManager.class).field("IActivityManagerSingleton").get();
}
...
IActivityManager origin = defaultSingleton.get();
IActivityManager activityManagerProxy = (IActivityManager) Proxy.newProxyInstance(mContext.getClassLoader(), new Class[] { IActivityManager.class },
createActivityManagerProxy(origin));
// Hook IActivityManager from ActivityManagerNative
Reflector.with(defaultSingleton).field("mInstance").set(activityManagerProxy);
...
}
可以看出使用 Proxy.newProxyInstance 生成了一个动态代理的 IActivityManager 对象。这样在调用 IActivityManager 的原始方法时,会转而调用 activityManagerProxy 代理的 invoke 方法。
createActivityManagerProxy 方法如下:
protected ActivityManagerProxy createActivityManagerProxy(IActivityManager origin) throws Exception {
return new ActivityManagerProxy(this, origin);
}
createActivityManagerProxy 构造了一个 ActivityManagerProxy 对象,并传递原始的 ActivityManager。
public class ActivityManagerProxy implements InvocationHandler {
...
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startService".equals(method.getName())) {
try {
return startService(proxy, method, args);
} catch (Throwable e) {
Log.e(TAG, "Start service error", e);
}
}
...
}
可以看出 startService 方法会调用 ActivityManagerProxy 的 startService。
protected Object startService(Object proxy, Method method, Object[] args) throws Throwable {
...
return startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_START_SERVICE);
}
startService 方法会先判断启动的是宿主的 Service 还是插件的 Service。如果是插件的 Service,直接调用 startDelegateServiceForTarget。startDelegateServiceForTarget 会启动代理 Service。
protected ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command);
return mPluginManager.getHostContext().startService(wrapperIntent);
}
protected Intent wrapperTargetIntent(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
// fill in service with ComponentName
target.setComponent(new ComponentName(serviceInfo.packageName, serviceInfo.name));
String pluginLocation = mPluginManager.getLoadedPlugin(target.getComponent()).getLocation();
// start delegate service to run plugin service inside
boolean local = PluginUtil.isLocalService(serviceInfo);
Class<? extends Service> delegate = local ? LocalService.class : RemoteService.class;
Intent intent = new Intent();
intent.setClass(mPluginManager.getHostContext(), delegate);
intent.putExtra(RemoteService.EXTRA_TARGET, target);
intent.putExtra(RemoteService.EXTRA_COMMAND, command);
intent.putExtra(RemoteService.EXTRA_PLUGIN_LOCATION, pluginLocation);
if (extras != null) {
intent.putExtras(extras);
}
return intent;
}
可以看出 startDelegateServiceForTarget 修改了 intent,它将原始 intent 作为参数直接传递给了新 intent。注意到 intent 本身实现了 Parcelable 接口,它可以作为参数传递。
如果是本地 Service,就启动 LocalService 作为代理。如果是多进程 Service,就启动 RemoteService 作为代理。
查看 VirtualAPK 的 manifest。
可以看出 LocalService 运行在主进程,RemoteService 运行在 daemon 新进程。
LocalService 的 onStartCommand 代码如下:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
...
int command = intent.getIntExtra(EXTRA_COMMAND, 0);
...
switch (command) {
case EXTRA_COMMAND_START_SERVICE: {
...
try {
service = (Service) plugin.getClassLoader().loadClass(component.getClassName()).newInstance();
...
attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am);
service.onCreate();
this.mPluginManager.getComponentsHandler().rememberService(component, service);
} catch (Throwable t) {
return START_STICKY;
}
}
service.onStartCommand(target, 0, this.mPluginManager.getComponentsHandler().getServiceCounter(service).getAndIncrement());
break;
}
...
return START_STICKY;
}
可以看出 LocalService 在 onStartCommand 时会从 intent 取出 command,代表需要执行哪些操作。比如 startService 会调用 classloader 加载 Service 的 class,然后依次调用 service 的 attach、onCreate、onStartCommand 方法,完成插件 Service 的启动。
和 StubActivity、LocalService 一样,VirtualAPK 在 AndroidManifest 提供了一个代理 ContentProvider,即 RemoteContentProvider。
正常流程下通过 getContentResolver 查询 ContentProvider 时,实际上调用了 ContextImpl 的 ApplicationContentResolver 去查询。
VirtualAPK 在加载插件时,会产生一个 LoadedPlugin,并且为 LoadedPlugin 创建 pluginContext,pluginContext 的 getContentResolver 方法会构造一个 PluginContentResolver。
@Override
public ContentResolver getContentResolver() {
return new PluginContentResolver(getHostContext());
}
正常流程下 ApplicationContentResolver 查询时会调用 ActivityThread 的 acquireProvider 方法。
在插件的 PluginContentResolver 中会调用 PluginManager 的 getIContentProvider 方法。
@Override
protected IContentProvider acquireProvider(Context context, String auth) {
if (mPluginManager.resolveContentProvider(auth, 0) != null) {
return mPluginManager.getIContentProvider();
}
return super.acquireProvider(context, auth);
}
PluginManager 的 getIContentProvider 方法调用了 hookIContentProviderAsNeeded。 hookIContentProviderAsNeeded 会 hook ActivityThread 中的 IContentProvider.
public synchronized IContentProvider getIContentProvider() {
if (mIContentProvider == null) {
hookIContentProviderAsNeeded();
}
return mIContentProvider;
}
hookIContentProviderAsNeeded 方法如下:
protected void hookIContentProviderAsNeeded() {
Uri uri = Uri.parse(RemoteContentProvider.getUri(mContext));
mContext.getContentResolver().call(uri, "wakeup", null, null);
try {
...
ActivityThread activityThread = ActivityThread.currentActivityThread();
Map providerMap = Reflector.with(activityThread).field("mProviderMap").get();
Iterator iter = providerMap.entrySet().iterator();
while (iter.hasNext()) {
...
if (auth.equals(RemoteContentProvider.getAuthority(mContext))) {
...
IContentProvider rawProvider = (IContentProvider) provider.get(val);
IContentProvider proxy = IContentProviderProxy.newInstance(mContext, rawProvider);
mIContentProvider = proxy;
Log.d(TAG, "hookIContentProvider succeed : " + mIContentProvider);
break;
}
}
} catch (Exception e) {
Log.w(TAG, e);
}
}
可以看出 hookIContentProviderAsNeeded 首先解析得到占坑的代理 Provider,即 RemoteContentProvider 的 Uri,然后立即 call 了占坑的 RemoteContentProvider。接着从 ActivityThread 的 mProviderMap 中查找对应 auth 的 provider。如果存在和 RemoteContentProvider 相同 auth 的 provider,就采用动态代理的方式 hook 掉这个 provider,替换为自己的 IContentProviderProxy 类。
IContentProviderProxy 是一个代理接口,它实现了 InvocationHandler,直接看它的 invoke 方法。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.v(TAG, method.toGenericString() + " : " + Arrays.toString(args));
wrapperUri(method, args);
try {
return method.invoke(mBase, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
可以看出 invoke 方法封装了 method、args,然后调用原始 IContentProvider 的方法。
wrapperUri 会调用 PluginContentResolver 的 wrapperUri 方法。
@Keep
public static Uri wrapperUri(LoadedPlugin loadedPlugin, Uri pluginUri) {
String pkg = loadedPlugin.getPackageName();
String pluginUriString = Uri.encode(pluginUri.toString());
StringBuilder builder = new StringBuilder(RemoteContentProvider.getUri(loadedPlugin.getHostContext()));
builder.append("/?plugin=" + loadedPlugin.getLocation());
builder.append("&pkg=" + pkg);
builder.append("&uri=" + pluginUriString);
Uri wrapperUri = Uri.parse(builder.toString());
return wrapperUri;
}
wrapperUri 将插件的存放位置、包名、uri 三者作为参数成为新的 uri。它的作用是在代理 RemoteContentProvider 分发增删改查操作时,再把这些信息提取出来。
这里以 RemoteContentProvider 的 query 方法为例。
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
ContentProvider provider = getContentProvider(uri);
Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI));
if (provider != null) {
return provider.query(pluginUri, projection, selection, selectionArgs, sortOrder);
}
return null;
}
可以看出 RemoteContentProvider 的 query,首先调用了 getContentProvider 找到真正的插件 Provider。
private ContentProvider getContentProvider(final Uri uri) {
...
synchronized (sCachedProviders) {
LoadedPlugin plugin = pluginManager.getLoadedPlugin(uri.getQueryParameter(KEY_PKG));
if (plugin == null) {
try {
pluginManager.loadPlugin(new File(uri.getQueryParameter(KEY_PLUGIN)));
} catch (Exception e) {
Log.w(TAG, e);
}
}
final ProviderInfo providerInfo = pluginManager.resolveContentProvider(auth, 0);
if (providerInfo != null) {
RunUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
LoadedPlugin loadedPlugin = pluginManager.getLoadedPlugin(uri.getQueryParameter(KEY_PKG));
ContentProvider contentProvider = (ContentProvider) Class.forName(providerInfo.name).newInstance();
contentProvider.attachInfo(loadedPlugin.getPluginContext(), providerInfo);
sCachedProviders.put(auth, contentProvider);
} catch (Exception e) {
Log.w(TAG, e);
}
}
}, true);
return sCachedProviders.get(auth);
}
}
return null;
}
可以看出 getContentProvider 方法把之前组合好的 uri 通过 getQueryParameter 的方式查到里面携带的信息参数。
pluginManager.loadPlugin(new File(uri.getQueryParameter(KEY_PLUGIN)));
比如用来从指定地址加载插件。
然后用 Class.forName 构造了 ContentProvider,attach 了 providerInfo,并且做了 provider 缓存。
ContentProvider contentProvider = (ContentProvider) Class.forName(providerInfo.name).newInstance();
contentProvider.attachInfo(loadedPlugin.getPluginContext(), providerInfo);
至此分发到了插件的 ContentProvider 并调用了插件 Provider 的方法。
BroadcastReceiver 的支持比较简单,直接将插件 AndroidManifest 中的静态广播转为动态广播,插件原有的动态广播放入宿主动态注册。
public LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception {
...
// Register broadcast receivers dynamically
Map<ComponentName, ActivityInfo> receivers = new HashMap<ComponentName, ActivityInfo>();
for (PackageParser.Activity receiver : this.mPackage.receivers) {
receivers.put(receiver.getComponentName(), receiver.info);
BroadcastReceiver br = BroadcastReceiver.class.cast(getClassLoader().loadClass(receiver.getComponentName().getClassName()).newInstance());
for (PackageParser.ActivityIntentInfo aii : receiver.intents) {
this.mHostContext.registerReceiver(br, aii);
}
}
...
这一步是在 LoadedPlugin 的构造方法中解析 BroadcastReceiver 完成的。
可以看出,对于插件 APK 静态注册的某些广播,比如开机广播,宿主 APK 是无法实现其功能的,因为它们被转换为了动态广播,手机开机时,应用进程没有被拉起,无法动态监听开机广播。
VirtualAPK 加载插件后会得到一个 LoadedPlugin,它是插件的核心类,类似于 Android 自身的 LoadedApk。LoadedPlugin 通过 insertDex 方法把插件 APK 的 Dex 和宿主 APK 的 Dex 合并,这样宿主 APK 包含了所有的 Dex,宿主可以加载插件 APK 的类。 同时 LoadedPlugin 的 DexClassLoader 在构造时传入了宿主的 classLoader,所以插件可以加载宿主的类。
VirtualAPK 将宿主的 resource 和插件的 resource 合并,最后把得到的新 Resources 更新给了每个插件的 LoadedPlugin。所以宿主 APK 和 插件 APK 可以相互共享资源。
VirtualAPK 通过“欺上瞒下”的方式分别 hook 了 Instrumentation、ActivityManager、IContentProvider,并将原始的调用 hook 为对占坑的 StubActivity、LocalService、RemoteContentProvider 的调用,在重新分发的过程中又解析了 intent 和 uri,并且加载了插件中的对应组件,调用原始组件的方法。BroadcastReceiver 和其他三个组件的方式有所不同,采用了静态广播转动态广播的方法。