多插件、多进程改造过程
在测试官方demo时,官方提供了两个插件
想提出以下几个问题
1、不管是选择哪个插件都是加载的 sample-plugin-app 这个demo是还没完成么?
2、宿主加载两个插件,就需要有两个插件的进程,每个插件是用同一个ppscontroller ?还是一个插件一个ppsController? 我之所以问这个问题就是因为我在加载多插件时会判断ppsController是否为空,为空才会绑定另外一个进程服务,很显然按照官方demo,加载了第一个插件后ppsController是不为空的,所以对于另外的插件不会启动单独的进程。对于下图中的代码,对于多插件的情况下能否满足要求么?

3、多插件,多进程的情况下,需要让FastFastPluginManager继承自PluginManagerThatSupportMultiLoader 对么?PluginManagerThatSupportMultiLoader类中有些未实现的抽象方法需要研发人员自己实现么?比如getPluginKey() ?
从提交记录 a0ea9d59443f538631e445a96dc9d51babbe9497 上可以看出来这个app2只是为了验证multiprocess的用法。
不同插件进程里的插件是不相关的,跨进程的代码相互之间没有影响。所以我们一般不会专门研究怎么支持插件多进程,就是manifest中的process属性。因为这个没有什么技术障碍,把插件组件分配到对应插件进程的PPS就行了。
一个插件进程一个PPS。宿主在manager这边控制它就行了。fast manager只是个支持持有一个controller的启动流程演示代码。要管理多个PPS只需要持有多个控制器就好了。
要是同一个插件进程需要多种Loader版本,才需要multiloader那套实现。具体可以看提交记录,搜issue。这种场景是多个业务被迫共用一个插件进程。多个业务的shadow版本不一样。
从提交记录 a0ea9d5 上可以看出来这个app2只是为了验证multiprocess的用法。
不同插件进程里的插件是不相关的,跨进程的代码相互之间没有影响。所以我们一般不会专门研究怎么支持插件多进程,就是manifest中的process属性。因为这个没有什么技术障碍,把插件组件分配到对应插件进程的PPS就行了。
一个插件进程一个PPS。宿主在manager这边控制它就行了。fast manager只是个支持持有一个controller的启动流程演示代码。要管理多个PPS只需要持有多个控制器就好了。
要是同一个插件进程需要多种Loader版本,才需要multiloader那套实现。具体可以看提交记录,搜issue。这种场景是多个业务被迫共用一个插件进程。多个业务的shadow版本不一样。
@shifujun 谢谢回复!这个必须使用多个ppscontroller才行是吧,因为一个pps对应一个IBinder 对象,我看改造的话还挺麻烦的。能给具体写段代码或有多插件加载的demo 么?
经过多次、多个插件进程代码的断点调试,多插件基本功能已经实现,一些小细节还需调整。下面贴出改造后的代码,如有错误还需指正:主要改的文件是FastPluginManager文件代码:修改后的代码如下
public abstract class FastPluginManager extends PluginManagerThatUseDynamicLoader {
private static final Logger mLogger = LoggerFactory.getLogger(FastPluginManager.class);
private static final Map<String, PpsController> ppsControllerMap = new HashMap<>();
private PpsController currentPpsController;
private ExecutorService mFixedPool = Executors.newFixedThreadPool(4);
public FastPluginManager(Context context) {
super(context);
}
//安装插件,每次启动插件都会走此方法:从压缩包中解压插件,更新数据库,安装插件的runtime,loader,.so
public InstalledPlugin installPlugin(String zip, String hash, boolean odex) throws IOException, JSONException, InterruptedException, ExecutionException {
final PluginConfig pluginConfig = installPluginFromZip(new File(zip), hash);
final String uuid = pluginConfig.UUID;
List<Future> futures = new LinkedList<>();
List<Future<Pair<String, String>>> extractSoFutures = new LinkedList<>();
if (pluginConfig.runTime != null && pluginConfig.pluginLoader != null) {
Future odexRuntime = mFixedPool.submit(new Callable() {
@Override
public Object call() throws Exception {
oDexPluginLoaderOrRunTime(uuid, InstalledType.TYPE_PLUGIN_RUNTIME,
pluginConfig.runTime.file);
return null;
}
});
futures.add(odexRuntime);
Future odexLoader = mFixedPool.submit(new Callable() {
@Override
public Object call() throws Exception {
oDexPluginLoaderOrRunTime(uuid, InstalledType.TYPE_PLUGIN_LOADER,
pluginConfig.pluginLoader.file);
return null;
}
});
futures.add(odexLoader);
}
for (Map.Entry<String, PluginConfig.PluginFileInfo> plugin : pluginConfig.plugins.entrySet()) {
final String partKey = plugin.getKey();
final File apkFile = plugin.getValue().file;
Future<Pair<String, String>> extractSo = mFixedPool.submit(() -> extractSo(uuid, partKey, apkFile));
futures.add(extractSo);
extractSoFutures.add(extractSo);
if (odex) {
Future odexPlugin = mFixedPool.submit(new Callable() {
@Override
public Object call() throws Exception {
oDexPlugin(uuid, partKey, apkFile);
return null;
}
});
futures.add(odexPlugin);
}
}
for (Future future : futures) {
future.get();
}
Map<String, String> soDirMap = new HashMap<>();
for (Future<Pair<String, String>> future : extractSoFutures) {
Pair<String, String> pair = future.get();
soDirMap.put(pair.first, pair.second);
}
onInstallCompleted(pluginConfig, soDirMap);
return getInstalledPlugins(1).get(0);
}
protected void callApplicationOnCreate(String partKey) throws RemoteException {
Map map = mPluginLoader.getLoadedPlugin();
Boolean isCall = (Boolean) map.get(partKey);
if (isCall == null || !isCall) {
mPluginLoader.callApplicationOnCreate(partKey);
}
}
private void loadPluginLoaderAndRuntime(String uuid, String partKey) throws RemoteException, TimeoutException, FailedException {
// 插件第一次启动时mPpsController为null 下一次启动插件则不为空
if (ppsControllerMap == null || !ppsControllerMap.containsKey(partKey)) {
bindPluginProcessService(getPluginProcessServiceName(partKey));
waitServiceConnected(10, TimeUnit.SECONDS);
savePpsController(partKey);
}else {
this.currentPpsController = ppsControllerMap.get(partKey);
}
loadRunTime(uuid);
loadPluginLoaderThis(uuid,partKey);
}
private void savePpsController(String partKey){
if(mPpsController!=null){
ppsControllerMap.put(partKey,mPpsController);
this.currentPpsController = mPpsController;
}
}
//加载插件
protected void loadPlugin(String uuid, String partKey) throws RemoteException, TimeoutException, FailedException {
loadPluginLoaderAndRuntime(uuid, partKey);
Map map = mPluginLoader.getLoadedPlugin();
if (!map.containsKey(partKey)) {
mPluginLoader.loadPlugin(partKey);
}
}
private final void loadPluginLoaderThis(String uuid, String partKey){
if (mLogger.isInfoEnabled()) {
mLogger.info("loadPluginLoader mPluginLoader:" + mPluginLoader);
}
if (pluginLoaderMap==null){
pluginLoaderMap = new HashMap<>();
}
try {
if (!pluginLoaderMap.containsKey(partKey)) {
PpsStatus ppsStatus = this.currentPpsController.getPpsStatus();
if (!ppsStatus.loaderLoaded) {
this.currentPpsController.loadPluginLoader(uuid);
}
// 拿到的是调用插件进程的service 这里相当于运行在PluginProcessService进程中不需要绑定二获得的IBinder对象。
IBinder iBinder = this.currentPpsController.getPluginLoader();
//加载插件的客户端接口,相当于ppscontroller的功能。
mPluginLoader = new BinderPluginLoader(iBinder);
pluginLoaderMap.put(partKey, mPluginLoader);
} else {
mPluginLoader = pluginLoaderMap.get(partKey);
}
}catch (Exception e){
e.printStackTrace();
}
}
protected abstract String getPluginProcessServiceName(String partKey);
}