引言
前面几篇文章各位应该对 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_install
mod下,进行函数逻辑的实现。
先写一段最基本的逻辑。
到这之后,我们创建好了新的 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
函数实现,截图如下。
至此,一个简单的热修复实现就完成了。
编译为so库
最后,执行命令行代码,进行so的编译,得到 jniLibs
文件夹。
bash
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 留作备用。
bash
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的实现了。
那么,本篇完。