1.Android 插件化腾讯零反射shadow 源码依赖方式完整运行

Tencent Shadow是业界知名的“零反射”、“零Hack”的Android插件化框架,以其稳定性和灵活性著称。本篇博客将以最贴近开发的源码依赖方式(projects/sample/source) 为蓝本,手把手带你运行腾讯官方Demo

1.源码的地址:

https://github.com/Tencent/Shadow/

Sample 文档projects/sample/README.md

然后在IDE中选择sample-app或sample-host模块直接运行,分别体验同一份代码在正常安装情况下和插件情况下的运行情况。

Shadow的所有代码都位于`projects`目录下的3个目录,分别是:

*   `sdk`包含SDK的所有代码
*   `test`包含SDK的自动化测试代码
*   `sample`包含演示代码

其中`sample`应该是大家体验Shadow的最佳环境。 详见`sample`目录中的[README](https://kkgithub.com/Tencent/Shadow/blob/master/projects/sample/README.md)介绍。

官方的说明:README.md

在这个Sample目录下,提供了两种示例工程:

一种是源码,一种是maven,
我们今天看源码的过程!

方式一:源码依赖SDK的Sample (projects/sample/source)

适用场景:学习、研究、修改Shadow源码时使用

核心特点:
Shadow SDK以源码形式依赖
修改Shadow源码后可以直接运行生效
所有模块在一个项目中
对 Shadow SDK 的依赖是直接引用源码(includeBuild '../..');
构建时自动把 manager 和插件 zip 打包进宿主的 assets/ 目录,运行时自动“模拟下载”。

图片说明: 就是source开头的!

官方的说明如下:

* `sample-host`是宿主应用
* `sample-manager`是插件管理器的动态实现
* `sample-plugin/sample-loader`是loader的动态实现,业务主要在这里定义插件组件和壳子代理组件的配对关系等。
* `sample-constant`是在前3者中共用的相同字符串常量。
* `sample-plugin/sample-runtime`是runtime的动态实现,业务主要在这里定义壳子代理组件的实际类。
* `sample-plugin/sample-base-lib`是业务App的一部分基础代码,是一个aar库。
* `sample-plugin/sample-base`是一个apk模块壳子,将`sample-base-lib`打包在其中。既用于正常安装运行开发`sample-base-lib`
  ,又用于编译`sample-base`插件。
* `sample-plugin/sample-app`是依赖`sample-base-lib`开发的更多业务代码。它编译出的插件apk没有打包`sample-base-lib`
  ,会在插件运行时依赖`sample-base`插件。

`sample-app`和`sample-base`构成了一个多插件示例,请注意`sample-app/build.gradle`中的`dependsOn = ['sample-base']`设置。

这些工程中对Shadow SDK的依赖完全是源码级的依赖,因此修改Shadow SDK的源码后可以直接运行生效。

使用时可以直接在Android Studio中选择运行`sample-host`模块。
`sample-host`在构建中会自动打包manager和"插件"到assets中,在运行时自动释放模拟下载过程。

2.项目结构概览

让我们先了解项目目录结构:

projects/sample/source/
├── sample-host/          # 宿主应用
├── sample-manager/       # 插件管理器
├── sample-plugin/        # 插件相关(包含多个子模块)
│   ├── sample-loader/    # Loader动态实现
│   ├── sample-runtime/   # Runtime动态实现
│   ├── sample-base-lib/  # 基础库(aar)
│   ├── sample-base/      # 基础插件(依赖base-lib)
│   └── sample-app/       # 业务插件(依赖base)
├── sample-constant/      # 公共常量
└── ...其他配置文件

🔍 一、整体架构:一个 Shadow 应用由哪几部分组成?
关键结论:Shadow 不是“一个 APK”,而是“宿主 + 管理器 + 插件(含 loader + runtime + 业务)”的组合体。

缺省

关键结论:一个完整的Shadow应用由 “宿主 + 管理器 + 插件” 三大部分组成,而“插件”本身又包含了 “Loader + Runtime + 业务代码”。

宿主(Host) :一个空壳App,主要提供运行环境和代理组件(如代理Activity)。在我们项目中对应 sample-host 模块。

管理器(Manager) :负责插件的下载、安装和版本管理。在Demo中,它还负责展示加载状态(Loading)。对应 sample-manager 模块。

插件(Plugin) :这是业务的核心。

Loader:负责定义业务组件与宿主中“壳子代理组件”的配对关系,是加载业务的关键。对应 sample-plugin/sample-loader。

Runtime:定义“壳子代理组件”的实际实现类,提供运行环境。对应 sample-plugin/sample-runtime。

业务App:你真正的业务代码,如MainActivity。示例中有两个业务插件:sample-plugin/sample-base(基础插件)和 sample-plugin/sample-app(主插件)。

3.构建和运行Demo

运行方法:

用Android Studio打开Shadow项目根目录

选择运行sample-host模块

宿主会自动打包manager和插件到assets中

运行时自动释放模拟下载过程

README.TXT

这个演示工程没有实现下载功能,而是假设下载的文件直接位于指定路径。
因此运行前需要手工用adb命令将指定内容push到指定位置。

编译插件,在`plugin-project`目录中运行:

./gradlew packageDebugPlugin

adb push build/plugin-debug.zip /data/local/tmp


编译PluginManager,在`manager-project`目录中运行:

./gradlew assembleDebug
adb push sample-manager/build/outputs/apk/debug/sample-manager-debug.apk /data/local/tmp


最后可以用Android Studio打开`host-project`直接运行`sample-host`模块

3.1 环境准备与打开工程

错误的:打开 Shadow/projects/sample/
正确的:打开 Shadow/
为什么?
因为 source 示例通过 includeBuild 依赖了上层的 Shadow SDK 源码;
如果只打开 source,Gradle 找不到 ../.. 的 SDK 模块,会同步失败。

同步完成后,在Android Studio左侧的Project面板中,应该能看到这样的结构:
Shadow (根项目)
├── .gradle
├── .idea
├── build
├── build-logic
├── buildScripts
├── gradle
├── projects
│   ├── sample
│   │   ├── sample-constant
│   │   ├── sample-host        # 👈 我们要运行的宿主应用
│   │   ├── sample-loader
│   │   ├── sample-manager
│   │   ├── sample-plugin
│   │   └── sample-runtime
│   └── test
├── sdk
└── 各种gradle配置文件

重要:确保你能看到所有模块,特别是sample-host模块!

3.2 运行sample-host

在运行过程中,你很可能会遇到一些错误,下面针对最常见的问题进行剖析。
报错,问题一:“plugin/loader file not exist...”
这是最典型的错误,本质是构建流程中断,导致关键的插件APK或ZIP文件没有生成。

F:\shadow1021\Shadow-master\projects\sample\source\sample-plugin\sample-
app\build\outputs\apk\plugin\debug\sample-app-plugin-debug.apk , plugin file not exist...

来源于:ShadowPluginHelper里面的代码

    fun getPluginFile(
            project: Project,
            pluginConfig: PluginApkConfig,
            checkExist: Boolean
        ): File {
            val pluginFile = File(project.rootDir, pluginConfig.apkPath)
            if (checkExist && !pluginFile.exists()) {
                throw IllegalArgumentException(pluginFile.absolutePath + " , plugin file not exist...")
            }
            project.logger.info("pluginFile = $pluginFile")
            return pluginFile
        }

解决方案:
确保在项目根目录(Shadow/)下操作。
手动执行关键构建命令,观察是否有报错:

./gradlew :projects:sample:source:sample-plugin:sample-app:packagePlugin (这是最核心的插件打包任务)
./gradlew :projects:sample:source:sample-host:assembleDebug (构建宿主)
非常重要,版本

版本匹配是成功的第一步。根据官方文档和我实践中的踩坑经验,Java 11 与项目自带的 Gradle 7.5 是兼容性最佳的组合。使用更高的Java版本(如Java 21)会导致不兼容错误

命令: ./gradlew --version

JDK: 17
和环境变量的jdk版本 : 17
Gradle的版本: gradle-7.5-bin.zip
tools的版本: 7.4.2

重要的4步:
1). /gradlew packageDebugPlugin

会生成

F:\shadow1021\Shadow-master\projects\sample\source\sample-plugin\
sample-app\build\outputs\apk\plugin\debug\sample-app-plugin-debug.apk
build/plugin-debug.zip

也会生成这个:

F:\shadow1021\Shadow-master\projects\sample\source\sample-plugin\
sample-loader\build\outputs\apk\debug\sample-loader-debug.apk
2)./gradlew assembleDebug

会生成:

F:\shadow1021\Shadow-master\projects\sample\source\sample-manager\build\outputs\apk\debug
\sample-manager-debug.apk

同时也会生成这个:

F:\shadow1021\Shadow-master\projects\sample\source\sample-host\build\outputs\apk\debug\sample-host-debug.apk

还会生成 sample-host-debug.apk

3)push 插件到宿主

进入到项目的目录:Shadow-master

adb push build/plugin-debug.zip /data/local/tmp

进入到manager目录:

adb push sample-manager/build/outputs/apk/debug/sample-manager-debug.apk /data/local/tmp
4 ) 用命令运行,然后手动安装宿主
adb install -r -t -d F:\shadow1021\Shadow-master\projects\sample\source\sample-host\build\outputs\apk\debug\sample-host-debug.apk
F:\shadow1021\Shadow-master\projects\sample\source\sample-host\build\outputs\apk\debug\sample-host-debug.apk

否则每次运行的时候,直接运行不行, 都会把之前的产生文件清空,导致又报这个错:

F:\shadow1021\Shadow-master\projects\sample\source\sample-plugin\sample-loader\build\outputs\apk\debug\sample-loader-debug.apk , loader file not exist...

生成 loader-debug.zip 和 plugin-debug.zip;

自动复制它们到 sample-host/src/main/assets/ 目录。

第二步:检查 zip 文件是否生成
构建成功后,你应该能在以下路径找到1个关键文件:plugin-debug.zip

3.3 运行的效果图:

宿主 App 启动后,你会看到一个简单界面,有一个按钮:“Load Plugin”;
点击该按钮;
如果跳转到一个新页面,显示:

Hello Shadow Plugin!
This is sample-app plugin.

3.4 关键代码位置

表格
功能 文件路径
插件加载入口 sample-host/src/main/java/.../HostApplication.java
加载逻辑 sample-host/src/main/java/.../PluginHelper.java
插件业务页面 sample-plugin/sample-app/src/main/java/.../MainActivity.java
常量定义 sample-constant/src/main/java/.../Constant.java
注意:Constant.java 中定义了插件类名、进程名等,宿主和插件必须一致,否则找不到 Activity。

4. 执行命令的具体操作

4.1 ./gradlew packageDebugPlugin 命令详解

这是Shadow插件化构建的灵魂命令。在源码项目中,它通常被宿主模块的构建任务自动调用,但你也可以手动执行以调试。

它会做什么?

编译 sample-loader、sample-runtime、sample-app 等模块,生成各自的APK。

对这些APK进行插件化处理(如重新编排资源ID,以适应插件化环境)。

将处理后的APK和配置文件,按照Shadow定义的格式打包成最终的 plugin-debug.zip 和 loader-debug.zip。

核心产物分析:plugin-debug.zip

解压这个zip包,你会看到Shadow插件的完整构成:

text
plugin-debug.zip/
├── sample-loader-debug.apk # 插件加载器
├── sample-runtime-debug.apk # 运行时环境
├── sample-app-plugin-debug.apk # 经过插件化处理的业务APK
└── config.json # 插件的配置文件(声明依赖、组件映射等)
这个zip包就是最终会被宿主下载和加载的“插件包”。运行时,宿主会先加载 loader 和 runtime,再由它们去加载真正的业务APK。

4.2 ./gradlew assembleDebug

这是标准的Android构建命令,会编译并打包指定模块的Debug版本APK。在Shadow项目中,你可以在各个模块目录下执行它来单独构建:

在 sample-manager 下执行,生成插件管理器APK。

在 sample-host 下执行,生成宿主APK。

4.3 plugin-debug.zip 是怎么自动被打包的?

4.4 plugin-debug.zip 里面有哪些东西

一、plugin-debug.zip 的完整结构
当你解压一个标准的 plugin-debug.zip 文件时,会发现它包含以下内容:

text
plugin-debug.zip/
├── sample-loader-debug.apk # 插件加载器 (Loader)
├── sample-runtime-debug.apk # 运行时环境 (Runtime)
├── sample-app-plugin-debug.apk # 业务插件 (Business Plugin)
├── config.json # 配置文件
└── checksum.txt # 校验文件 (可选)
让我们通过一个表格来快速了解每个文件的核心职责:

文件 类型 核心职责 类比说明
sample-loader-debug.apk APK 定义插件组件与宿主壳子的配对关系 相当于“翻译官”,告诉宿主哪个插件Activity对应哪个壳子Activity
sample-runtime-debug.apk APK 提供壳子代理组件的实际实现 相当于“演员替身”,当壳子Activity被调用时,执行真正的插件逻辑
sample-app-plugin-debug.apk APK 包含实际的业务代码和界面 相当于“剧本”,包含真正的业务逻辑和界面布局
config.json JSON 描述插件包的元数据和依赖关系 相当于“说明书”,告诉Shadow如何正确加载和使用这个插件包

5:简单的代码运行逻辑

sample-hello-api: 定义宿主api接口 ,支持生命周期的接口

sample-hello-api-holder:将 api 动态化,宿主通过这个包提供的方法来获取apk中的实现

sample-hello-apk: 生成apk

sample-hello-host: 用上面的sample-hello-apk产生的apk

调用关系链:

sample-manager: 插件的版本管理(重点)! 用于管理宿主和插件之间的关系

sameple-host: 里面的

HostApplication

    Shadow.getPluginManager(apk);

          PluginManager-- ---  DynamicPluginManager  --->ManagerImplLoader

                  PluginHelper

                        ManagerFactoryImpl 

6:总结与心得体会

成功运行Shadow源码Demo,只是深入理解插件化技术的第一步。通过这个过程,我们可以总结出以下几点关键心得:

架构清晰是核心:理解“宿主-管理器-插件(Loader/Runtime/业务)”的分层架构,是后续进行任何定制开发或问题排查的基础。

构建流程是关键:Shadow通过自定义的Gradle插件(packagePlugin任务)完成了将普通APK“改造”为插件化APK的复杂过程。理解这一过程对于集成到自己的CI/CD流程至关重要。

源码方式便于学习:projects/sample/source 示例将所有代码放在一个工程内,方便你跟踪从SDK源码到最终APK的完整调用链,是学习和调试的最佳选择。

问题定位有方法:遇到文件找不到的错误,首要检查Gradle构建任务是否成功执行,并去对应的 build/outputs/ 目录下验证产物是否存在。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容