- 作者:老汪软件技巧
- 发表时间: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 函数实现,截图如下。
至此,一个简单的热修复实现就完成了。
编译为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的实现了。
那么,本篇完。