把四大组件串起来:一个 Java 轻量插件框架应该长什么样

前面已经把几个零件都拆出来了,现在可以把它们拼起来。

我比较倾向于保持非常小的结构,不要一开始就几十个类。

1. 推荐目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
plugin/
├── PluginManager.java
├── PluginInfo.java
├── PluginContext.java
├── PluginClassLoader.java
├── PluginResources.java
├── activity/
│ └── ProxyActivity.java
├── service/
│ └── ProxyService.java
├── receiver/
│ └── ProxyReceiver.java
└── provider/
└── ProxyProvider.java

插件侧:

1
2
3
4
5
plugin-api/
├── PluginActivity.java
├── PluginService.java
├── PluginReceiver.java
└── PluginContentProvider.java

这里特别建议把 API 单独放一个小模块。

宿主和插件都依赖 API,但宿主不直接依赖插件实现。

2. PluginInfo

每个插件至少记录:

1
2
3
4
5
6
7
8
9
public class PluginInfo {
public String packageName;
public String apkPath;
public String versionName;
public int versionCode;

public ClassLoader classLoader;
public Resources resources;
}

后面如果需要签名校验、插件版本、插件状态,也可以继续加。

3. PluginManager

核心 API 可以非常简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class PluginManager {

private final Map<String, PluginInfo> plugins = new HashMap<>();

public void loadPlugin(Context context, String apkPath) {
// 1. 解析 APK
// 2. 创建 ClassLoader
// 3. 创建 Resources
// 4. 保存 PluginInfo
}

public PluginInfo getPlugin(String packageName) {
return plugins.get(packageName);
}
}

框架其他模块全部通过它拿插件。

4. 统一启动 Activity

不要让业务代码到处自己拼 ProxyActivity Intent。

可以封装:

1
2
3
4
5
6
7
8
9
public void startActivity(Context context,
String packageName,
String className) {

Intent intent = new Intent(context, ProxyActivity.class);
intent.putExtra("plugin_package", packageName);
intent.putExtra("plugin_class", className);
context.startActivity(intent);
}

这样业务侧看起来就是:

1
2
3
4
5
pluginManager.startActivity(
this,
"com.demo.plugin",
"com.demo.plugin.MainActivity"
);

5. 为什么要保存 packageName

只保存 className 不太够。

因为以后可能同时加载:

1
2
3
pluginA.apk
pluginB.apk
pluginC.apk

它们甚至可能存在相同的类名。

所以路由信息最好是:

1
packageName + className

四大组件都一样。

6. 四大组件最终形成一套路由

1
2
3
4
5
6
7
8
9
10
11
                   PluginManager
|
+-----------------+------------------+
| | |
pluginA pluginB pluginC
|
+----+----+----+
| | | |
Act Svc Rec Provider
| | | |
ProxyProxyProxy Proxy

真正的系统组件永远在宿主 Manifest 中。

插件组件只是框架运行时创建的普通对象,或者实现特定 API 的对象。

7. 这样做还有一个好处

以后插件升级的时候,不需要改宿主代码。

比如:

1
2
3
plugin-v1.apk

plugin-v2.apk

只要 API 兼容,宿主继续使用同一套 PluginManager。

8. 但别急着拿去生产

这个方案作为学习框架已经够用了。

真正生产环境还要处理:

  • APK 签名和完整性校验;
  • 插件版本管理;
  • ClassLoader 冲突;
  • 多插件资源冲突;
  • Android 不同版本差异;
  • 进程和线程问题;
  • 插件崩溃隔离;
  • 权限边界;
  • Provider 跨进程;
  • ProGuard/R8;
  • ABI 和 native so;
  • 隐藏 API 限制。

尤其是最后一点,Android 越来越不鼓励应用随便碰内部 API。

所以这个框架定位还是很明确:先把插件化最核心的运行机制看懂。

下一篇做一个完整 Demo,把宿主、插件、API 三个工程放到一起跑。

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