Shadow的接入记录
零、结构
项目结构中主要有宿主,也就是我们平常的app,它依赖introduce-shadow-lib完成对sample-manager的管理,宿主传入需要跳转的插件sample-plugin的参数,让sample-manager完成对插件的管理和调用。插件sample-plugin靠sample-runtime和sample-loader的支撑完成加载运行时所需要的环境。sample-host、sample-manager、sample-plugin虽然有依次调用,但并没有依赖关系。
一、插件配置
插件需要依赖runtime和loader两个模块,所以需要配置这两个模块。我们最需要更改是partKey,apkName,apkName和版本管理的三个参数。对于loader和runtime的包名和别名都可以视情况更改。
shadow {
packagePlugin {
pluginTypes {
debug {
loaderApkConfig = new Tuple2('sample-loader-debug.apk', ':sample-loader:assembleDebug')
runtimeApkConfig = new Tuple2('sample-runtime-debug.apk', ':sample-runtime:assembleDebug')
pluginApks {
pluginApk1 {
businessName = 'sample-plugin'//businessName相同的插件,context获取的Dir是相同的。businessName留空,表示和宿主相同业务,直接使用宿主的Dir。
partKey = 'sample-plugin'//注意此处的partKey后续会用到
buildTask = 'assemblePluginDebug'
apkName = 'Vlog-plugin-debug.apk'
apkName = 'Vlog/build/outputs/apk/plugin/debug/Vlog-plugin-debug.apk'
}
}
}
release {
loaderApkConfig = new Tuple2('sample-loader-release.apk', ':sample-loader:assembleRelease')
runtimeApkConfig = new Tuple2('sample-runtime-release.apk', ':sample-runtime:assembleRelease')
pluginApks {
pluginApk1 {
businessName = 'demo'
partKey = 'sample-plugin'
buildTask = 'assemblePluginRelease'
apkName = 'plugin-app-plugin-release.apk'
apkPath = 'plugin-app/build/outputs/apk/plugin/release/plugin-app-plugin-release.apk'
}
}
}
}
loaderApkProjectPath = 'sample-loader'
runtimeApkProjectPath = 'sample-runtime'
//插件版本管理
version = 4
compactVersion = [1, 2, 3]
uuidNickName = "1.1.5"
}
}
我们可以集成多个pluginApk来更新多个插件,集成方式和在pluginApks层级下面的pluginApk1方式一致,我们可以新增pluginApk2,pluginApk3等等。插件自身最开始的applicationId可以不用管它,我们新的配置需和宿主同applicationid,和我们的defaultConfig是同级的。不然会报错插件和宿主包名不一致。
// 将插件applicationId设置为和宿主相同
productFlavors {
plugin {
applicationId "com.tencent.shadow.sample.host"
}
}
如果有多个插件,我们需要保证我的partKey唯一,uuidNickName最好也保持唯一,不管多少个插件,每个插件的applicationId都要和宿主一致。
二、个人实现
2.1、introduce-shadow-lib
该模块是对插件管理模块的进行管理的模块,依赖于宿主。在这个项目中我们需要给到manager-project编译好的apk路径。在shadow的示例中是在本地编译然后adb推送到手机上,指定固定的路径。但我们的实现肯定不能如此,我们需要从远端下载保存到本地,动态的设置manager-project apk的路径。下载同时需要考虑网络无法下载,下载中断,判断是否已下载,是否需要更新等工作。
2.2、manager-project
插件管理需要做的是对插件的下载、验证、更新、解析、安装等工作。shadow本身没有实现对插件的下载、验证、更新工作,我们需要实现我们自己的插件管理工作。目前按adb push的方式直接放在手机下,后续更改未下载方式。
2.3、宿主传参
这些参数本来是写死在manager-project里面的,但我们的业务更加的灵活,写死肯定是不行,我们需要动态的传入这些参数来实现动态插件化。
参数键 | 参数值 | 说明 |
---|---|---|
KEY_PLUGIN_ZIP_PATH | /data/local/tmp/plugin-debug.zip | 插件的本地地址,插件需要先下载下来,可以提前异步下载也可以等到调用时再下载 |
KEY_PLUGIN_PART_KEY | sample-plugin | 插件的键名,在插件模块的build gradle里有配置,需相同 |
KEY_ACTIVITY_CLASSNAME | com.jiayz.videorecorder.MainActivity | 需要启动的插件的包名加activity类名 |
2.4、SDK的自发布
在前面的几个模块有大量的依赖腾讯的SDK,它们已经被发布到了maven。但是shadow不推荐我们在生产环境中使用他们已经发布的库,推荐我们自己编译发布SDK更改依赖地址
三、包名的更改
我们如果想移植到自己的项目中去,最好还是把包名改一下,不然顶这个tencent.shadow.samplexxx也不好,但是在改包名的过程中有一些需要注意的地方,不然会导致项目运行出错。
3.1、不能动的包和类名
shadow规定了有几个不能动的包名和类名,他在他的sdk已经写死包名和类名,分别是:
-
- manager-project下的com.tencent.shadow.dynamic.impl包下的两个类-ManagerFactoryImpl和WhiteList
- sample-loader下的com.tencent.shadow.dynamic.loader.impl.DynamicPluginLoader类
3.2、需同步的包名
我们更改完我们的包名后需要同步的更改我们的包名,不然会出现ClassNotFound的异常,需要同步的包如下:
-
- 如果我们更改了introduce-shadow-lib模块下的MainPluginProcessService的包名,我们需要同步manager-project模块下的原先SamplePluginManager类中的getPluginProcessServiceName方法返回的包名,保证和修改的一致。
- 如果我们更改了sample-runtime原先的包名com.tencent.shadow.sample.runtime,那么相应的我们需要同步introduce-shadow-lib模块下Manifest注册的活动,不然会提示不能启动活动,是否已在Manifest下注册该活动。
四、插件的编译发布
先编译整个项目,确保runtime和loader生成了apk,且apk路径和你在插件中配置的runtime和loaderapk路径一致,否则会报错找不到文件。
在你插件都依赖成功的情况下,可以命令行运行gradle命令
./gradlew packageDebugPlugin
但我执行命令失败了,其实也可以在AndroidStudio右侧的gradle工具栏点击执行。
执行完成后会在插件app的输出目录下发现plugin目录,里面有plugin.apk,再执行plugin-manager下的plugin任务的
执行完成后,项目根build下看到一个plugin-debug.zip文件,根据业务自己更改zip名字。
此时插件编译完成。后续我们可以给到后台上传发布新插件,供后续的下载更新。 个人见解不尽正确,欢迎大家交流指正。
参考链接:
转载自:https://juejin.cn/post/7160545636280958983