ShadowSample
使用腾讯 Shadow 实现插件化的 Demo
下面记录一下接入流程和遇到的问题,方便以后查看。
结构
整个 Demo 由 3 个工程构成,分别是宿主 App,插件管理 App,插件 App,三个工程相互完全独立。
|--HostApp //宿主
|
|--PluginManager //负责加载插件,管理插件
|
|--BusinessApp //插件 app
|--plugin-app //业务 app
|--sample-loader //配置了插件 Activity 到壳 Activity 的对应关系等
|--sample-runtime //定义了宿主中占位的 Activity 等
Shadow 主要接入流程
- Clone Shadow 的代码 Shdow Github 地址,然后编译,上传本地 maven 库或者远程 maven 库,以便后续应用,不建议直接引用 sdk 代码,里面的库分很多块,三个工程各自需要引用的模块不一样,操作不方便,用 maven 可以分别按需引用。
- 参照腾讯 Shadow 的 Sample 中 Maven 下面的 sample(结构也是一样的三个工程),分别创建工程,引用相应的库,实现必要的接口(占位的 Activity、Service,插件管理类等),分别编译通过。
PluginManager 编译之后生成 apk 文件,BusinessApp 编译之后生成一个 zip 包,需要将他们分别放到宿主指定的文件目录之下,让宿主能够加载到。
BusinessApp 编译用如下命令:
gradlew packageDebugPlugin
放文件的位置是宿主中配置的,pluginManager 的 apk 存放位置在初始化管理类的时候定义的,关键代码是 `FixedPathPmUpdater fixedPathPmUpdater= new FixedPathPmUpdater(new File(PLUGIN_MANAGER_APK_FILE_PATH));`
插件 zip 包的位置可以在启动插件四大组件的时候传:
bundle.putString("plugin_path", "/data/local/tmp/plugin-debug.zip");
也可以直接在 PluginManager 中硬编码:InstalledPlugin installedPlugin = installPlugin(pluginZipPath, null, true);
在 PluginManager 里面根据 part_key 来判断是哪个插件,然后直接判断出地址要方便一些- 安装宿主 App,测试是否可以加载插件 app 的页面、服务等。
==第二步的步骤很多==,可以参考这里
小注意点:
- 插件的包名和宿主的包名要一致
- 有一些类要求路径和类名是固定的,像 loader 里面的 CoreLoaderFactoryImpl,写的时候要注意,弄错了很难排查问题。
- 占位的 activity 在宿主的 manifest 里面注册,在插件的 runtime 里面声明,在插件的 loader 里面写对应关系,类似这些地方要注意,包类名要对应上,不要写错
遇到问题:
1.实际按照这个博客做的时候,最后从宿主 App 跳到插件的时候报了如下错,
java.lang.ClassCastException: Cannot cast androidx.core.app.CoreComponentFactory to com.tencent.shadow.core.runtime.ShadowAppComponentFactory
没能找到具体原因,最后重新建了一个 BusinessApp 工程引入官方 demo 中 Maven 下面的 Plugin-project,改了相关的配置,然后才测试通过的;
2.在插件的 app 配置时,业务 app 的 build.gradle 配置 loader 和 runtime 的 apk 路径已经插件模块的 partKey 等信息的时候,报了 apkName 无法识别的错误,没有解决,最后注释了这一行,后面也能测试通过,暂时不知道还有什么影响。
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 = 'my-plugin'
//businessName 相同的插件,context 获取的 Dir 是相同的。businessName 留空,表示和宿主相同业务,直接使用宿主的 Dir。
partKey = 'my-plugin'
buildTask = 'assemblePluginDebug'
// apkName = 'plugin-app-plugin-debug.apk'
apkPath = 'plugin-app/build/outputs/apk/plugin/debug/plugin-app-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 = 'my-plugin'
partKey = 'my-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"
}
}