最近想把 Android 插件化这件事情重新捋一遍。
这里不准备直接上某个成熟框架,而是反过来:如果只允许使用 Java 和 Android SDK,能不能自己写一个尽量小的插件框架?
答案是可以,但有个前提:我们接受使用一些 Android 内部机制,尤其是四大组件代理部分,并且这个方案更适合学习原理和做受控环境下的插件系统。
1. 我们到底要解决什么
假设宿主 App 是 host.apk,另外有一个可以单独发布的 plugin.apk。
宿主希望做到:
- 插件 APK 不安装到系统;
- 宿主启动后动态加载插件代码;
- 插件自己的 Activity 可以打开;
- 插件 Service 可以启动;
- 插件 BroadcastReceiver 可以工作;
- 插件 ContentProvider 可以访问;
- 插件代码尽量不用改成特殊写法。
最麻烦的其实不是“加载 APK”。
真正麻烦的是 Android 系统并不知道插件存在。
比如插件里面声明了:
1 | <activity android:name=".PluginActivity" /> |
系统 PackageManager 根本没有安装这个 APK,自然也就不知道 PluginActivity。
所以整个方案可以简单理解成一句话:
代码和资源自己加载,四大组件欺骗/适配系统。
2. 最小架构
我准备把整个框架拆成下面几个部分:
1 | Host App |
插件 APK 则保持普通 APK 的形式:
1 | plugin.apk |
宿主只需要知道插件 APK 的路径,以及插件入口类。
3. 为什么 ClassLoader 是第一步
APK 本质上是一个压缩包,里面有 dex。
Java 代码真正执行时,Android 最终还是要把 dex 加载进当前进程。所以第一步就是拿插件 APK 创建一个 ClassLoader。
Android 中常见的做法是 DexClassLoader:
1 | DexClassLoader classLoader = new DexClassLoader( |
这里最重要的是最后一个参数。
插件通常还要使用宿主提供的接口类,因此可以让宿主 ClassLoader 作为 parent。
这样形成:
1 | PluginClassLoader |
4. 四大组件为什么要代理
Activity 是最容易理解的例子。
宿主真正注册的是:
1 | <activity android:name=".ProxyActivity" /> |
插件想启动:
1 | com.demo.plugin.PluginActivity |
那么就把 Intent 改成:
1 | ProxyActivity |
同时把真正的插件 Activity 名字塞进 Intent extra。
ProxyActivity 启动以后,再通过插件 ClassLoader 创建真正的 PluginActivity。
Service、Receiver、Provider 的思路也是类似的,只不过它们和系统交互的方式完全不同,所以不能简单复制 Activity 的代码。
5. 这套方案的边界
这里先把话说在前面。
这个系列不是准备重新造一个 RePlugin、VirtualAPK 之类的大型框架。
我们要做的是一个几十到几百行核心代码就能看懂的教学版框架。
因此会主动接受一些限制:
- 只支持特定 Android 版本范围;
- 不追求覆盖所有 ROM;
- 不处理复杂的多进程插件;
- 不解决所有隐藏 API 兼容问题;
- 插件和宿主最好共享明确的 API 接口;
- 插件来源必须可信,不能随便加载陌生 APK。
这样反而比较容易把原理看清楚。
下一篇先从最基础的 DexClassLoader + Resources 开始。