2.20 模块化&插件化
2.13.1 VirtualAPK
-
框架架构图(来自官网)
-
使用
-
loadApk
, 加载插件
// PluginManager.java
public void loadPlugin(File apk) throws Exception {
...
LoadedPlugin plugin = createLoadedPlugin(apk);
...
// ConcurrentHashMap保存plugin
this.mPlugins.put(plugin.getPackageName(), plugin);
...
}
protected LoadedPlugin createLoadedPlugin(File apk) throws Exception {
return new LoadedPlugin(this, this.mContext, apk);
}
public LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception {
...
// 初始化插件的PackageManager, Resource, ClassLoader(Dex)
this.mPackageManager = createPluginPackageManager();
this.mPluginContext = createPluginContext(null);
...
// 把插件的native so拷贝宿主的nativedir下
tryToCopyNativeLib(apk);
...
}
Activity
, 通过hookInstrumentation
, 且在CoreLibrary下的AndroidManifest
增加占位
protected PluginManager(Context context) {
// 初始化PluginManager时hook掉Instrumentation, IActivityManager和DataBindingUtil
...
hookCurrentProcess();
}
protected void hookCurrentProcess() {
hookInstrumentationAndHandler();
hookSystemServices();
hookDataBindingUtil();
}
// 1. hook Instrumentation, 由于Context#startActivity()实际调.
// 用Instrumentation#execStartActivity, 因此这里可以把目标替换成占位Activity
// 从而绕过PackageManager检查宿主Manifest是否注册了Activity, 另外当
// 启动Activity, 即可ActivityThread#performLaunchActivity时, 会调
// 用Instrumentation#newActivity, 在newActivity替换成目标插件实
// 例, 从而完成对插件Activity的加载
protected void hookInstrumentationAndHandler() {
// 获取ActivityThread
ActivityThread activityThread = ActivityThread.currentActivityThread();
// 获取原来的Instrumentation
Instrumentation baseInstrumentation = activityThread.getInstrumentation();
final VAInstrumentation instrumentation = createInstrumentation(baseInstrumentation);
// 通过反射, 把自定义VAInstrumentation赋值给ActivityThread
Reflector.with(activityThread).field("mInstrumentation").set(instrumentation);
Handler mainHandler = Reflector.with(activityThread).method("getHandler").call();
Reflector.with(mainHandler).field("mCallback").set(instrumentation);
this.mInstrumentation = instrumentation;
...
}
// 2. 启动Activity, 即Instrumentation#execStartActivity
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);
}
protected void injectIntent(Intent intent) {
mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);
...
// resolve intent with Stub Activity if needed
this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
}
public void markIntentIfNeeded(Intent intent) {
// search map and return specific launchmode stub activity
...
intent.putExtra(Constants.KEY_IS_PLUGIN, true);
intent.putExtra(Constants.KEY_TARGET_PACKAGE, targetPackageName);
intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName);
dispatchStubActivity(intent);
}
private void dispatchStubActivity(Intent intent) {
ComponentName component = intent.getComponent();
String targetClassName = intent.getComponent().getClassName();
LoadedPlugin loadedPlugin = mPluginManager.getLoadedPlugin(intent);
ActivityInfo info = loadedPlugin.getActivityInfo(component);
...
int launchMode = info.launchMode;
Resources.Theme themeObj = loadedPlugin.getResources().newTheme();
themeObj.applyStyle(info.theme, true);
// 根据targetClassName, launchMode等信息, 找到占位Activity
String stubActivity = mStubActivityInfo.getStubActivity(targetClassName, launchMode, themeObj);
...
intent.setClassName(mContext, stubActivity);
}
// 3. 回到真正实例化Activity的方法, 即Instrumentation#newActivity
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
try {
cl.loadClass(className);
Log.i(TAG, String.format("newActivity[%s]", className));
} catch (ClassNotFoundException e) {
ComponentName component = PluginUtil.getComponent(intent);
...
LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(component);
...
// 通过插件的DexClassLoader把插件Activity实例化
Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
activity.setIntent(intent);
...
return newActivity(activity);
}
return newActivity(mBase.newActivity(cl, className, intent));
}
Activity
插件实现原理总结:- hook
Instrumentation
- 在宿主的Manifest埋下占位
Activity
, 在Instrumentation#execStartActivity
中替换成占位Activity
, 绕过AMS
的检查 - 在
Instrumentation#newActivity
中再实例化插件Activity
并返回
- hook
Resource
, 资源加载
protected Resources createResources(Context context, String packageName, File apk) throws Exception {
if (Constants.COMBINE_RESOURCES) {
// 插件资源合并到宿主中, 插件可以访问宿主的资源
return ResourcesManager.createResources(context, packageName, apk);
} else {
// 生成新的AssetManager, 并把插件的资源加载...
Resources hostResources = context.getResources();
AssetManager assetManager = createAssetManager(context, apk);
return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
}
}
public static synchronized Resources createResources(Context hostContext, String packageName, File apk) throws Exception {
...
Resources resources = ResourcesManager.createResourcesSimple(hostContext, apk.getAbsolutePath());
ResourcesManager.hookResources(hostContext, resources);
return resources;
}
private static Resources createResourcesSimple(Context hostContext, String apk) throws Exception {
Resources hostResources = hostContext.getResources();
Resources newResources = null;
AssetManager assetManager;
Reflector reflector = Reflector.on(AssetManager.class).method("addAssetPath", String.class);
...
// 加载宿主资源
assetManager = hostResources.getAssets();
reflector.bind(assetManager);
...
// 加载插件资源
List<LoadedPlugin> pluginList = PluginManager.getInstance(hostContext).getAllLoadedPlugins();
for (LoadedPlugin plugin : pluginList) {
final int cookie3 = reflector.call(plugin.getLocation());
if (cookie3 == 0) {
throw new RuntimeException("createResources failed, can't addAssetPath for " + plugin.getLocation());
}
}
...
newResources = new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
// lastly, sync all LoadedPlugin to newResources
for (LoadedPlugin plugin : pluginList) {
plugin.updateResources(newResources);
}
// 返回加载好宿舍和插件资源的Resource对象
return newResources;
}
- 总结
- 资源的加载包含两种模式, 模式一即参考Instant Run, 创建AssetManager, 再通过反射调用addAssetPath加载插件的资源, 模式二即合并插件和宿主资源, 也是类似, 创建AssetManager, 再通过反射调用addAssetPath加载把宿主和插件的资源均加载进来
参考
- dynamic-load-apk
- DYNAMIC-LOAD-APK插件原理整理
- VirtualAPK 资源篇
-
[深度 滴滴插件化方案 VirtualApk 源码解析](https://mp.weixin.qq.com/s?__biz=MzAxMTI4MTkwNQ==&mid=2650823488&idx=1&sn=2976c8ddc0c206149b14c527260f7766&chksm=80b78fdeb7c006c8a9585db794c51e799049ec50d23c4d738c78d77454f6b0291227a00e2def&mpshare=1&scene=1&srcid=0712oTUswGWi172UK0Azpg4i&key=8652b956ca1971a47b1e263b435230c7469d30646ddbe6ce2fb781033d6eba5215c9fd7e5eaf0bd73dd5da279b32dd901261d5e55bf32997bc333ad8a059e095e2193a5baa805447fc49cd315fca4404&ascene=0&uin=MTI0NjM4NTEyMA%3D%3D&devicetype=iMac+MacBookPro11%2C4+OSX+OSX+10.11.1+build(15B42)&version=12010110&nettype=WIFI&fontScale=100&pass_ticket=mswE9bS3QeCxTOoepaUWh9VXHxeYMosdkHkAydyR09JHQkVe%2BAJHCCnPQrRpBfQN)