说明
之前写过一篇水文《关于 《Android 使用 Rust 生成的动态库》的补充》
为啥又来补充了?没完没了是吧!主要原因是,最近对接公司的算法过程中发觉 Cpp 调 Rust 构建的动态库比直接调动态库麻烦得多,同时也没必要,我都用 Rust 啦,那当然是要一镜到底Rewrite it in Rust。
JNI crate 登场
什么年代啦还在用传统 Cpp,当然是要用 Modern Cpp Rust 来写 JNI 相关的代码啦! 具体代码在这里 limitliu.coding.net/public/java… 我已经改成全用 Rust 版本的啦。
查看一下里面的 rust
文件夹,里面有一个 ffi-example 的 Rust 项目,一个是之前的转 md5 的功能,另一个调用动态库的函数是把浮点数组里面的值乘以 2,顺便我也给出了出异常如何处理的方式,通过 throw 抛出,Java/Kotlin 端只要 try/catch 就可以。主要是利用 JNI 这个 crate 做一些事情,现在全程只要用 Rust 就能完成之前得上 Cpp 才能完成的工作,开发体验大幅上升。
use jni::{
objects::{JClass, JFloatArray, JString},
sys::{jfloatArray, jint, jstring},
JNIEnv,
};
use md5::compute;
#[no_mangle]
pub extern "system" fn Java_rust_ffi_Demo_md5(mut env: JNIEnv, _: JClass, buf: JString) -> jstring {
let input: String = match env.get_string(&buf) {
Ok(x) => x.into(),
Err(err) => {
env.exception_clear().expect("clear");
env
.throw_new("java/lang/Exception", format!("{err:?}"))
.expect("throw");
return std::ptr::null_mut();
}
};
let output = env
.new_string(format!("{:x}", compute(input)))
.expect("Failed to new string");
output.into_raw()
}
#[no_mangle]
pub extern "system" fn Java_rust_ffi_Demo_transform(
env: JNIEnv,
_: JClass,
array: JFloatArray,
) -> jfloatArray {
let len = env.get_array_length(&array).unwrap_or(0);
let mut v = vec![0f32; len as usize];
env
.get_float_array_region(array, 0, &mut v)
.expect("Failed to copy array to vec");
let v: Vec<f32> = v.into_iter().map(|x| x * 2.0).collect();
let output = env
.new_float_array(v.len() as jint)
.expect("Failed to create new float array.");
env
.set_float_array_region(&output, 0, &v)
.expect("Failed to set float array.");
output.into_raw()
}
然后是项目配置问题,删掉之前的 app/src/main/cpp 文件夹,Cpp 这玩意早该屠屠啦,删完在 java 同级目录建一个 jniLibs 文件夹,里面放对应 CPU 架构的文件夹(譬如 arm64-v8a),然后构建一下 ffi-example,完事就是直接把生成的动态库(*.so)文件放里头。还是同样的配方
cargo build --target aarch64-linux-android --release
总之来看看目录结构吧
.
├── LICENSE
├── README.md
├── app
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ ├── androidTest
│ │ └── java
│ │ └── wiki
│ │ └── mdzz
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── wiki
│ │ │ └── mdzz
│ │ ├── jniLibs
│ └── arm64-v8a
│ └── libffi_example.so
├── build.gradle
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── rust
│ └── ffi-example
│ ├── Cargo.lock
│ ├── Cargo.toml
│ ├── src
│ └── target
├── rustfmt.toml
└── settings.gradle
现在把之前跟 Cpp 相关的配置给删掉,主要是在 app/build.gradle 这边。
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
defaultConfig {
compileSdk 34
buildToolsVersion = '34.0.0'
applicationId "wiki.mdzz.ffidemo"
minSdk 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- externalNativeBuild {
- cmake {
- cppFlags ''
- }
-}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
-externalNativeBuild {
- cmake {
- path file('src/main/cpp/CMakeLists.txt')
- version '3.22.1'
- }
-}
buildFeatures {
viewBinding true
}
ndkVersion '26.3.11579264'
namespace 'wiki.mdzz.ffidemo'
}
dependencies {
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
Java/Kotlin 代码调整
因为我们在上面的代码里,写得函数名是 Java_rust_ffi_Demo_md5
/Java_rust_ffi_Demo_transform
,所以我们建一个文件 app/src/main/java/rust/ffi/Demo.kt
,里面的内容是
package rust.ffi
class Demo {
external fun md5(buf: String): String
external fun transform(array: FloatArray): FloatArray
companion object {
init {
System.loadLibrary("ffi_example")
}
}
}
在 Java/Kotlin 端的感知上跟之前 Cpp 的体验基本没区别,甚至由于 Rust 非常容易写出比较安全的代码,可能实际开发中,没 Cpp 那么多问题。