前文《Replugin插件化技术解读之框架初始化、插件安装与加载(一)》主要从Replugin源码角度分析了Replugin框架初始化整体流程,本文承接上文,框架初始化过程中扫描出是位于asset目录下的内置插件,那么外置插件又是如何安装的呢?各个插件又是如何加载,资源调用的呢?
一、外置插件安装
外置插件的安装其实很简单,直接调用Replugin.install(String apkPath)方法实现。内部逻辑中会判断外置插件路径合法性,并根据是常驻进程或者非常驻进程来获取IPluginHost对象去执行插件安装接口。IPluginHost常驻和非常驻进程区分详见《Replugin插件化技术解读之框架初始化、插件安装与加载(一)》
PluginInfo info = PluginProcessMain.getPluginHost().pluginDownloaded(path);
同时会通过广播的形式通知外部业务,最终会将外置插件相关数据信息如同 《Replugin插件化技术解读之框架初始化、插件安装与加载(一)》讲解
的放至PmBase存储所有插件信息的变量 Map二、 插件的加载
外部插件和内部插件的加载流程都是一样的。当我们用RepluginClassLoader去host替换掉当前进程的ClassLoader之后,在类加载比如插件内部Activity的时候,会首先调用PMF.loadclass类去看看对应插件内部类加载器DexClassLoader去加载。
插件Class加载大致流程:
插件Class -> PMF.loadClass -> PmBase.loadClass-> 存储插件信息容器mPlugins获取对应Plugin对象 ->Plugin.load -> 加载插件(首次) -> Plugin.loadLocked加载插件内部Resource、so库、插件内部ClassLoader创建,生成插件内部PluginContext -> 通过反射初始化插件内部Entry对象 -> 插件加载成功 -> 加载插件Class
我们根据类加载Plugin内部Class的场景走读下插件的加载流程
第1步:PmBase.loadClass中在类加载插件Class,其实这些目标Class基本就是插件中定义的四大组件了,但是系统走到这里的时候其实还都是在宿主app中定义的“坑位”,所以需要在这里将“坑位”替换为目标插件组件如Activity。(坑位思想以及插件Activity调用会在另一篇博客详细介绍,这里带过)。
final Class> loadClass(String className, boolean resolve) {
if (mContainerActivities.contains(className)) {
Class> c = mClient.resolveActivityClass(className);
PluginProcessPer.java
/**
* 类加载器根据容器解析到目标的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=
return ForwardActivity.class;
}
plugin = state.plugin;
activity = state.activity;
Plugin p = mPluginMgr.loadAppPlugin(plugin);
if (p == null) {
// PACM: loadActivityClass, not found plugin
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "load fail: c=" + container + " p=" + plugin + " t=" + activity);
}
return null;
}
ClassLoader cl = p.getClassLoader();
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: loadActivityClass, plugin activity loader: in=" + container + " activity=" + activity);
}
Class> c = null;
try {
c = cl.loadClass(activity);
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, e.getMessage(), e);
}
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: loadActivityClass, plugin activity loader: c=" + c + ", loader=" + cl);
}
return c;
}
这里会直接使用Plugin p =mPluginMgr.loadAppPlugin(plugin),根据plugin名去加载此插件,也就是调用PmBase的loadPlugin(mPlugins.get(plugin), Plugin.LOAD_APP,true)方法,mPlugins这个我们很熟悉了,就是插件安装或者框架初始化扫描出的存储所有插件信息的Map容器啦。
final Plugin loadPlugin(Plugin p, int loadType, boolean useCache) {
if (p == null) {
return null;
}
if (!p.load(loadType, useCache)) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "pmb.lp: f to l. lt=" + loadType + "; i=" + p.mInfo);
}
return null;
}
return p;
}
第2步:插件加载关键接口就是Plugin.load()方法了,内部会调用Plugin.loadLoaked方法,有一系列的判断,如插件状态是否被禁用,是否已经加载过,是否在缓存中存在等等,如果是首次加载此插件就会走doLoad方法
boolean rc = doLoad(logTag, context, parent, manager, load);
doLoad中主要是初始化Plugin的Loader mLoader对象,
Loader 中封装了Plugin关于资源、so库、PluginContext的所有信息。
private final boolean doLoad(String tag, Context context, ClassLoader parent, IPluginManager manager, int load) {
。。。
mLoader = new Loader(context, mInfo.getName(), mInfo.getPath(), this);
if (!mLoader.loadDex(parent, load)) {
return false;
}
// 若需要加载Dex,则还同时需要初始化插件里的Entry对象
if (load == LOAD_APP) {
// NOTE Entry对象是可以在任何线程中被调用到
if (!loadEntryLocked(manager)) {
return false;
}
}
。。。
}
Loader.loadDex就是在家插件dex的入口方法,loadDex中会使用ApkParse去解析插件AndroidManifest中注册的插件四大组件相关信息,并初始化到辅助类ComponetList mComponents中,插件Resource、插件ClassLoader、插件PluginContex也是在这里初始化好的哦。
final boolean loadDex(ClassLoader parent, int load) {
。。。
//1. 解析插件AndroidManifest配置的四大组件信息
mComponents = new ComponentList(mPackageInfo, mPath, mPluginName);
。。。
//2. 初始化插件Resource对象
Resources r = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
mPkgResources = new Resources(r.getAssets(), r.getDisplayMetrics(), r.getConfiguration());
。。。
//3. 初始化插件DexClassClassLoader对象
mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPath, out, soDir, parent);
。。。
//4. 初始化插件PluginContext对象
mPkgContext = new PluginContext(mContext, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this);
。。。
}
这里重点讲解下loadDex过程中ComPonentList、Resource、CLassLoader、PluginContext的作用
1. ComPonentList: Replugin中插件其实就是我们正常使用的单品app,只是在gradle动态编译过程中会对插件进行一些字节码层次的修改,所以插件中也是有AndroidManifest的,会注册插件app本身定义的四大组件、Application等。所以这些信息我们都要通过Manifest文件解析出来存储好,比如Activity、Broadcast的Intent-Filiter过滤的映射信息我们肯定是要保存。对于插件静态注册的广播,我们提取出来直接通过宿主动态注册的形式注册。
2. Resource:正常的单品apk,我们常用R.XX.XX形式去获取资源,其实底层实现都是通过系统为应用自动生成的底层Resource去实现的。但是现在插件本身已经不是一个正常的app了,系统不会去自动去给它创建的,那么我们就需要仿照系统创建流程,自己diy一个插件Resource来给它用,保证插件内部R.XX.XX形式能够正常的使用。
3. 插件ClassLoader,我们知道Android中类加载器有PathClassLoader和DexClassLoader,PathClassLoader主要用于已经安装过的apk中,而插件是没有安装的,所以我们需要使用能够加载存储外部的dex的类加载器来加载插件中的类。
4.PluginContext,Context上下午的概念不用多讲了,对于插件的上下文,同样是没有的,所以需要我们同样diy一个出来。
第3步: 第2步中Loader初始化成功后,还需要加载插件的Entry类对象完成插件的初始化工作,具体逻辑在Plugin.loadEntryLocked方法中。这里会直接通过反射调用位于replugin-plugin-lib库中的Entry方法。if (mLoader.loadEntryMethod3()) {
if (!mLoader.invoke2(manager)) {
return false;
}
这里,已经开始走到plugin-lib库中的逻辑了,在Entry中的create方法就是实现插件框架的初始化逻辑,相比宿主框架初始化逻辑无疑简单了很多,主要涉及的功能就是通过反射技术获取宿主框架的一些功能,
并传入了宿主的ClassLoader,同时插件上下文PluginContext也在这个时候传入。
public class Entry {
/**
* @param context 插件上下文
* @param cl HOST程序的类加载器
* @param manager 插件管理器
* @return
*/
public static final IBinder create(Context context, ClassLoader cl, IBinder manager) {
// 初始化插件框架
RePluginFramework.init(cl);
// 初始化Env
RePluginEnv.init(context, cl, manager);
return new IPlugin.Stub() {
@Override
public IBinder query(String name) throws RemoteException {
return RePluginServiceManager.getInstance().getService(name);
}
};
}
}
第4步:Plugin.load中loadLocked方法执行结束后,会去尝试回调插件Apk定义的Application方法,为什么要这么做呢?我们知道Replugin的插件apk同样也可以作为单品发布的,所以想要插件apk逻辑正常,在首次加载插件时候其插件的Application肯定要初始化的才行,并且还应该在UI线程自动回调执行onCreate等方法。这里就不细加分析啦。
final boolean load(int load, boolean useCache) {
PluginInfo info = mInfo;
boolean rc = loadLocked(load, useCache);
// 尝试在此处调用Application.onCreate方法
// Added by Jiongxuan Zhang
if (load == LOAD_APP && rc) {
callApp();
}
1. Replugin插件区分外置插件和内置插件,内置插件也就是存储在asset目录下的会在进程刚起来框架初始化的时候会自动扫描出来,而外置插件,则需要代码驱动去识别。
2. 插件加载使用ApkParse去解析插件Manifest,获取插件中四大组件的配置,初始化插件DexClassLoader、Resource、PluginContext来用于插件内部资源调用,类加载等操作。