阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680

本文通过Activity调用原理来解读Replugin插件化技术

一、开启插件Activity流程

第1步
开启插件Activity的入口在Replugin.StartActivity(Context context, Intent intent),其他intent内部一定要传入想要开启的插件名。

public static boolean startActivity(Context context, Intent intent) {
        // TODO 先用旧的开启Activity方案,以后再优化
        ComponentName cn = intent.getComponent();
        if (cn == null) {
            // TODO 需要支持Action方案
            return false;
        }
        String plugin = cn.getPackageName();
        String cls = cn.getClassName();
        return Factory.startActivityWithNoInjectCN(context, intent, plugin, cls, IPluginManager.PROCESS_AUTO);
    }

Factory.startActivityWithNoInjectCN内部调用sPluginManager.startActivity方法,那么sPluginManager是哪里传进去的?
PMF.java

Factory.sPluginManager = PMF.getLocal();
Factory2.sPluginManager = PMF.getInternal();

看下PMF.init的方法可以看到sPluginManager是PMF内部维护的sPluginMgr.mLocal,也就是Pmbase内部的PmLocalmpl mLocal对象,继续跟踪发现真正实现startActivity逻辑的是PmBase内部的PmInternalmpl.startActivity下面这个方法。

public boolean startActivity(Context context, Intent intent, String plugin, String activity, int process, boolean download) {
        。。。
    }

第2步
重点走读下上面startActivity方法,首先会判断要跳转的插件是否已经存在了,如果不存在则回调callback接口去提示用户下载等逻辑操作,如果插件状态不正确,则回调外部callback去提示用户插件不可用或者去升级,如果插件首次加载并且是大插件则异步加载并弹窗显示正在加载。然后在调用context.startActivity方法前会去通过下面的loadPluginActivity方法将目标插件Activity class替换为“坑位”Activity,这样其实传给AMS的还是宿主的坑位Activity。
PmInternalmpl.java

public boolean startActivity(Context context, Intent intent, String plugin, String activity, int process, boolean download) {
        if (LOG) {
            LogDebug.d(PLUGIN_TAG, "start activity: intent=" + intent + " plugin=" + plugin + " activity=" + activity + " process=" + process + " download=" + download);
        }

        // 1. 是否启动下载
        // 若插件不可用(不存在或版本不匹配),则直接弹出“下载插件”对话框
        // 因为已经打开UpdateActivity,故在这里返回True,告诉外界已经打开,无需处理
        if (download) {
            if (PluginTable.getPluginInfo(plugin) == null) {
                if (LOG) {
                    LogDebug.d(PLUGIN_TAG, "plugin=" + plugin + " not found, start download ...");
                }

                // 如果用户在下载即将完成时突然点按“取消”,则有可能出现插件已下载成功,但没有及时加载进来的情况
                // 因此我们会判断这种情况,如果是,则重新加载一次即可,反之则提示用户下载
                // 原因:“取消”会触发Task.release方法,最终调用mDownloadTask.destroy,导致“下载服务”的Receiver被注销,即使文件下载了也没有回调回来
                // NOTE isNeedToDownload方法会调用pluginDownloaded再次尝试加载
                if (isNeedToDownload(context, plugin)) {
                    return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process);
                }
            }
        }
          ...
        // 2. 如果插件状态出现问题,则每次弹此插件的Activity都应提示无法使用,或提示升级(如有新版)
        // Added by Jiongxuan Zhang
        if (PluginStatusController.getStatus(plugin) < PluginStatusController.STATUS_OK) {
            if (LOG) {
                LogDebug.d(PLUGIN_TAG, "PmInternalImpl.startActivity(): Plugin Disabled. pn=" + plugin);
            }
            return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process);
        }

        // 3. 若为首次加载插件,且是“大插件”,则应异步加载,同时弹窗提示“加载中”
        // Added by Jiongxuan Zhang
        if (!RePlugin.isPluginDexExtracted(plugin)) {
            PluginDesc pd = PluginDesc.get(plugin);
            if (pd != null && pd.isLarge()) {
                ...
                return RePlugin.getConfig().getCallbacks().onLoadLargePluginForActivity(context, plugin, intent, process);
            }
        }

        // WARNING:千万不要修改intent内容,尤其不要修改其ComponentName
        // 因为一旦分配坑位有误(或压根不是插件Activity),则外界还需要原封不动的startActivity到系统中
        // 可防止出现“本来要打开宿主,结果被改成插件”,进而无法打开宿主Activity的问题

        // 缓存打开前的Intent对象,里面将包括Action等内容
        Intent from = new Intent(intent);

        // 帮助填写打开前的Intent的ComponentName信息(如有。没有的情况如直接通过Action打开等)
        if (!TextUtils.isEmpty(plugin) && !TextUtils.isEmpty(activity)) {
            from.setComponent(new ComponentName(plugin, activity));
        }

        //4. 会去将目标Activity替换为坑位Activity
        ComponentName cn = mPluginMgr.mLocal.loadPluginActivity(intent, plugin, activity, process);
        if (cn == null) {
            if (LOG) {
                LogDebug.d(PLUGIN_TAG, "plugin cn not found: intent=" + intent + " plugin=" + plugin + " activity=" + activity + " process=" + process);
            }
            return false;
        }

        // 将Intent指向到“坑位”。这样:
        // from:插件原Intent
        // to:坑位Intent
        intent.setComponent(cn);


        //5. 调用系统startActivity方法
        context.startActivity(intent);

        // 6. 通知外界,已准备好要打开Activity了
        // 其中:from为要打开的插件的Intent,to为坑位Intent
        RePlugin.getConfig().getEventCallbacks().onPrepareStartPitActivity(context, from, intent);

        return true;
    }

坑位Activity的替换规格,逻辑是在PmLocalmpl.allocActivityContainer方法中执行,这个有兴趣的可以自己去跟踪下,坑位Activity替换规格,这里就不深入去分析了。
第3步
上述几步使用坑位Activity来替换目标Activity之后,AMS内部都是基于这个坑位Activity来实现对其合法性、生命周期进行回调啦,AMS一系列流程走完,然后AMS再通过宿主进程的ActivityThread提供的IApplicationThread Binder代理对象去实现宿主进程中类加载坑位Activity啦,并对其回调相关接口如onCreate。正常startActivity是这样没错,但是我们明明是希望调用的宿主的Activity啊,又不是坑位Activity?是的这里需要对坑位Activity进行恢复真身处理,恢复为目标插件Activity。在哪里恢复呢?没错,就是在我们唯一hook点:RePluginClassLoader中。
我们在《Replugin插件化技术解读之框架初始化、插件安装与加载(二)》一文中已经分析的很明白,从RePluginClassLoader的loadClass方法中会首先去使用PMF.loadClass去加载插件,去生成插件的Loader对象,
初始化好插件的DexClassLoader、Resource、PluginContext等资源,然后用插件的ClassLoader去尝试加载目标Activity。具体执行逻辑在PluginProcessPer.resolveActivityClass方法中。

/**
   * 类加载器根据容器解析到目标的activity
   * @param container
   * @return
   */
  final Class<?> resolveActivityClass(String container) {
      String plugin = null;
      String activity = null;

      // 先找登记的,如果找不到,则用forward activity
      PluginContainers.ActivityState state = mACM.lookupByContainer(container);
      if (state == null) {
          // PACM: loadActivityClass, not register, use forward activity, container=
          if (LOGR) {
              LogRelease.w(PLUGIN_TAG, "use f.a, c=" + container);
          }
          return ForwardActivity.class;
      }
      plugin = state.plugin;
      activity = state.activity;

      Plugin p = mPluginMgr.loadAppPlugin(plugin);
      if (p == null) {
          return null;
      }
      ClassLoader cl = p.getClassLoader();
      if (LOG) {
      Class<?> c = null;
      try {
          c = cl.loadClass(activity);
      } catch (Throwable e) {
          if (LOGR) {
              LogRelease.e(PLUGIN_TAG, e.getMessage(), e);
          }
      }
      return c;
  }

这样我们插件目标Activity就恢复成功,目标Activity内部逻辑就能正常执行啦。
总结:
开启插件Activity的大致流程为:
插件Activity -> Replugin.startActivity -> 解析出插件Activity对应的pluginName -> 挑选坑位Activity替换 ->调用系统startActivity方法 -> AMS回调执行坑位Activity类加载 ->加载并初始化目标插件资源、上下文、类加载器(首次) ->Hook掉ClassLoader的类加载中恢复为插件Activity -> 插件Activity启动,并拥有完整生命周期。

二、插件内部startActivity流程

插件中startActiviy有两种场景:调用自己插件本身的Activity或者调用宿主的Activity。显然光凭插件自己是无法成功开启的。因为插件的Manifest根本就没有被AMS识别到呀,必须还是要用统一的Replugin.startActivity接口去按照上述分析的流程重走一遍才行的啊。
在《Replugin插件化技术解读之目录结构解读》一文中,我们了解到其实插件在动态编译过程中会在字节码层做适量修改,如插件的所有Activity都会被继承replugin-plugin-lib中的PluginActivity这样一个基类。所以我们插件内部其实Activity内部最后执行startActivity都会先走PluginActivity的此方法;
PluginActivity.java

static void initLocked(final ClassLoader classLoader) {

            final String factory2 = "com.qihoo360.i.Factory2";
            final String factory = "com.qihoo360.i.Factory";

            // 初始化Factory2相关方法
            createActivityContext = new MethodInvoker(classLoader, factory2, "createActivityContext", new Class<?>[]{Activity.class, Context.class});
            handleActivityCreateBefore = new MethodInvoker(classLoader, factory2, "handleActivityCreateBefore", new Class<?>[]{Activity.class, Bundle.class});
            handleActivityCreate = new MethodInvoker(classLoader, factory2, "handleActivityCreate", new Class<?>[]{Activity.class, Bundle.class});
            handleActivityDestroy = new MethodInvoker(classLoader, factory2, "handleActivityDestroy", new Class<?>[]{Activity.class});
            handleRestoreInstanceState = new MethodInvoker(classLoader, factory2, "handleRestoreInstanceState", new Class<?>[]{Activity.class, Bundle.class});
            startActivity = new MethodInvoker(classLoader, factory2, "startActivity", new Class<?>[]{Activity.class, Intent.class});
            startActivityForResult = new MethodInvoker(classLoader, factory2, "startActivityForResult", new Class<?>[]{Activity.class, Intent.class, int.class, Bundle.class});

            // 初始化Factory相关方法
            loadPluginActivity = new MethodInvoker(classLoader, factory, "loadPluginActivity", new Class<?>[]{Intent.class, String.class, String.class, int.class});

显然,反射的是Factory2中的startActivity方法,而Factory.startActivity其实就是调用的PmInternalmpl.startActivity方法,显然这个跟上面宿主启动插件Activity对上啦。

那么RepluginInternal.initLocked方法是什么时候走的呢?容易看到是在RepluginFramework.init中调用,而RepluginFramework.init初始化时在Entry的create方法中,这里是不是很熟悉啦。没错,在《Replugin插件化技术解读之框架初始化、插件安装与加载(二)》一文中我们知道在加载插件的时候会通过回调执行插件的Entry的create方法初始化插件,显然这里这条线又连上啦。

这样插件内部启动startActivity说白了就是通过反射调用宿主Factory2中startActivity方法去重走了遍上述分析的标准启动插件Activity流程。注意,这里其实仅仅是通过插件Activity继承PluginActivity重写了Activity内部StartActivity方法,改变了Activity内startActivity方法启动步骤。

我们显然还要修改Context上下文对应的startActivity方法才能保证这里调用的时候也会走到我们设定好的宿主startActivityq启动流程,

那么插件内部其他startActivity的地方都要进行这样的调整才行,这个就大家自己跟下就能明白了,原理差不多。

public abstract class PluginActivity extends Activity {

    @Override
    protected void attachBaseContext(Context newBase) {
        newBase = RePluginInternal.createActivityContext(this, newBase);
        super.attachBaseContext(newBase);
    }

PluginActivity的attachBaseContext方法中我们会使用RepluginInternal.createActivityContext方法,通过反射,最终吊起来PmInternalImpl.createActivityContext方法。创建对应插件的PluginContext对象,PluginContext前文已经介绍,然后用这个PluginContext替换掉插件内部的上下文BaseContext,其实也就是Activity继承的ContextWrapper类的Context mBase对象。跟踪Android源码很容易发现,ContextWrapper内部封装了startActivity startService等方法,所以这里我们要将这个Context替换掉。那么PluginContext内部又做了什么呢?显然它也重写了startActivity方法。

 @Override
    public void startActivity(Intent intent) {
        if (mContextInjector != null) {
            mContextInjector.startActivityBefore(intent);
        }

        super.startActivity(intent);

        if (mContextInjector != null) {
            mContextInjector.startActivityAfter(intent);
        }
    }

这里 super.startActivity其实就是对应宿主上下文了,直接走宿主startActivity流程。
其他还有在PluginApplicationClient为插件创建的Application对象,在PluginApplicationClient.callAttachBaseContext方法中使用反射调用了Application.attachf方法,用PluginContext对象替换了原来的Context,这个就各位看官自己去梳理了哈。

原文链接https://blog.csdn.net/hellogmm/article/details/79058135
阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680

12-28 22:21