1.replugin-host-gralde: 宿主脚本
2.replugin-host-library: 宿主库
3.replugin-plugin-gradle: 插件脚本
4.replugin-plugin-library:插件库
打算从以上四个模块对replugin进行拆解阅读
RePlugin的思想是Hook ClassLoader, 因此接下来就围绕ClassLoader进行分析
一、流程总结
1.1 插件的安装
1.2 内置插件
1.3 外置插件
1.4 replugin-host-gradle
1.5 replugin-host-library
1.6 replugin-plugin-gradle
1.7 replugin-plugin-library
1.1 插件的安装
RePlugin插件的安装并不会真正的处理插件APK中的dex、so库、资源等, 只是将插件移动到需要的位置, 这个位置默认是宿主的context.getFilesDir(), 然后将插件信息包装成Plugin对象并绑定宿主的Context、宿主ClassLoader、负责和宿主通信的PluginContextImpl类, 最后将Plugin对象存入插件管理进程统一管理.
1.2 内置插件
内置插件的安装是在初始化时就自动安装和加载了
1.3 外置插件
外置插件的安装需要调用Replugin.install()方法来安装插件, 这个过程和内置插件类似, 区别就是内置插件是通过assets目录下的json文件来生成插件对象, 外置插件则是通过获取插件apk的PackageInfo来生成插件对象, 但是并不会处理apk中的dex、so库、资源等, 只有当真正使用这个插件中的类时才会去真正的解析加载这个插件
1.4 replugin-host-gradle
主程序使用的Gradle插件, 主要职责是在我们的主程序打包的过程中(编译的过程中)动态的修改AndroidManifest.xml的信息, 动态的生成占位各种Activity、provider和service的声明.
其次还会动态生成一个HostBuildConfig的java类, 这个类是根据app下的build.gradle中配置的参数信息(repluginHostConfig)产生的, 这个类的路径在BuildConfig的同级目录的gen包下.
然后会扫描内置插件目录assets/plugins目录, 解析插件文件生成包含文件名、包名、版本、路径的plugins-build.json文件, 这个文件的路径在assets目录.
1.5 replugin-host-library
这个库是由主程序依赖的, 也是RePlugin的核心, 它的主要职责是初始化Replugin的整体框架, 整体框架使用了Binder机制来实现多进程直接的沟通和数据共享, 或者说是插件之间和宿主之间沟通和数据共享, hook住ClassLoader, 加载插件、启动插件、多插件的管理全部都由这个库辅助
1.6 replugin-plugin-gradle
这个是插件工程使用的gradle插件, 这个库使用了Transform API和Javassist实现了编译期间动态修改字节码文件, 主要是替换插件工程中的Activity的继承全部替换成Replugin库中定义的XXXActivity, 动态的将插件APK中调用LocalBroadcastManager的地方修改为Replugin中的PluginLocalBroadcastManager调用, 动态修改ContentResolver和ContentProvider的调用修改成Replugin调用, 动态的修改插件工程中所有调用Resource.getIdentifier方法的地方, 将第三个参数修改为插件工程的包名
1.7 replugin-plugin-library
这个库是由插件工程依赖的, 这个库的主要目的是通过反射的方式来使主程序中接口和功能, 这个库在主程序加载插件apk后悔进行初始化.
二、RePluginClassLoader初始化
2.1 ClassLoader
2.2 RePluginApplication初始化
2.3 PatchClassLoaderUtils.patch
2.4 创建RePluginClassLoader
2.1 ClassLoader
Android系统默认三个ClassLoader
1. BootClassLoader: 系统启动时创建, 一般不需要用到. 加载系统类
2. PathClassLoader: 应用启动时创建, 只能加载内部dex
3. DexClassLoader: 可以加载外部的dex
Replugin提供了两个ClassLoader
RePluginClassLoader:
宿主APP中的ClassLoader, 加载应用内部的Activity
PluginDexClassLoader:
加载插件的Loader.
public class RePluginClassLoader extends PathClassLoader;
public class PluginDexClassLoader extends DexClassLoader;
2.2 RePluginApplication初始化
调用链:
RepluginApplication.attachBaseContext -> RepluginApplication.App.attachBaseContext
-> PMF.init()
-> PatchClassLoaderUtils.patch()
2.3 PatchClassLoaderUtils.patch
public static boolean patch(Application application) {
//1.oBase指向ContextImpl: 具体结合LoadedApk.makeApplication()
Context oBase = application.getBaseContext();
// 2.反射获取ComTextImpl.mPackageInfo变量 -> LoadedApk
Object oPackageInfo = ReflectUtils.readField(oBase, "mPackageInfo");
// 3.反射获取LoadedApk.mClassLoader -> ClassLoader = PathClassLoader
ClassLoader oClassLoader = (ClassLoader) ReflectUtils.readField(oPackageInfo, "mClassLoader");
// 外界可自定义ClassLoader的实现,但一定要基于RePluginClassLoader类
// 4.创建RePluginClassLoader
ClassLoader cl = RePlugin.getConfig().getCallbacks().createClassLoader(oClassLoader.getParent(), oClassLoader);
// 将新的ClassLoader写入mPackageInfo.mClassLoader
// 5.将新创建的RePluginClassLoader替换掉LoadedApk中的PathClassLoader
ReflectUtils.writeField(oPackageInfo, "mClassLoader", cl);
// 设置线程上下文中的ClassLoader为RePluginClassLoader
// 防止在个别Java库用到了Thread.currentThread().getContextClassLoader()时,“用了原来的PathClassLoader”,或为空指针
Thread.currentThread().setContextClassLoader(cl);
return true;
}
2.4 创建RePluginClassLoader
RePlugin.getConfig().getCallbacks().createClassLoader:
/**
* @param parent 该ClassLoader的父亲,通常为BootClassLoader
* @param original 宿主的原ClassLoader,通常为PathClassLoader
*/
public RePluginClassLoader(ClassLoader parent, ClassLoader orig) {
// 由于PathClassLoader在初始化时会做一些Dir的处理,所以这里必须要传一些内容进来
// 但我们最终不用它,而是拷贝所有的Fields
super("", "", parent);
mOrig = orig;
// 将原来宿主里的关键字段,拷贝到这个对象上,这样骗系统以为用的还是以前的东西(尤其是DexPathList)
// 注意,这里用的是“浅拷贝”
copyFromOriginal(orig);
initMethods(orig);
}
三、加载插件
3.1 Replugin.startActivity
3.2 PluginLibraryInternalProxy.startActivity
3.1 Replugin.startActivity
// 调用链:
Replugin.startActivity() -> Factory.startActivityWithNoInjectCN()
-> PluginCommImpl.startActivity()
-> PluginLibraryInternalProxy.startActivity()
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);
}
3.2 PluginLibraryInternalProxy.startActivity
/**
* 启动一个插件中的activity, 如果插件不存在会触发下载界面
* @param context 应用上下文或Activity上下文
* @param intent
* @param plugin 插件名
* @param activity 待启动的activity类名
* @param process 是否在指定进程中启动
* @param download 下载
* @return 插件机制是否成功, 例如没有插件存在, 没有合适的Activity坑
*/
public boolean startActivity(Context context, Intent intent, String plugin, String activity, int process, boolean download) {
// 是否启动下载
// 若插件不可用(不存在或版本不匹配),则直接弹出“下载插件”对话框
// 因为已经打开UpdateActivity,故在这里返回True,告诉外界已经打开,无需处理
if (download) {
if (PluginTable.getPluginInfo(plugin) == null) {
// 如果用户在下载即将完成时突然点按“取消”,则有可能出现插件已下载成功,但没有及时加载进来的情况
// 因此我们会判断这种情况,如果是,则重新加载一次即可,反之则提示用户下载
// 原因:“取消”会触发Task.release方法,最终调用mDownloadTask.destroy,导致“下载服务”的Receiver被注销,即使文件下载了也没有回调回来
// NOTE isNeedToDownload方法会调用pluginDownloaded再次尝试加载
if (isNeedToDownload(context, plugin)) {
return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process);
}
}
}
/* 检查是否是动态注册的类 */
// 如果要启动的 Activity 是动态注册的类,则不使用坑位机制,而是直接动态类。
// 原因:宿主的某些动态注册的类不能运行在坑位中(如'桌面'插件的入口Activity)
if (Factory2.isDynamicClass(plugin, activity)) {
intent.putExtra(IPluginManager.KEY_COMPATIBLE, true);
intent.setComponent(new ComponentName(IPC.getPackageName(), activity));
context.startActivity(intent);
return true;
}
// 如果插件状态出现问题,则每次弹此插件的Activity都应提示无法使用,或提示升级(如有新版)
if (PluginStatusController.getStatus(plugin) < PluginStatusController.STATUS_OK) {
return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process);
}
// 若为首次加载插件,且是“大插件”,则应异步加载,同时弹窗提示“加载中”
// Added by Jiongxuan Zhang
if (!RePlugin.isPluginDexExtracted(plugin)) {
PluginDesc pd = PluginDesc.get(plugin);
if (pd != null && pd.isLarge()) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PM.startActivity(): Large Plugin! p=" + plugin);
}
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));
}
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);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start activity: real intent=" + intent);
}
// if (RePluginInternal.FOR_DEV) {
// try {
// String str = cn.getPackageName() + "/" + cn.getClassName();
// if (LOG) {
// LogDebug.d(PLUGIN_TAG, "str=" + str);
// }
// new ProcessBuilder().command("am", "start", "-D", "--user", "0", "-n", str).start();
// } catch (IOException e) {
// e.printStackTrace();
// }
// } else {
context.startActivity(intent);
// 通知外界,已准备好要打开Activity了
// 其中:from为要打开的插件的Intent,to为坑位Intent
RePlugin.getConfig().getEventCallbacks().onPrepareStartPitActivity(context, from, intent);
return true;
}