Replugin 的hook点以及hook流程

153 阅读4分钟

Replugin 的 Hook 机制以 ‌唯一 Hook 点(ClassLoader) ‌ 为核心,通过轻量级改造实现插件化动态加载。以下为具体 Hook 点及流程解析:


一、核心 Hook 点

  1. 宿主 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;
    }
}
  1. 双亲委派模型改造

    • 目标方法‌: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);
}
  1. Instrumentation 拦截

    • 目标方法‌:Instrumentation.newActivity()

    • 流程‌:

      • 在启动插件 Activity 时,通过 Hook 该方法将占位 Activity(如 PitActivity)替换为插件真实 Activity 类;
      • 动态注入插件 ClassLoaderResources 上下文‌26。

二、Hook 流程解析

阶段 1:初始化宿主 Hook

  1. 替换 ClassLoader

    • Application.attachBaseContext() 阶段,反射获取宿主的 PathClassLoader

    • 构建 RePluginClassLoader 实例并替换宿主默认加载器‌17。

    • 关键代码‌:

      ClassLoader originalLoader = getClassLoader();
      RePluginClassLoader newLoader = new RePluginClassLoader(originalLoader);
      ReflectUtils.setField(ActivityThread.currentActivityThread(), "mClassLoader", newLoader);
      
  2. 预加载公共类

    • 通过 Gradle 插件在编译期生成占位组件(如 Activity、Service),并预注册至宿主 Manifest‌15。

阶段 2:插件加载与类路由

  1. 插件安装

    • 调用 RePlugin.install() 释放插件 APK 至私有目录,并创建 PluginDexClassLoader 实例,其父加载器指向 RePluginClassLoader‌57。
    • 资源隔离‌:通过 AssetManager.addAssetPath() 合并插件资源路径,生成独立 Resources 对象‌68。
  2. 动态类加载

    • 当调用插件组件时,RePluginClassLoader 根据类名匹配插件标识,路由至对应的 PluginDexClassLoader 加载目标类‌37。

阶段 3:组件启动拦截

  1. Activity 启动流程

    • Intent 替换‌:将插件 Activity 的类名替换为宿主预注册的占位 Activity 类名,绕过 AMS 校验‌26。
    • 实例化拦截‌:在 Instrumentation.newActivity() 中,通过插件 ClassLoader 加载真实 Activity 类,并绑定插件上下文‌28。
  2. 其他组件处理

    • Service‌:通过 ActivityManagerProxy 拦截 startService 调用,动态创建插件 Service 实例‌48。
    • BroadcastReceiver‌:静态广播转为动态注册,通过 PackageParser 解析插件 Manifest 并注册至系统‌8。

三、与其他框架对比

框架Hook 点数量稳定性侵入性
RePlugin1(ClassLoader)高(阿里系验证)低(无继承要求)‌16
DroidPlugin多(AMS、PMS 等)低(易崩溃)高(需继承基类)‌8
VirtualApp全面 Hook高(沙箱环境)‌8

技术优势

  1. 轻量 Hook‌:仅改造 ClassLoaderInstrumentation,避免深度 Hook 系统服务‌16。
  2. 兼容性‌:适配 Android 6.0-13,兼容主流厂商 ROM‌68。
  3. 开发透明‌:插件组件无需继承特定基类,与原生开发体验一致‌17。

通过上述机制,RePlugin 在 ‌稳定性‌ 与 ‌灵活性‌ 之间取得平衡,成为轻量化插件框架的典型代表‌