• 作者:老汪软件技巧
  • 发表时间:2024-10-03 07:00
  • 浏览量:

引言

前面几篇文章各位应该对 jni-rs 的了解比较清晰了。实际上在开发中,除去Rust所有权和生命周期的折磨,大致的用法与C/C++写起来基本一致,而Rust的优势在我看来就是它的各类依赖都统一的放在 crates.io ,简单来说就是比较好找吧。

开发环境

操作系统:Windows 11

编码软件:Visual Studio Code 1.93.1 | Android Studio Ladybug | 2024.2.1 Beta 1

正文

本文将通过 jni-rs 与 Java 的深度结合,来在Rust层实现一个简单的Android热修复案例。

思路整理

首先呢,对于Android的热修复,通常都是通过 DexClassloader 去加载远程下放的 dex 文件,那当然了 InMemoryDexClassLoader 也是 api 28 之后常用的方式了。

本文就基于 DexClassloader的思路来实现一个简单的热修复so库。

熟悉热修复的各位都知道,当我们拿到热修复的 dex文件后,会将它的路径放到 DexPathList 中 Element[] 数组的首位。

然后根据 Classloader 双亲委托的加载机制,当某个类被加载后,就会直接返回该类。而不会继续向后查找,这也就是为何要将热修复 dex 放在 Element[] 数组首位的原因。

创建Rust项目

还是老样子,我们创建名为 example_4 的Rust lib项目,然后通过 vscode 打开它。

接着,按照前几篇文章的步骤,改造这个项目。

开始创建初始化方法。

如上图,我们创建了一个 Java_com_hotfix_HotFix_init 的导出方法,它接收了两个参数 context 和 dex_path 分别对应 Android 中的 Context 上下文和自定义的热修复文件路径。

然后,通过第9行的红框代码,我们创建了一个mod名为 dex_installer 顾名思义就是对dex进行安装。

接着,第20行代码,我们拿到dex_path变量的Rust String类型的表示,并再次通过对dex_path的定义进行了变量遮掩。

最终,我们调用了dex_installer::install_dex自定的mod和自定义 install_dex 函数对dex进行热修复的安装。

实现install_dex函数

我们切换到定义好的dex_installmod下,进行函数逻辑的实现。

先写一段最基本的逻辑。

到这之后,我们创建好了新的 DexClassloadr 并且将 dex_path 的路径放了进去。

接下来就需要我们拿到 新创建的dex_class_loader 和 原先Context的class_loader 的 DexPathList。

因为两个 classloader 都需要拿到 DexPathList 这里就写一个函数 get_path_list。

然后就是拿到 DexPathList 的 Element[] 数组,再次封装成一个函数 get_dex_elements。

我们再抽一下共用逻辑,得到最终的实现。

然后,补齐我们前面的 //to do,分别调用 get_path_list 和 get_dex_elements 拿到两个 classloader 的 DexPathList 和 element[] 数组,如下图。

接下来呢,就需要对两个数组进行合并,并且对原来的 DexPathList 设置为合并后的 element[] 新值。

因此,我们还需要一个函数 combine_element_array 来对两个 element[] 数组进行合并。

最后,就可以补齐剩下的//to do了。

完整的 install_dex 函数实现,截图如下。

_热修复补丁_热修复原理android

至此,一个简单的热修复实现就完成了。

编译为so库

最后,执行命令行代码,进行so的编译,得到 jniLibs 文件夹。

cargo ndk -t armeabi-v7a -t arm64-v8a -o ./jniLibs build --release

创建Android项目

接下来,我们创建一个Android项目并导入jniLibs测试一下。

创建hot子module

我们首先创建一个hot子module。

通过 Build->Rebuild Project 进行项目构建得到aar。

然后,通过压缩工具将这个arr文件中的 classes.jar 解压出来,我这里直接放到桌面上了。

最后通过 d8 命令行工具,将这个 jar 转为 dex 留作备用。

d8 --output=output_dex.jar input.jar

这时候再通过压缩工具打开classes_dex.jar就能得到dex文件了。

做完这一切后,我们的热修复dex总算是创建好了。

接下来,开始使用它。

创建好 jni 接口类和方法后,我们需要为 App 创建一个自定义的 Application,并复写它的 attachBaseContext 方法。

我们在app的外部私有目录下创建一个hotfix子目录,内部放入hotfix.dex(也就是刚才d8出来的dex文件,将它改名为hotfix.dex即可)。

这个就是需要被热修复的dex文件了。

接下来,最重要的一步来了。

只读引入hot子module

打开app的build.gradle.kts,将刚才创建的 hot module 进行只读引入。

然后在 MainActivity 中使用它。

最后,Run一下。

热修复

结果自然就可想而知了,肯定是崩溃的下场,因为这个类无法被找到。

那么我们将刚才的dex改名为 hotfix.dex 后推入设备上的指定目录中,让它能够被热修复加载。

之后,再次打开app。

就能够看见修复后的调用结果了。

文章至此,相信各位将这个例子敲下之后(加粗),就已经能够尝试在自己的项目中通过Rust来编写NDK的实现了。

那么,本篇完。