上一章把目标定下来了。这一篇先干一件很简单的事情:宿主不安装 plugin.apk,但是可以创建插件里的 Java 对象。
1. 准备一个插件 APK
假设插件里有:
1 | package com.demo.plugin; |
把插件正常编译成 plugin.apk,放到宿主可以访问的位置。
宿主拿到 APK 路径以后,就可以创建 ClassLoader。
2. DexClassLoader
核心代码其实非常短:
1 | File apkFile = new File(pluginPath); |
然后:
1 | Class<?> clazz = loader.loadClass("com.demo.plugin.PluginEntry"); |
到这里,其实已经完成了最核心的一半:
1 | plugin.apk |
3. 为什么还需要 Resources
代码能加载,不代表插件就能正常运行。
比如插件里面:
1 | setContentView(R.layout.activity_plugin); |
这个 layout 在插件 APK 中,不在宿主 APK 中。
所以我们还需要单独创建插件 Resources。
最简单的思路是根据插件 APK 创建 AssetManager,然后创建 Resources。
不同 Android 版本在 AssetManager 的公开接口上有差异,教学版可以通过反射完成资源路径注入:
1 | AssetManager assetManager = AssetManager.class |
然后插件页面需要资源时,不再使用宿主 context.getResources(),而是使用插件 Resources。
4. PluginContext
这里很容易踩坑。
如果插件 Activity 里面直接使用宿主 Context,那么:
1 | getResources() |
拿到的还是宿主资源。
所以后面真正创建插件组件时,最好提供一个简单的 PluginContext,至少把下面几个方法接管掉:
1 | getClassLoader() |
核心关系变成:
1 | PluginContext |
这一步做完以后,插件已经可以在宿主进程里“活起来”了。
5. PluginManager
可以把前面的东西统一封装一下:
1 | public class PluginManager { |
实际项目里还应该保存插件 APK 的 PackageInfo、版本号、签名信息等。
6. 到这里还差什么
现在可以:
- 加载插件类;
- 创建普通插件对象;
- 获取插件资源。
但是还不能直接启动插件 Activity。
因为 Android 系统根本不知道这个 Activity。
下一篇就开始处理最典型的 Activity 代理。