Android 插件化系列文章目录
【Android 插件化】插件化简介 ( 组件化与插件化 )
【Android 插件化】插件化原理 ( JVM 内存数据 | 类加载流程 )
【Android 插件化】插件化原理 ( 类加载器 )
【Android 插件化】“ 插桩式 “ 插件化框架 ( 原理与实现思路 )
【Android 插件化】“ 插桩式 “ 插件化框架 ( 类加载器创建 | 资源加载 )
【Android 插件化】“ 插桩式 “ 插件化框架 ( 注入上下文的使用 )
【Android 插件化】“ 插桩式 “ 插件化框架 ( 获取插件入口 Activity 组件 | 加载插件 Resources 资源 )
【Android 插件化】“ 插桩式 “ 插件化框架 ( 运行应用 | 代码整理 )
【Android 插件化】Hook 插件化框架 ( Hook 技术 | 代理模式 | 静态代理 | 动态代理 )
【Android 插件化】Hook 插件化框架 ( Hook 实现思路 | Hook 按钮点击事件 )
【Android 插件化】Hook 插件化框架 ( Hook Activity 启动过程 | 静态代理 )
【Android 插件化】Hook 插件化框架 ( 从 Hook 应用角度分析 Activity 启动流程 一 | Activity 进程相关源码 )
【Android 插件化】Hook 插件化框架 ( 从 Hook 应用角度分析 Activity 启动流程 二 | AMS 进程相关源码 | 主进程相关源码 )
【Android 插件化】Hook 插件化框架 ( hook 插件化原理 | 插件包管理 )
【Android 插件化】Hook 插件化框架 ( 通过反射获取 “插件包“ 中的 Element[] dexElements )
【Android 插件化】Hook 插件化框架 ( 通过反射获取 “宿主“ 应用中的 Element[] dexElements )
【Android 插件化】Hook 插件化框架 ( 合并 “插件包“ 与 “宿主“ 中的 Element[] dexElements | 设置合并后的 Element[] 数组 )
文章目录
- Android 插件化系列文章目录
- 前言
- 一、合并 “插件包“ 与 “宿主“ 中的 Element[] dexElements
-
- 1、获取 “插件包“ 与 “宿主“ 中的 Element[] dexElements 数组长度
- 2、获取数组元素类型 Element
- 3、计算合并后的 Element[] dexElements 数组长度
- 4、创建 Element[] 数组
- 5、拷贝 Element[] 数组元素
- 6、完整代码
- 二、设置 “宿主“ 中的 Element[] dexElements
- 三、完整代码示例
- 四、博客资源
前言
在 【Android 插件化】Hook 插件化框架 ( 通过反射获取 “插件包“ 中的 Element[] dexElements ) 博客中介绍了从 " 插件包 " APK 文件中获取 Element[] dexElements 流程 ;
在上一篇博客 【Android 插件化】Hook 插件化框架 ( 通过反射获取 “宿主“ 应用中的 Element[] dexElements ) 介绍了从 " 宿主 " 应用中获取 Element[] dexElements 流程 ;
本篇博客中开始将 " 插件包 " APK 中的 Element[] dexElements 和 “宿主“ 应用中的 Element[] dexElements 合并 ;
一、合并 “插件包“ 与 “宿主“ 中的 Element[] dexElements
将两个 Element[] dexElements 数组合并 , 合并完成后 , 设置到 PathClassLoader 中的 DexPathList pathList 成员的 Element[] dexElements 成员中 ;
1、获取 “插件包“ 与 “宿主“ 中的 Element[] dexElements 数组长度
调用 Java 提供的 Array API 中的 getLength 方法 , 获取数组长度 ;
代码语言:javascript复制// 获取 “宿主“ 中的 Element[] dexElements 数组长度
int host_dexElementsLength = Array.getLength(host_dexElementsObject);
// 获取 “插件包“ 中的 Element[] dexElements 数组长度
int plugin_dexElementsLength = Array.getLength(plugin_dexElementsObject);
2、获取数组元素类型 Element
获取 Element[] dexElements 数组中的 , 数组元素的 Element 类型 , 获取的是 Element.class ;
代码语言:javascript复制// 获取 Element[] dexElements 数组中的 , 数组元素的 Element 类型
// 获取的是 Element.class
Class<?> elementClazz = host_dexElementsObject.getClass().getComponentType();
3、计算合并后的 Element[] dexElements 数组长度
代码语言:javascript复制// 合并后的 Element[] dexElements 数组长度
int new_dexElementsLength = plugin_dexElementsLength host_dexElementsLength;
4、创建 Element[] 数组
创建 Element[] 数组 , elementClazz 是 Element.class 数组元素类型 ;
代码语言:javascript复制// 创建 Element[] 数组 , elementClazz 是 Element.class 数组元素类型
Object newElementsArray = Array.newInstance(elementClazz, new_dexElementsLength);
5、拷贝 Element[] 数组元素
为新的 Element[] newElementsArray 数组赋值 , 先将 “插件包“ 中的 Element[] dexElements 数组放入到新数组中 , 然后将 “宿主“ 中的 Element[] dexElements 数组放入到新数组中 ;
代码语言:javascript复制// 为新的 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));
}
}
6、完整代码
完整代码 :
代码语言:javascript复制 // 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));
}
}
二、设置 “宿主“ 中的 Element[] dexElements
将之前 合并 “插件包“ 与 “宿主“ 中的 Element[] dexElements 数组 , 设置到 宿主 PathClassLoader 中的 DexPathList pathList 成员的 Element[] dexElements 属性值中 ;
先获取 Element[] dexElements 字段 Field 对象 ;
代码语言:javascript复制Field elementsFiled = null;
try {
elementsFiled = host_pathListObject.getClass().getDeclaredField("dexElements");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
elementsFiled.setAccessible(true);
然后调用 Field 的 set 方法 , 设置该属性值 , 其中 host_pathListObject 是原来的属性值 , newElementsArray 是新的合并后的 Element[] dexElements 数组 ;
代码语言:javascript复制elementsFiled.set(host_pathListObject, newElementsArray);
完整代码 :
代码语言:javascript复制// 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();
}
三、完整代码示例
代码语言:javascript复制package kim.hsl.plugin;
import android.content.Context;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;
/**
* 使用 Hook 实现的插件使用入口
* 1. 加载插件包中的字节码
* 2. 直接通过 hook 技术, 钩住系统的 Activity 启动流程实现
* ① Activity 对象创建之前 , 要做很多初始化操作 , 先在 ActivityRecord 中加载 Activity 信息
* 如果修改了该信息 , 将要跳转的 Activity 信息修改为插件包中的 Activity
* 原来的 Activity 只用于占位 , 用于欺骗 Android 系统
* ② 使用 hook 技术 , 加载插件包 apk 中的 Activity
* ③ 实现跳转的 Activity ( 插件包中的 )
* 3. 解决 Resources 资源冲突问题
* ( 使用上述 hook 插件化 , 可以不用考虑 Activity 的声明周期问题 )
*
* 插件包中的 Activity 是通过正规流程 , 由 AMS 进行创建并加载的
* 但是该 Activity 并没有在 AndroidManifest.xml 清单文件中注册
* 这里需要一个已经在清单文件注册的 Activity 欺骗系统
*
* 插装式插件化 是通过代理 Activity , 将插件包加载的字节码 Class 作为一个普通的 Java 类
* 该普通的 Java 类有所有的 Activity 的业务逻辑
* 该 Activity 的声明周期 , 由代理 Activity 执行相关的生命周期方法
* hook 插件化 : hook 插件化直接钩住系统中 Activity 启动流程的某个点
* 使用插件包中的 Activity 替换占位的 Activity
*/
public class PluginManager {
/**
* 上下文
*/
private Context mBase;
/**
* 单例
*/
private static PluginManager instance;
public static PluginManager getInstance(Context context) {
if (instance == null) {
instance = new PluginManager(context);
}
return instance;
}
private PluginManager(Context context) {
this.mBase = context;
}
/**
* Application 启动后 , 调用该方法初始化插件化环境
* 加载插件包中的字节码
*/
private void init() {
// 加载 apk 文件
loadApk();
}
private void loadApk() {
// 插件包的绝对路径 , /data/data/< package name >/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();
}
}
}
四、博客资源
博客资源 :
- GitHub : https://github.com/han1202012/Plugin_Hook