在 Android 開發中使用 Rust
首先我們需要為不同 CPU 架構生成獨立的 NDK 工具鏈,這些工具鏈將會被 Rust 編譯器作為後端使用。以 Windows 系統為例,假定你已經安裝好了 Android studio ,且 Android SDK 和 NDK 按照默認路徑存放。
你還需要安裝好 python 以執行生成工具鏈的 py 腳本。
創建一個文件夾用於存放 NDK 工具鏈,打開 powershell 切換到該路徑下,執行下面的命令。注意命令中的 api 參數對應你 Android 項目的 API 版本,同時使用的 NDK 要包含對該 API 版本的支持:
$NDK_HOME="$env:LOCALAPPDATAAndroidSdk
dk-bundle"
mkdir NDK
python "$NDK_HOMEuild oolsmake_standalone_toolchain.py" --api 28 --arch arm64 --install-dir NDK/arm64
python "$NDK_HOMEuild oolsmake_standalone_toolchain.py" --api 28 --arch arm --install-dir NDK/arm
python "$NDK_HOMEuild oolsmake_standalone_toolchain.py" --api 28 --arch x86 --install-dir NDK/x86
暫停部分安全軟體比如 Windows defender 的實時保護功能以避免掃描帶來的生成速度緩慢問題。

之後在 cargo 配置文件中配置工具鏈,它的位置在 C:Users用戶名.cargo 下,如果沒有就新建一個 config 文件,配置內容如下:
[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
replace-with = ustc
[source.ustc]
registry = "https://mirrors.ustc.edu.cn/crates.io-index/"
[target.aarch64-linux-android]
ar = "D:\Documents\temp\rust_jni_test\NDK\arm64\bin\aarch64-linux-android-ar.exe"
linker = "D:\Documents\temp\rust_jni_test\NDK\arm64\bin\aarch64-linux-android-clang.cmd"
[target.armv7-linux-androideabi]
ar = "D:\Documents\temp\rust_jni_test\NDK\arm\bin\arm-linux-androideabi-ar.exe"
linker = "D:\Documents\temp\rust_jni_test\NDK\arm\bin\arm-linux-androideabi-clang.cmd"
[target.i686-linux-android]
ar = "D:\Documents\temp\rust_jni_test\NDK\x86\bin\i686-linux-android-ar.exe"
linker = "D:\Documents\temp\rust_jni_test\NDK\x86\bin\i686-linux-android-clang.cmd"
注意按上面生成工具鏈的具體路徑來填入。
最後執行如下命令:
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android
安裝組件以支持各 CPU 架構的的交叉編譯。
到這裡, Rust 就可以編譯生成適合 Android 的 .so 文件了。我們使用 cargo new --lib 創建一個 Rust 項目,在 Cargo.toml 添加 jni 和 android_logger 這兩個 crate 以支持使用 JNI 和 Android 的 log 功能,同時將 [lib] 下的 crate_type 設置為 dylib:
[target.cfg(target_os="android").dependencies]
jni = { version = "0.10", default-features = false }
error-chain = { version = "0.12", default-features = false }
log = "0.4"
android_logger = "0.5"
[lib]
crate_type = ["dylib"]
關於各種生成格式,見 The Rust Reference 。
安卓的部分不再贅述,假定已經創建了這麼兩個 native 方法:
public native void init();
public native String stringFromJNI();
在 Android studio 里可自動生成它們的方法簽名:
extern "C"
JNIEXPORT void JNICALL
Java_com_github_xxx_yyy_MainActivity_init(JNIEnv *env, jobject instance) {
// TODO
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_github_xxx_yyy_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
那麼這兩個方法在 Rust 中的對應代碼是:
pub extern "system" fn Java_com_github_xxx_yyy_MainActivity_init() {
// TODO
}
pub extern "system" fn Java_com_github_xxx_yyy_MainActivity_stringFromJNI(env: JNIEnv,
_class: JClass)
-> jstring {
let hello = env.new_string("Hello from Rust")
.expect("Couldnt create java string!");
hello.into_inner()
}
移植起來還是很方便的。
完整部分可參考 jni 的示例代碼:
jni - Rust演示一下如何在 Rust 中調用 Android / Java 方法:
#[allow(non_snake_case)]
fn get_package_signature(env: &JNIEnv, context: JClass) -> jint {
let packageManager = env.call_method(context.into(), "getPackageManager", "()Landroid/content/pm/PackageManager;", &[]).unwrap();
let packageName = env.call_method(context.into(), "getPackageName", "()Ljava/lang/String;", &[]).unwrap();
let packageInfo = env.call_method(packageManager.l().unwrap(), "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;", &[packageName, JValue::from(GET_SIGNATURES)]).unwrap();
let signatureArray = env.get_field(packageInfo.l().unwrap(), "signatures", "[Landroid/content/pm/Signature;").unwrap();
let signature = env.get_object_array_element(signatureArray.l().unwrap().into_inner(), 0).unwrap();
let hash_code = env.call_method(signature, "hashCode", "()I", &[]).unwrap();
hash_code.i().unwrap()
}
更多的用法可以參考 jni-rs/jni-rs 中的測試用例。
編譯命令為:
cargo build --target aarch64-linux-android --release
cargo build --target armv7-linux-androideabi --release
cargo build --target i686-linux-android --release
將生成的 so 複製到 jniLibs.srcDirs 下:

注意 aarch64-linux-android 的目標文件要複製到的文件夾是 arm64-v8a ,不要寫成 arm64 了。
其實用 Rust 替代 C/C++ 進行 Android 開發並沒有什麼特別大的吸引力,生成的 so 比較大(可以優化,並且經過 gradle strip 之後會小很多),門檻還很高。這裡只是演示一下 Rust 具備這種能力。
另:看了一下生成的 so 反編譯之後的代碼,比 C/C++ 反編譯的結果難讀一些,不像很多 C/C++ 反編譯的代碼看一眼就知道這部分是做什麼的,這可能算一個優勢吧。
參考:Building and Deploying a Rust library on Android
推薦閱讀:
TAG:Android | Rust(編程語言) | JavaNativeInterface |
