Replugin 的 Hook 机制以 唯一 Hook 点(ClassLoader) 为核心,通过轻量级改造实现插件化动态加载。以下为具体 Hook 点及流程解析:
一、核心 Hook 点
-
宿主 ClassLoader 替换
- 目标对象:宿主的默认
PathClassLoader - 实现方式:在宿主应用启动时,通过反射将
ClassLoader替换为自定义的RePluginClassLoader,继承自PathClassLoader并重写关键方法。 - 作用:接管所有类的加载逻辑,优先从插件加载类。
- 目标对象:宿主的默认
唯一的hook点 就是在patch中,将系统的ClassLoader替换为为RePluginClassLoader
public class PatchClassLoaderUtils {
private static final String TAG = "PatchClassLoaderUtils";
//唯一的hook点 就是在patch中
public static boolean patch(Application application) {
try {
// 获取Application的BaseContext (来自ContextWrapper)
Context oBase = application.getBaseContext();
if (oBase == null) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "pclu.p: nf mb. ap cl=" + application.getClass());
}
return false;
}
// 获取mBase.mPackageInfo
// 1. ApplicationContext - Android 2.1
// 2. ContextImpl - Android 2.2 and higher
// 3. AppContextImpl - Android 2.2 and higher
Object oPackageInfo = ReflectUtils.readField(oBase, "mPackageInfo");
if (oPackageInfo == null) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "pclu.p: nf mpi. mb cl=" + oBase.getClass());
}
return false;
}
// mPackageInfo的类型主要有两种:
// 1. android.app.ActivityThread$PackageInfo - Android 2.1 - 2.3
// 2. android.app.LoadedApk - Android 2.3.3 and higher
if (LOG) {
Log.d(TAG, "patch: mBase cl=" + oBase.getClass() + "; mPackageInfo cl=" + oPackageInfo.getClass());
}
// 获取mPackageInfo.mClassLoader
ClassLoader oClassLoader = (ClassLoader) ReflectUtils.readField(oPackageInfo, "mClassLoader");
if (oClassLoader == null) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "pclu.p: nf mpi. mb cl=" + oBase.getClass() + "; mpi cl=" + oPackageInfo.getClass());
}
return false;
}
// 外界可自定义ClassLoader的实现,但一定要基于RePluginClassLoader类
ClassLoader cl = RePlugin.getConfig().getCallbacks().createClassLoader(oClassLoader.getParent(), oClassLoader);
// 将新的ClassLoader写入mPackageInfo.mClassLoader
ReflectUtils.writeField(oPackageInfo, "mClassLoader", cl);
// 设置线程上下文中的ClassLoader为RePluginClassLoader
// 防止在个别Java库用到了Thread.currentThread().getContextClassLoader()时,“用了原来的PathClassLoader”,或为空指针
Thread.currentThread().setContextClassLoader(cl);
if (LOG) {
Log.d(TAG, "patch: patch mClassLoader ok");
}
} catch (Throwable e) {
e.printStackTrace();
return false;
}
return true;
}
}
-
双亲委派模型改造
-
目标方法:
RePluginClassLoader.loadClass() -
流程:
- 优先通过插件
PluginDexClassLoader加载类; - 若未命中,委托至宿主原
ClassLoader(即父类加载器)加载36。
- 优先通过插件
-
代码示意:这个是伪代码,示意逻辑 通过继承系统PathClassLoader,并重载loadClass()方法,通过PMF.loadClass优先加载插件class
protected Class<?> loadClass(String name, boolean resolve) { Class<?> clazz = pluginClassLoader.loadClass(name); // 插件优先 if (clazz == null) { clazz = super.loadClass(name, resolve); // 宿主兜底 } return clazz; }
具体代码如下
-
@Override
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
//
Class<?> c = null;
c = PMF.loadClass(className, resolve); //插件优先
if (c != null) {
return c;
}
//
try {
c = mOrig.loadClass(className); //宿主兜底
// 只有开启“详细日志”才会输出,防止“刷屏”现象
if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) {
LogDebug.d(TAG, "loadClass: load other class, cn=" + className);
}
return c;
} catch (Throwable e) {
//
}
//
return super.loadClass(className, resolve);
}
-
Instrumentation 拦截
-
目标方法:
Instrumentation.newActivity() -
流程:
- 在启动插件 Activity 时,通过 Hook 该方法将占位 Activity(如
PitActivity)替换为插件真实 Activity 类; - 动态注入插件
ClassLoader和Resources上下文26。
- 在启动插件 Activity 时,通过 Hook 该方法将占位 Activity(如
-
二、Hook 流程解析
阶段 1:初始化宿主 Hook
-
替换 ClassLoader
-
在
Application.attachBaseContext()阶段,反射获取宿主的PathClassLoader; -
构建
RePluginClassLoader实例并替换宿主默认加载器17。 -
关键代码:
ClassLoader originalLoader = getClassLoader(); RePluginClassLoader newLoader = new RePluginClassLoader(originalLoader); ReflectUtils.setField(ActivityThread.currentActivityThread(), "mClassLoader", newLoader);
-
-
预加载公共类
- 通过 Gradle 插件在编译期生成占位组件(如 Activity、Service),并预注册至宿主 Manifest15。
阶段 2:插件加载与类路由
-
插件安装
- 调用
RePlugin.install()释放插件 APK 至私有目录,并创建PluginDexClassLoader实例,其父加载器指向RePluginClassLoader57。 - 资源隔离:通过
AssetManager.addAssetPath()合并插件资源路径,生成独立Resources对象68。
- 调用
-
动态类加载
- 当调用插件组件时,
RePluginClassLoader根据类名匹配插件标识,路由至对应的PluginDexClassLoader加载目标类37。
- 当调用插件组件时,
阶段 3:组件启动拦截
-
Activity 启动流程
- Intent 替换:将插件 Activity 的类名替换为宿主预注册的占位 Activity 类名,绕过 AMS 校验26。
- 实例化拦截:在
Instrumentation.newActivity()中,通过插件ClassLoader加载真实 Activity 类,并绑定插件上下文28。
-
其他组件处理
- Service:通过
ActivityManagerProxy拦截startService调用,动态创建插件 Service 实例48。 - BroadcastReceiver:静态广播转为动态注册,通过
PackageParser解析插件 Manifest 并注册至系统8。
- Service:通过
三、与其他框架对比
| 框架 | Hook 点数量 | 稳定性 | 侵入性 |
|---|---|---|---|
| RePlugin | 1(ClassLoader) | 高(阿里系验证) | 低(无继承要求)16 |
| DroidPlugin | 多(AMS、PMS 等) | 低(易崩溃) | 高(需继承基类)8 |
| VirtualApp | 全面 Hook | 中 | 高(沙箱环境)8 |
技术优势
- 轻量 Hook:仅改造
ClassLoader和Instrumentation,避免深度 Hook 系统服务16。 - 兼容性:适配 Android 6.0-13,兼容主流厂商 ROM68。
- 开发透明:插件组件无需继承特定基类,与原生开发体验一致17。
通过上述机制,RePlugin 在 稳定性 与 灵活性 之间取得平衡,成为轻量化插件框架的典型代表