zoukankan      html  css  js  c++  java
  • 插件化框架解读之四大组件调用原理-Activity(三)上篇

    阿里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

  • 相关阅读:
    dotnet 新项目格式与对应框架预定义的宏
    dotnet 线程静态字段
    dotnet 线程静态字段
    dotnet 通过 WMI 拿到显卡信息
    dotnet 通过 WMI 拿到显卡信息
    dotnet 通过 WMI 获取指定进程的输入命令行
    dotnet 通过 WMI 获取指定进程的输入命令行
    dotnet 通过 WMI 获取系统信息
    dotnet 通过 WMI 获取系统信息
    PHP show_source() 函数
  • 原文地址:https://www.cnblogs.com/Android-Alvin/p/11983660.html
Copyright © 2011-2022 走看看