【Android 插件化】Hook 插件化框架总结 ( 插件包管理 | Hook Activity 启动流程 | Hook 插件包资源加载 ) ★★★(三)

简介: 【Android 插件化】Hook 插件化框架总结 ( 插件包管理 | Hook Activity 启动流程 | Hook 插件包资源加载 ) ★★★(三)

2、插件包管理器类


在 init 方法中 , 进行插件的总体初始化操作 , 包括 加载 APK 文件 , 加载 APK 中的资源文件 , Activity 替换 , 加载插件 Activity 资源 ;



loadApk 方法中 , 读取文件系统中的插件包 , 加载其中的 Dex 字节码文件 , 将其合并到宿主字节码数据中 ;


loadResources 方法中 , 读取文件系统中的插件包 , 加载其中的资源文件 ;


package kim.hsl.plugin;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;
/**
 * 使用 Hook 实现的插件使用入口 <br><br>
 *  1. 加载插件包中的字节码<br><br>
 *  2. 直接通过 hook 技术, 钩住系统的 Activity 启动流程实现<br>
 *      ① Activity 对象创建之前 , 要做很多初始化操作 , 先在 ActivityRecord 中加载 Activity 信息<br>
 *          如果修改了该信息 , 将要跳转的 Activity 信息修改为插件包中的 Activity<br>
 *          原来的 Activity 只用于占位 , 用于欺骗 Android 系统<br>
 *      ② 使用 hook 技术 , 加载插件包 apk 中的 Activity<br>
 *      ③ 实现跳转的 Activity ( 插件包中的 )<br><br>
 *  3. 解决 Resources 资源冲突问题
 *  ( 使用上述 hook 插件化 , 可以不用考虑 Activity 的声明周期问题 )
 *  <br><br>
 *  插件包中的 Activity 是通过正规流程 , 由 AMS 进行创建并加载的
 *      但是该 Activity 并没有在 AndroidManifest.xml 清单文件中注册
 *      这里需要一个已经在清单文件注册的 Activity 欺骗系统<br><br>
 *
 *  插装式插件化 是通过代理 Activity , 将插件包加载的字节码 Class 作为一个普通的 Java 类<br>
 *      该普通的 Java 类有所有的 Activity 的业务逻辑<br>
 *      该 Activity 的声明周期 , 由代理 Activity 执行相关的生命周期方法<br>
 *  hook 插件化 : hook 插件化直接钩住系统中 Activity 启动流程的某个点<br>
 *      使用插件包中的 Activity 替换占位的 Activity<br>
 */
public class PluginManager {
    /**
     * 上下文
     */
    private Context mBase;
    /**
     * 单例
     */
    private static PluginManager mInstance;
    /**
     * 要加载的插件包中的资源文件
     */
    private Resources mResources;
    public static PluginManager getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new PluginManager(context);
        }
        return mInstance;
    }
    private PluginManager(Context context) {
        this.mBase = context;
    }
    /**
     * Application 启动后 , 调用该方法初始化插件化环境
     *  加载插件包中的字节码
     */
    public void init() {
        // 加载 apk 文件
        loadApk();
        // 加载插件包中的资源文件
        loadResources();
        // 在 AMS 启动之前使用占坑 Activity 替换插件包 Activity
        HookUtils.hookAms(mBase);
        // 在 AMS 执行完毕后 , 主线程 ActivityThread 中创建 Activity 实例对象之间 ,
        //      再将插件包 Activity 替换回去
        HookUtils.hookActivityThread(mBase);
        // 通过 Hook 方式修改 Activity 中的 Resources 资源
        HookUtils.hookInstrumentation();
    }
    private void loadApk() {
        // 插件包的绝对路径 ,  /data/user/0/com.example.plugin_hook/files , 注意最后没有 " / "
        // 需要手动添加 "/"
        String apkPath = mBase.getFilesDir().getAbsolutePath() + "/plugin.apk";
        // 加载插件包后产生的缓存文件路径
        // /data/data/< package name >/app_plugin_cache/
        String cachePath =
                mBase.getDir("plugin_cache", Context.MODE_PRIVATE).getAbsolutePath();
        // 创建类加载器
        DexClassLoader plugin_dexClassLoader =
                new DexClassLoader(
                        apkPath,                // 插件包路径
                        cachePath,              // 插件包加载时产生的缓存路径
                        null,   // 库的搜索路径, 可以设置为空
                        mBase.getClassLoader()  // 父加载器, PathClassLoader
                );
        // 1. 反射 " 插件包 " 应用的 dexElement
        // 执行步骤 :
        // ① 反射获取 BaseDexClassLoader.class
        // ② 反射获取 BaseDexClassLoader.calss 中的 private final DexPathList pathList 成员字段
        // ③ 反射获取 DexClassLoader 类加载器中的 DexPathList pathList 成员对象
        // ④ 反射获取 DexPathList.class
        // ⑤ 反射获取 DexPathList.class 中的 private Element[] dexElements 成员变量的 Field 字段对象
        // ⑥ 反射获取 DexPathList 对象中的 private Element[] dexElements 成员变量对象
        // ① 反射获取 BaseDexClassLoader.class
        // 通过反射获取插件包中的 dexElements
        // 这种类加载是合并类加载 , 将所有的 Dex 文件 , 加入到应用的 dex 文件集合中
        //  可参考 dex 加固 , 热修复 , 插装式插件化 的实现步骤
        // 反射出 BaseDexClassLoader 类 , PathClassLoader 和 DexClassLoader
        //  都是 BaseDexClassLoader 的子类
        // 参考 https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
        Class<?> baseDexClassLoaderClass = null;
        try {
            baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        // ② 反射获取 BaseDexClassLoader.calss 中的 private final DexPathList pathList 成员字段
        Field plugin_pathListField = null;
        try {
            plugin_pathListField = baseDexClassLoaderClass.getDeclaredField("pathList");
            // 设置属性的可见性
            plugin_pathListField.setAccessible(true);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        // ③ 反射获取 plugin_dexClassLoader 类加载器中的 DexPathList pathList 成员对象
        // 根据 Field 字段获取 成员变量
        //  DexClassLoader 继承了 BaseDexClassLoader, 因此其内部肯定有
        //  private final DexPathList pathList 成员变量
        Object plugin_pathListObject = null;
        try {
            plugin_pathListObject = plugin_pathListField.get(plugin_dexClassLoader);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        // ④ 获取 DexPathList.class
        // DexPathList 类中有 private Element[] dexElements 成员变量
        // 通过反射获取该成员变量
        // 参考 https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
        // 获取 DexPathList pathList 成员变量的字节码类型 ( 也可以通过反射获得 )
        // 获取的是 DexPathList.class
        Class<?> plugin_dexPathListClass = plugin_pathListObject.getClass();
        // ⑤ 反射获取 DexPathList.class 中的 private Element[] dexElements 成员变量的 Field 字段对象
        Field plugin_dexElementsField = null;
        try {
            plugin_dexElementsField = plugin_dexPathListClass.getDeclaredField("dexElements");
            // 设置属性的可见性
            plugin_dexElementsField.setAccessible(true);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        // ⑥ 反射获取 DexPathList 对象中的 private Element[] dexElements 成员变量对象
        Object plugin_dexElementsObject = null;
        try {
            plugin_dexElementsObject = plugin_dexElementsField.get(plugin_pathListObject);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        // 2. 反射 " 宿主 " 应用的 dexElement
        // 执行步骤 :
        // ① 反射获取 BaseDexClassLoader.class
        // ② 反射获取 BaseDexClassLoader.calss 中的 private final DexPathList pathList 成员字段
        // ③ 反射获取 PathClassLoader 类加载器中的 DexPathList pathList 成员对象
        // ④ 反射获取 DexPathList.class
        // ⑤ 反射获取 DexPathList.class 中的 private Element[] dexElements 成员变量的 Field 字段对象
        // ⑥ 反射获取 DexPathList 对象中的 private Element[] dexElements 成员变量对象
        // ① 反射获取 BaseDexClassLoader.class
        Class<?> host_baseDexClassLoaderClass = null;
        try {
            host_baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        // ② 反射获取 BaseDexClassLoader.calss 中的 private final DexPathList pathList 成员字段
        Field host_pathListField = null;
        try {
            host_pathListField = host_baseDexClassLoaderClass.getDeclaredField("pathList");
            // 设置属性的可见性
            host_pathListField.setAccessible(true);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        // ③ 反射获取 DexClassLoader 类加载器中的 DexPathList pathList 成员对象
        // 根据 Field 字段获取 成员变量
        //  DexClassLoader 继承了 BaseDexClassLoader, 因此其内部肯定有
        //  private final DexPathList pathList 成员变量
        PathClassLoader host_pathClassLoader = (PathClassLoader) mBase.getClassLoader();
        Object host_pathListObject = null;
        try {
            host_pathListObject = host_pathListField.get(host_pathClassLoader);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        // ④ 获取 DexPathList.class
        // DexPathList 类中有 private Element[] dexElements 成员变量
        // 通过反射获取该成员变量
        // 参考 https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
        // 获取 DexPathList pathList 成员变量的字节码类型 ( 也可以通过反射获得 )
        // 获取的是 DexPathList.class
        Class<?> host_dexPathListClass = host_pathListObject.getClass();
        // ⑤ 反射获取 DexPathList.class 中的 private Element[] dexElements 成员变量的 Field 字段对象
        Field host_dexElementsField = null;
        try {
            host_dexElementsField = host_dexPathListClass.getDeclaredField("dexElements");
            // 设置属性的可见性
            host_dexElementsField.setAccessible(true);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        // ⑥ 反射获取 DexPathList 对象中的 private Element[] dexElements 成员变量对象
        Object host_dexElementsObject = null;
        try {
            host_dexElementsObject = host_dexElementsField.get(host_pathListObject);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        // 3. 合并 “插件包“ 与 “宿主“ 中的 Element[] dexElements
        // 将两个 Element[] dexElements 数组合并 ,
        //  合并完成后 , 设置到 PathClassLoader 中的
        //  DexPathList pathList 成员的 Element[] dexElements 成员中
        // 获取 “宿主“ 中的 Element[] dexElements 数组长度
        int host_dexElementsLength = Array.getLength(host_dexElementsObject);
        // 获取 “插件包“ 中的 Element[] dexElements 数组长度
        int plugin_dexElementsLength = Array.getLength(plugin_dexElementsObject);
        // 获取 Element[] dexElements 数组中的 , 数组元素的 Element 类型
        // 获取的是 Element.class
        Class<?> elementClazz = host_dexElementsObject.getClass().getComponentType();
        // 合并后的 Element[] dexElements 数组长度
        int new_dexElementsLength = plugin_dexElementsLength + host_dexElementsLength;
        // 创建 Element[] 数组 , elementClazz 是 Element.class 数组元素类型
        Object newElementsArray = Array.newInstance(elementClazz, new_dexElementsLength);
        //  为新的 Element[] newElementsArray 数组赋值
        //      先将 “插件包“ 中的 Element[] dexElements 数组放入到新数组中
        //      然后将 “宿主“ 中的 Element[] dexElements 数组放入到新数组中
        for (int i = 0; i < new_dexElementsLength; i++) {
            if (i < plugin_dexElementsLength) {
                // “插件包“ 中的 Element[] dexElements 数组放入到新数组中
                Array.set(newElementsArray, i, Array.get(plugin_dexElementsObject, i));
            } else {
                //  “宿主“ 中的 Element[] dexElements 数组放入到新数组中
                Array.set(newElementsArray, i, Array.get(host_dexElementsObject, i - plugin_dexElementsLength));
            }
        }
        // 4. 重新设置 PathClassLoader 中的 DexPathList pathList 成员的 Element[] dexElements 属性值
        Field elementsFiled = null;
        try {
            elementsFiled = host_pathListObject.getClass().getDeclaredField("dexElements");
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        elementsFiled.setAccessible(true);
        //  设置 DexPathList pathList 的 Element[] dexElements 属性值
        //      host_pathListObject 是原来的属性值
        //      newElementsArray 是新的合并后的 Element[] dexElements 数组
        //  注意 : 这里也可以使用 host_dexElementsField 字段进行设置
        try {
            elementsFiled.set(host_pathListObject, newElementsArray);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    /**
     * 加载资源文件
     * @return
     */
    public Resources loadResources() {
        // 使用反射工具类进行链式调用 , 创建 AssetManager 对象
        AssetManager assetManager = Reflector.on(AssetManager.class).newInstance();
        // 获取插件包 APK 文件路径 , 加载该 APK 下的资源
        // /data/user/0/com.example.plugin_hook/files/plugin.apk
        String pluginPath = mBase.getFilesDir() + "/plugin.apk";
        // 使用反射调用 AssetManager 中的 addAssetPath 方法 , 传入 APK 插件包的路径
        // addAssetPath 方法的参数为 /data/user/0/com.example.plugin_hook/files/plugin.apk
        Reflector.on(assetManager).method("addAssetPath", String.class).call(pluginPath);
        // 创建 Resources 并返回
        return mResources = new Resources(
                assetManager,
                mBase.getResources().getDisplayMetrics(),
                mBase.getResources().getConfiguration()
        );
    }
    public Resources getResources() {
        return mResources;
    }
}




目录
相关文章
|
5小时前
|
Android开发
Android Mediatek bootloader oem锁定和解锁流程
Android Mediatek bootloader oem锁定和解锁流程
21 0
|
5小时前
|
SQL 缓存 安全
Android ORM 框架之 greenDAO
Android ORM 框架之 greenDAO
37 0
|
4小时前
|
Java Android开发
Android 切换壁纸代码流程追踪
Android 切换壁纸代码流程追踪
15 0
|
4小时前
|
编解码 调度 Android开发
Android音频框架之一 详解audioPolicy流程及HAL驱动加载与配置
Android音频框架之一 详解audioPolicy流程及HAL驱动加载与配置
16 0
|
4小时前
|
Java Android开发
Android startActivity流程
Android startActivity流程
6 0
|
5小时前
|
缓存 Java Android开发
Android 9.0 WiFi 扫描结果上报和获取流程
Android 9.0 WiFi 扫描结果上报和获取流程
9 0
|
5小时前
|
Java Linux Android开发
Android系统的启动流程
Android系统的启动流程
17 1
|
5小时前
|
前端开发 API vr&ar
Android开发之OpenGL绘制三维图形的流程
即将连载的系列文章将探索Android上的OpenGL开发,这是一种用于创建3D图形和动画的技术。OpenGL是跨平台的图形库,Android已集成其API。文章以2D绘图为例,解释了OpenGL的3个核心元素:GLSurfaceView(对应View)、GLSurfaceView.Renderer(类似Canvas)和GL10(类似Paint)。通过将这些结合,Android能实现3D图形渲染。文章介绍了Renderer接口的三个方法,分别对应2D绘图的构造、测量布局和绘制过程。示例代码展示了如何在布局中添加GLSurfaceView并注册渲染器。
54 1
Android开发之OpenGL绘制三维图形的流程
|
Android开发 Java
如何在Android Studio中导入包:android-support-v4/Fragment
版权声明:转载请联系本人,感谢配合!本站地址:http://blog.csdn.net/nomasp https://blog.csdn.net/NoMasp/article/details/48710143 ...
3240 0
|
5小时前
|
存储 安全 Android开发
安卓应用开发:构建一个高效的用户登录系统
【5月更文挑战第3天】在移动应用开发中,用户登录系统的设计与实现是至关重要的一环。对于安卓平台而言,一个高效、安全且用户体验友好的登录系统能够显著提升应用的用户留存率和市场竞争力。本文将探讨在安卓平台上实现用户登录系统的最佳实践,包括对最新身份验证技术的应用、安全性考量以及性能优化策略。