用 Java 写一个最轻量的 Android 插件化框架:先把问题想明白

最近想把 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
2
3
4
5
6
7
8
9
10
11
12
13
14
Host App
|
+-- PluginManager
| |
| +-- PluginClassLoader
| +-- PluginResource
| +-- PluginPackageInfo
|
+-- ComponentManager
|
+-- Activity Proxy
+-- Service Proxy
+-- Receiver Proxy
+-- Provider Proxy

插件 APK 则保持普通 APK 的形式:

1
2
3
4
plugin.apk
├── classes.dex
├── AndroidManifest.xml
└── res/

宿主只需要知道插件 APK 的路径,以及插件入口类。

3. 为什么 ClassLoader 是第一步

APK 本质上是一个压缩包,里面有 dex。

Java 代码真正执行时,Android 最终还是要把 dex 加载进当前进程。所以第一步就是拿插件 APK 创建一个 ClassLoader。

Android 中常见的做法是 DexClassLoader

1
2
3
4
5
6
DexClassLoader classLoader = new DexClassLoader(
apkPath,
optimizedDir,
librarySearchPath,
hostClassLoader
);

这里最重要的是最后一个参数。

插件通常还要使用宿主提供的接口类,因此可以让宿主 ClassLoader 作为 parent。

这样形成:

1
2
3
4
5
6
7
PluginClassLoader
|
v
HostClassLoader
|
v
BootClassLoader

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 之类的大型框架。

我们要做的是一个几十到几百行核心代码就能看懂的教学版框架

因此会主动接受一些限制:

  1. 只支持特定 Android 版本范围;
  2. 不追求覆盖所有 ROM;
  3. 不处理复杂的多进程插件;
  4. 不解决所有隐藏 API 兼容问题;
  5. 插件和宿主最好共享明确的 API 接口;
  6. 插件来源必须可信,不能随便加载陌生 APK。

这样反而比较容易把原理看清楚。

下一篇先从最基础的 DexClassLoader + Resources 开始。

坚持原创技术分享,您的支持将鼓励我继续创作!