上一篇:唯一插件化RePlugin源码及原理深度剖析–工程职责
提示:请不要忽略代码注释,由于通畅上下逻辑思维,不太重要的部分跳转代码不会全部进去一行行的看,但是会将注释写出来,所以请务必不要忽略注释,而且最好是
跟着文章一起看源码。
一、Replugin的初始化会参照下面几个类的设置,这样设计可以让接入者更方便的定制Replugin功能,但是必要需要继承Replugin提供的类以保证不影响其核心逻辑,建议在自定制的时候也要阅读源码不要影响本身的核心功能,几个类中都有注释,不再一一的说方法的用处了,只简单说一下几个类主要干什么的
RePluginApplication:Replugin对外提供方便宿主继承的Application类,因为Replugin需要在Application中进行初始化
RePluginCallbacks:主要用来生成宿主和插件的ClassLoader、要打开的插件不存在时的回调、要打开的插件文件过大时回调等。
RePluginEventCallbacks:插件化框架对外事件回调接口集,宿主需继承此类,并复写相应的方法来自定义插件框架的事件处理机制,其中有安装插件成功、失败、启动插件Activity时的一些事件回调等。
RePluginConfig:主要用于对Replugin的一些初始化设置,其中包括:设置是否开启插件的签名校检,当插件中使用某个类不存在时是否使用宿主中的类等。而且还保持了对RepluginCallbacks和RepluginEventCallbacks的引用。
以上几个类都可以不设置,如果不设置Replugin会创建一个默认的
二.这里先简单了说一下Replugin框架的核心原理或者说是Replugin的底层原理,Replugin的整体框架使用了Binder机制来进行宿主和多插件之间交互通信和数据共享,这里如果了解android四大组件的运行流程的话,看完了Replugin的源码后会感觉非常像简易ServiceManager和AMS的结构。
Replugin默认会使用一个常驻进程作为Server端,其他插件进程和宿主进程全部属于Client端。当然如果修改不使用常驻进程,那么宿主的主进程将作为插件管理进程,而不管是使用宿主进程还是使用默认的常驻进程,Server端其实就是创建了一个运行在该进程中的Provider,通过Provider的query方法返回了Binder对象来实现多进程直接的的沟通和数据共享,或者说是插件之间和宿主之间沟通和数据共享,插件的安装,卸载,更新,状态判断等全部都在这个Server端完成。
其实Replugin还是使用的占坑的方式来实现的插件化,replugin-host-gradle这个gradle插件会在编译的时候自动将坑位信息生成在主工程的AndroidManifest.xml中,Replugin的唯一hook点是hook了系统了ClassLoader,当启动四大组件的时候会通过Clent端发起远程调用去Server做一系列的事情,例如检测插件是否安装,安装插件,提取优化dex文件,分配坑位,启动坑位,这样可以欺骗系统达到不在AndroidManifest.xml注册的效果,最后在Clent端加载要被启动的四大组件,因为已经hook了系统的ClassLoader,所以可以对系统的类加载过程进行拦截,将之前分配的坑位信息替换成真正要启动的组件信息并使用与之对应的ClassLoader来进行类的加载,从而启动未在AndroidManifest.xml中注册的组件。
Replugin的初始化从大面上来说可以分为三个模块:
1)Replugin框架的初始化:Replugin的整个实现的主体,整体框架感觉非常像一个微型的Android系统,对所有插件的控制操作和系统的AMS等一些类非常的相似
2)Hook住系统的ClassLoader:这也是Replugin与其他插件框架众不同之处,唯一的Hook点保证了系统的稳定性和后期的维护成本
3)加载默认插件:在上面两点初始化完成后,进行了默认插件的加载,加载插件并不只是主程序的工作,其实还要初始化插件工程,将主工程和插件工程绑定,后面才可以完美的相互调用。
由于篇幅的缘故,本章先说框架的初始化,其他两块会在后续文章分别分析
三、源码阅读
1、首先Replugin给我们提供了一个RePluginApplication的类,方便宿主之间继承,如果不继承该类必须手动调用初始化相关方法。初始化的代码要在Application的attachBaseContext中执行,为什么要在这个方法中初始化呢?因为在应用启动后Replugin必须要尽量早的初始化框架相关代码才能够保证后续在使用相关功能的时候可以得到正确的执行,例如我们已经知道了Replugin其中的的核心之一是通过hook住了系统ClassLoader来加载未注册组件的,那么就要尽量早的接管系统的类加载过程,而attachBaseContext是ContextWrapper中的方法,这个方法在Application被创建后,调用attach的方法时被调用的,可以说是应用端可以收到最早的回调。
入口位置 : com.qihoo360.replugin.RePluginApplication
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
//获取宿主创建的RePluginConfig
RePluginConfig c = createConfig();
if (c == null) {
//如果为空Replugin自己创建一个RePluginConfig,
c = new RePluginConfig();
}
//获取宿主中的RePluginCallbacks对象,如果为空后面会自动创建一个默认的
//这个类提供了宿主和插件的ClassLoader
RePluginCallbacks cb = createCallbacks();
if (cb != null) {
//设置给config
c.setCallbacks(cb);
}
//初始化
RePlugin.App.attachBaseContext(this, c);
}
2、上面的代码是在为了初始化做准备, 初始化时会根据RePluginConfig和RePluginCallbacks来进行Replugin的基本设置,上面已经说过了两个类的主要作用,本文主要分析框架初始化,ClassLoader部分后续会单独分析,初始化调用了RePlugin.App.attachBaseContext方法,下面我们去看一下这个方法
源码位置:com.qihoo360.replugin.RePlugin
//App是RePlugin的内部类
public static void attachBaseContext(Application app, RePluginConfig config) {
//保证只会初始化一次
if (sAttached) {
return;
}
//缓存Application
RePluginInternal.init(app);
sConfig = config;
//1.获取pn插件安装目录,即context.getFilesDir()
//2.判断如果RePluginCallbacks为空创建一个
//3.判断RePluginEventCallbacks为空创建一个
sConfig.initDefaults(app);
//初始化进程间通信辅助类,因为不同进程的创建会使attachBaseContext方法走多次,
//IPC.init中会标记当前进程类型,将会影响下面的代码在不同进程中的逻辑
IPC.init(app);
// 初始化HostConfigHelper,通过反射宿主RePluginHostConfig实现的,具体参数请参看RePluginHostConfig
//就是在编译期间replugin-host-gradle自动生成的RepluginHostConfig,这个方法是一个空方法,反射初始化的逻辑在static语句块中
HostConfigHelper.init();
//缓存Application
AppVar.sAppContext = app;
//PluginStatusController用来管理插件的运行状态:
//用来管理插件的状态:正常运行、被禁用,等情况
//设置Application的引用
PluginStatusController.setAppContext(app);
//真正初始化Replugin的框架和hook住了系统的PatchClassLoader地方
//最重要的地方
PMF.init(app);
//加载默认插件
PMF.callAttach();
//标记已经初始化完成,完成以后将不能再修改RepluginConfig类中的设置
sAttached = true;
}
上面初始化重要的地方是初始化多进程通信的辅助类IPC,这个类的多次调用会影响Replugin的整体代码逻辑的执行,还有就是PMF的初始化时创建初始了Replugin需要核心类和hook系统的ClassLoader都在这里。这里的IPC.init非常重要,因为每一个进程创建都会执行一次,会导致后面初始化代码在不同进程中执行不同的逻辑。既然IPC的初始化导致不同逻辑,我们先来看一下IPC.init这个方法
3.看一下IPC初始化:
源码位置:com.qihoo360.replugin.base.IPC
public static void init(Context context) {
//通过proc文件获取当前进程名
sCurrentProcess = SysUtils.getCurrentProcessName();
//获取当前进程pid
sCurrentPid = Process.myPid();
//获取宿主程序包名
sPackageName = context.getApplicationInfo().packageName;
// 判断是否使用“常驻进程”(见PERSISTENT_NAME)作为插件的管理进程
//并设置常驻进程名称,默认常驻进程名称是以:GuardService结尾的,可以通过
//宿主module下的build.gradle的repluginHostConfig{}中设置,很多参数参考宿主生成的RePluginHostConfig类
if (HostConfigHelper.PERSISTENT_ENABLE) {
//设置cppn名称为:GuardService
String cppn = HostConfigHelper.PERSISTENT_NAME;
if (!TextUtils.isEmpty(cppn)) {
if (cppn.startsWith(":")) {
//常驻进程名称为 包名:GuardService
sPersistentProcessName = sPackageName + cppn;
} else {
sPersistentProcessName = cppn;
}
}
} else {
//如果不使用常驻进程管理插件,则使用当前进程名称
sPersistentProcessName = sPackageName;
}
//判断当前进程是否是主进程
sIsUIProcess = sCurrentProcess.equals(sPackageName);
//判断当前线程是不是常驻进程
sIsPersistentProcess = sCurrentProcess.equals(sPersistentProcessName);
}
4.IPC的代码初始化就这么多,主要就是通过proc文件获取当前进程名、进程id和宿主包名,然后设置常驻进程的名称,最后标记当前进程是否是ui进程和是不是常驻进程,我们知道每一个进程在创建后都会调用该进程的ActivitiThread的main方法,开启消息循环,绑定AMS,创建Application,加载provider,然后回调Application生命周