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 插件接入指南