Shadow icon indicating copy to clipboard operation
Shadow copied to clipboard

多插件、多进程改造过程

Open senda58 opened this issue 2 years ago • 3 comments

在测试官方demo时,官方提供了两个插件

image

想提出以下几个问题

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

3、多插件,多进程的情况下,需要让FastFastPluginManager继承自PluginManagerThatSupportMultiLoader 对么?PluginManagerThatSupportMultiLoader类中有些未实现的抽象方法需要研发人员自己实现么?比如getPluginKey() ?

senda58 avatar Apr 07 '23 03:04 senda58

从提交记录 a0ea9d59443f538631e445a96dc9d51babbe9497 上可以看出来这个app2只是为了验证multiprocess的用法。

不同插件进程里的插件是不相关的,跨进程的代码相互之间没有影响。所以我们一般不会专门研究怎么支持插件多进程,就是manifest中的process属性。因为这个没有什么技术障碍,把插件组件分配到对应插件进程的PPS就行了。

一个插件进程一个PPS。宿主在manager这边控制它就行了。fast manager只是个支持持有一个controller的启动流程演示代码。要管理多个PPS只需要持有多个控制器就好了。

要是同一个插件进程需要多种Loader版本,才需要multiloader那套实现。具体可以看提交记录,搜issue。这种场景是多个业务被迫共用一个插件进程。多个业务的shadow版本不一样。

shifujun avatar Apr 07 '23 04:04 shifujun

从提交记录 a0ea9d5 上可以看出来这个app2只是为了验证multiprocess的用法。

不同插件进程里的插件是不相关的,跨进程的代码相互之间没有影响。所以我们一般不会专门研究怎么支持插件多进程,就是manifest中的process属性。因为这个没有什么技术障碍,把插件组件分配到对应插件进程的PPS就行了。

一个插件进程一个PPS。宿主在manager这边控制它就行了。fast manager只是个支持持有一个controller的启动流程演示代码。要管理多个PPS只需要持有多个控制器就好了。

要是同一个插件进程需要多种Loader版本,才需要multiloader那套实现。具体可以看提交记录,搜issue。这种场景是多个业务被迫共用一个插件进程。多个业务的shadow版本不一样。

@shifujun 谢谢回复!这个必须使用多个ppscontroller才行是吧,因为一个pps对应一个IBinder 对象,我看改造的话还挺麻烦的。能给具体写段代码或有多插件加载的demo 么?

senda58 avatar Apr 07 '23 06:04 senda58

经过多次、多个插件进程代码的断点调试,多插件基本功能已经实现,一些小细节还需调整。下面贴出改造后的代码,如有错误还需指正:主要改的文件是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);

}

senda58 avatar Apr 12 '23 08:04 senda58