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

插件化框架解读之四大组件调用原理-Activity(三)上篇的更多相关文章

  1. 插件化框架解读之四大组件调用原理-Service(三)下篇

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 本文将继续通过Service调用原理来解读Replugin插件化 ...

  2. 插件化框架解读之android系统服务实现原理(五)

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 一.系统服务提供方式 1.我们平时最常见的系统服务使用方式 Wi ...

  3. Android Small插件化框架解读——Activity注册和生命周期

    通过对嵌入式企鹅圈原创团队成员degao之前发表的<Android Small插件化框架源码分析>的学习,对Android使用的插件化技术有了初步的了解,但还是有很多需要认真学习的地方,特 ...

  4. 插件化框架解读之so 文件加载机制(四)

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 提问 本文的结论是跟着 System.loadlibrary() ...

  5. 插件化框架解读之Android 资源加载机制详解(二)

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680Android提供了一种非常灵活的资源系统,可以根据不同的条件提供 ...

  6. 插件化框架解读之Class文件与Dex文件的结构(一)

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 Class文件 Class文件是Java虚拟机定义并被其所识别的 ...

  7. [置顶] 滴滴插件化框架VirtualAPK原理解析(一)之插件Activity管理

    上周末,滴滴与360都开源了各自的插件化框架,VirtualAPK与RePlugin,作为一个插件化方面的狂热研究者,在周末就迫不及待的下载了Virtualapk框架来进行研究,本篇博客带来的是Vir ...

  8. Android插件化框架

    1.   dynamic-load-apk/DL动态加载框架 是基于代理的方式实现插件框架,对 App 的表层做了处理,通过在 Manifest 中注册代理组件,当启动插件组件时,首先启动一个代理组件 ...

  9. android 插件化框架VitualAPK

    推荐阅读: 滴滴Booster移动App质量优化框架-学习之旅 一 Android 模块Api化演练 不一样视角的Glide剖析(一) LeakCanary 与 鹅场Matrix ResourceCa ...

随机推荐

  1. NGUI多行输入框和滚动条结合使用(text list script 和scroll bar script)

    一,我们添加一个label,如下图:将label属性设置 二,给label添加一个box collider.然后在add component 添加test list,如下图: 三,添加一个脚本Test ...

  2. ES6——函数-箭头函数

    箭头函数: 1.普通函数 function 函数名(){...} 2.箭头函数 注意:  1)如果只有一个返回值,{}return可以省略: let arr = [12,5,8,99,33,14,26 ...

  3. Bootstrap-带语境色彩的面板

    使用语境状态类 panel-primary.panel-success.panel-info.panel-warning.panel-danger,来设置带语境色彩的面板,实例如           ...

  4. vue,一路走来(15)--简单投票系统

    今天记录一下简单的投票系统,主要实现选中至少五张作品,并提交投票. 思路:选中作品,将作品id存入到数组里. 取消投票,则从数组中移除该作品id. 如图效果: <li v-for="( ...

  5. find命令使用详解

    一.主要内容 ====================================== 1. 用文件名查找文件 2.用文件名查找文件,忽略大小写 3. 使用mindepth和maxdepth限定搜 ...

  6. Jenkins ant打包部署

    选择项目 自由风格

  7. Python 把字符串变成浮点数

    from functools import reducedi = {}di.update(zip('1234567890.', [1,2,3,4,5,6,7,8,9,0,'.'])) def str2 ...

  8. C# 枚举的声名和使用

    namespace xxxxxx { public enum EnumTextHAlign { Left = , Center = , Right = } } using xxxxxx;

  9. Request Payload 和 Form Data 的区别

    概述 我正在开发的项目前端和后端是完全独立的,通过配置 webpack 的 proxy 将前端请求跨域代理到后台服务.昨天发现,我前端执行 post 请求,后台 springmvc 的 @Reques ...

  10. Halo(十一)

    Spring Boot 继承 AbstractErrorController 实现全局异常处理 @RequestMapping("${server.error.path:${error.pa ...