Shadow插件化框架原理
sample项目调试
1 | sample-host-debug.apk -> projects/sample/source/sample-host |
下载Shadow工程后,在根目录执行./gradlew build
等待编译成功,然后通过adb安装:
1 | adb install projects/sample/source/sample-host/build/outputs/apk/debug/sample-host-debug.apk |
在sample-host-debug.apk的assets目录下包含pluginmanager.apk
和plugin-debug.zip
,其中plugin-debug.zip包含的文件:
1 | . |
config.json文件内容如下:
1 | { |
插件启动过程
在sample代码中启动插件Activity会通过一个跳板PluginLoadActivity
,跳板Activity会展示插件loading view,从而避免插件启动过程中的黑屏给用户带来困惑。
zenuml PluginLoadActivity."startPlugin run" { mPluginManager = HostApplication.loadPluginManager { PluginManager = Shadow.getPluginManager { new DynamicPluginManager() } } DynamicPluginManager.enter { updateManagerImpl { //从pluginmanager.apk中加载manager new SamplePluginManager() } SamplePluginManager.enter { onStartActivity { "展示pluginmanager.apk的loading" { "👇① 子线程run" } } } } }
zenuml SamplePluginManager."☝① onStartActivity run" { FastPluginManager.installPlugin FastPluginManager.loadPlugin(sample-base) { loadPluginLoaderAndRuntime { bindPluginProcessService { "独立进程启动PluginProcessService" } loadRunTime { PpsController."👇① loadRuntime" } loadPluginLoader { PpsController."👇② loadPluginLoader" } } BinderPluginLoader."👇③ loadPlugin" } FastPluginManager.loadPlugin(sample-plugin-app) FastPluginManager.callApplicationOnCreate(sample-base) { BinderPluginLoader.callApplicationOnCreate } FastPluginManager.callApplicationOnCreate(sample-plugin-app) BinderPluginLoader.convertActivityIntent BinderPluginLoader.startActivityInPluginProcess }
加载Runtime
zenuml PluginProcessService."☝① (binder)loadRuntime" { InstalledApk = BinderUuidManager.getRuntime DynamicRuntime.loadRuntime { "runtime挂在ClassLoader parent" } }
PluginProcessService
类位于宿主apk中,但是是在独立进程中启动服务。
runtime加载后ClassLoader的结构是:BootClassLoader
<- RuntimeClassLoader
<- PathClassLoader
。
1 | dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/~~ENGi6EgoGsbDZ8Ntp66PEg==/com.tencent.shadow.sample.host-tXgX3vZIGlZxM6yNcDzx3A==/base.apk"],nativeLibraryDirectories=[/data/app/~~ENGi6EgoGsbDZ8Ntp66PEg==/com.tencent.shadow.sample.host-tXgX3vZIGlZxM6yNcDzx3A==/lib/x86_64, /system/lib64, /system_ext/lib64]]] |
加载Loader
loader和runtime一样都是在PluginProcessService中进行加载。
zenuml PluginProcessService."☝② (binder)loadPluginLoader" { PluginProcessService."InstalledApk = BinderUuidManager.getPluginLoader" PluginLoaderImpl = LoaderImplLoader.load { "ApkClassLoader.loadClass(LoaderFactoryImpl)" LoaderFactoryImpl.buildLoader { new DynamicPluginLoader new PluginLoaderBinder(DynamicPluginLoader) } } PluginLoaderBinder.setUuidManager }
加载插件
zenuml DynamicPluginLoader."☝③ loadPlugin(binder)" { SamplePluginLoader.loadPlugin { "ShadowPluginLoader#loadPlugin" { "new PluginServiceManager" LoadPluginBloc.loadPlugin { PluginClassLoader = LoadApkBloc.loadPlugin "PluginManifest = PluginClassLoader.loadPluginManifest" "ApplicationInfo = CreatePluginApplicationInfoBloc.create" "new PluginPackageManagerImpl" "Resources = CreateResourceBloc.create" "new ShadowAppComponentFactory" "ShadowApplication = CreateApplicationBloc.createShadowApplication" ComponentManager.addPluginApkInfo "PluginParts注册到map" PluginPartInfoManager.addPluginInfo } } } }
每个插件包中都包含一个com.tencent.shadow.core.manifest_parser.PluginManifest
类,用户记录当前插件包中有那些activity、service、receiver、provider,加载插件后这些信息会被注册到ComponentManager
1 | static { |
宿主进程和插件进程binder
zenuml BinderPluginLoader.startActivityInPluginProcess { PluginLoaderBinder."binder onTransact" { DynamicPluginLoader.startActivityInPluginProcess } }
Activity插件方案
Shadow解决Activity等组件生命周期的方法解析这篇文章是插件框架作者对于Activity生命周期的介绍。
比如插件中有个MainActivity,如下:
经过Javassist
处理后类图结构(替换代码实现在com.tencent.shadow.core.transform.specific.ActivityTransform
):
下面看下整个Activity的详细继承关系,包括插件Activity与壳子Activity的关系:
启动其他插件Activity
com.tencent.shadow.core.runtime.ShadowContext#startActivity
zenuml ShadowContext.startActivity { ComponentManager.startActivity { "pluginIntent.toActivityContainerIntent" ShadowContext.superStartActivity } }
Activity的启动时会把目标Activity替换成PluginDefaultProxyActivity
,即PluginContainerActivity
。比如,我们通过如下方式启动一个插件中的Activity:
1 | Intent intent = new Intent(); |
经过ComponentManager转换后的Intent是:
1 | Intent { cmp=com.tencent.shadow.sample.host/com.tencent.shadow.sample.plugin.runtime.PluginDefaultProxyActivity (has extras) } |
资源的处理
资源的生成逻辑在com.tencent.shadow.core.loader.blocs.CreateResourceBloc#create
中。
当Android版本高于8.1时,ApplicationInfo#sharedLibraryFiles添加了插件apk路径来实现:
1 | sharedLibraryFiles=[ |
然后通过packageManager.getResourcesForApplication(applicationInfo)
来生成新的Resources。
如果Android版本不高于8.1,则通过自定义MixResources
同时持有宿主和插件的资源,当获取资源时首先获取宿主的,当获取不到时再从插件中获取。
资源句柄最终会赋值给com.tencent.shadow.core.runtime.ShadowContext#mPluginResources
。
为了解决合并资源后资源ID的冲突,Shadow把插件的资源ID的package-id字段给位了0x7E,可以看下sample-app项目下面的的build.gradle的配置:
1 | // 将插件的资源ID分区改为和宿主0x7F不同的值 |
在Sample中添加插件
插件目录projects/sample/source/sample-plugin/my-plugin-app
。需要注意的一点是shadow字段不要添加到my-plugin-app/build.gradle
,而是要添加到projects/sample/source/sample-plugin/sample-app/build.gradle
中,如下:
1 | shadow { |
插件apk的applicationId的配置:
1 | android { |
参考资料
Home Wiki
Sample Readme
Shadow解决Activity等组件生命周期的方法解析
Android插件化框架-Shadow原理解析
腾讯插件框架Shadow解析之动态化和插件加载
Shadow的接入记录
Android Tencent Shadow 插件接入指南