Java是一個需要運作在JVM上的程式語言,因此效能會比原生(native)程式還要來得差一些。不過對於一些比較需要花費硬體資源的運算(例如影像處理、聲音處理),我們還是可以透過Java提供的JNI(Java Native Interface)來連結並使用原生函式庫提供的功能來完成。Rust的函式庫也可以透過JNI來呼叫,在這篇文章中,會介紹如何把任意現有的Rust函式庫拿進Java程式語言中使用。
ShortCrypt
ShortCrypt是筆者開發的跨程式語言的資料加解密函式庫,其介紹文章的網址如下:
在本篇文章中,將會嘗試把用Rust程式語言實作的ShortCrypt函式庫加上JNI,使其能夠在Java程式語言中使用。
ShortCrypt JNI
建立專案目錄
要將現有的Rust函式庫加上JNI,不一定需要去修改原本Rust函式庫程式專案的程式碼。我們同樣可以先建立出一個新的Cargoc函式庫專案,名為short-crypt-jni
。
指令如下:
引用short-crypt
套件(crate)
編輯Cargo.toml
,在[dependencies]
區塊下加上:
引用jni
套件(crate)
為了能夠讓我們用Rust程式語言撰寫JNI程式,就需要引用jni
這個crate。
編輯Cargo.toml
,在[dependencies]
區塊下加上:
引用libc
套件(crate)
由於我們需要自己管理記憶體中的ShortCrypt
結構實體,因此會需要用到libc
提供的free
函數,來清除某個已經被分配空間的指標。
編輯Cargo.toml
,在[dependencies]
區塊下加上:
Java程式實作
是的,您沒看錯標題。在開始把ShortCrypt的Rust函式庫加上JNI前,我們應該要先規劃好Java程式中哪些地方需要使用到由Rust程式語言實作的方法或是函數,這些方法都需要先使用Java的native
修飾子來進行宣告,並且使用System
類別下的loadLibrary
方法來讀入指定名稱的動態函式庫。
我們在Cargo程式專案的根目錄下,新增一個java
目錄,在這個目錄中再新增一個Java原始碼檔案,檔名為ShortCrypt.java
。內容如下:
import java.io.IOException;
public class ShortCrypt {
// TODO -----Static Initializer-----
static {
System.loadLibrary("short_crypt_jni");
}
// TODO -----Static Classes-----
public static class Cipher {
public final byte base;
public final byte[] body;
public Cipher(final byte base, final byte[] body) {
this.base = base;
this.body = body;
}
}
public static class DecryptException extends IOException {
public DecryptException(final String message) {
super(message);
}
public DecryptException(final String message, final Throwable cause) {
super(message, cause);
}
}
// TODO -----Static Native Methods-----
private static native long sc_new(final String key);
// TODO -----Object Constants-----
private final long sc;
// TODO -----Constructors-----
public ShortCrypt(final String key) {
this.sc = sc_new(key);
}
// TODO -----Object Native Methods-----
public native Cipher encryptString(final String plaintext);
public native Cipher encryptBinary(final byte[] plaintext);
public native byte[] decryptToBinary(final Cipher cipher) throws DecryptException;
public native String decryptToString(final Cipher cipher) throws DecryptException;
public native String encryptStringToURLComponent(final String data);
public native String encryptBinaryToURLComponent(final byte[] data);
public native byte[] decryptURLComponentToBinary(final String urlComponent);
public native String decryptURLComponentToString(final String urlComponent);
public native String encryptStringToQRCodeAlphanumeric(final String data);
public native String encryptBinaryToQRCodeAlphanumeric(final byte[] data);
public native byte[] decryptQRCodeAlphanumericToBinary(final String qrCodeAlphanumeric);
public native String decryptQRCodeAlphanumericToString(final String qrCodeAlphanumeric);
public native void close();
}
以上程式中,比較需要特別注意的部份有以下幾點:
- 如果是使用Unix-like系統
System.loadLibrary("short_crypt_jni")
會去連結的函式庫檔名為libshort_crypt_jni.so
;如果是使用Windows系統,它會去連結的函式庫檔名為libshort_crypt_jni.dll
;如果是使用macOS系統,它會去連結的函式庫檔名為libshort_crypt_jni.dylib
。 - 在Rust程式中可以直接存取到Java程式的類別,因此要建立
Cipher
類別的物件實體並回傳是可行的。 - 在Rust程式中可以拋出Java的例外。
- Java的
long
型別可以當作指標。 - 由於Rust的
ShortCrypt
結構實體不是在Java程式中建立出來的,因此它並不會被垃圾回收,我們必須提供額外的close
方法來清除掉不使用的ShortCrypt
結構實體。
使用javac
建立C/C++的標頭檔(*.h
)
javac
是Java程式語言的編譯工具,它也可以用來把Java程式中所有用到native
關鍵字修飾的方法,轉成C/C++的標頭檔(.h
檔)。
指令的用法如下:
例如要編譯java/ShortCrypt.java
並且將標頭檔產生至java
目錄中的話,指令如下:
產生出來的標頭檔的路徑為java/ShortCrypt.h
,內容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class ShortCrypt */
#ifndef _Included_ShortCrypt
#define _Included_ShortCrypt
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: ShortCrypt
* Method: sc_new
* Signature: (Ljava/lang/String;)J
*/
JNIEXPORT jlong JNICALL Java_ShortCrypt_sc_1new
(JNIEnv *, jclass, jstring);
/*
* Class: ShortCrypt
* Method: encryptString
* Signature: (Ljava/lang/String;)LShortCrypt/Cipher;
*/
JNIEXPORT jobject JNICALL Java_ShortCrypt_encryptString
(JNIEnv *, jobject, jstring);
/*
* Class: ShortCrypt
* Method: encryptBinary
* Signature: ([B)LShortCrypt/Cipher;
*/
JNIEXPORT jobject JNICALL Java_ShortCrypt_encryptBinary
(JNIEnv *, jobject, jbyteArray);
/*
* Class: ShortCrypt
* Method: decryptToBinary
* Signature: (LShortCrypt/Cipher;)[B
*/
JNIEXPORT jbyteArray JNICALL Java_ShortCrypt_decryptToBinary
(JNIEnv *, jobject, jobject);
/*
* Class: ShortCrypt
* Method: decryptToString
* Signature: (LShortCrypt/Cipher;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_ShortCrypt_decryptToString
(JNIEnv *, jobject, jobject);
/*
* Class: ShortCrypt
* Method: encryptStringToURLComponent
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_ShortCrypt_encryptStringToURLComponent
(JNIEnv *, jobject, jstring);
/*
* Class: ShortCrypt
* Method: encryptBinaryToURLComponent
* Signature: ([B)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_ShortCrypt_encryptBinaryToURLComponent
(JNIEnv *, jobject, jbyteArray);
/*
* Class: ShortCrypt
* Method: decryptURLComponentToBinary
* Signature: (Ljava/lang/String;)[B
*/
JNIEXPORT jbyteArray JNICALL Java_ShortCrypt_decryptURLComponentToBinary
(JNIEnv *, jobject, jstring);
/*
* Class: ShortCrypt
* Method: decryptURLComponentToString
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_ShortCrypt_decryptURLComponentToString
(JNIEnv *, jobject, jstring);
/*
* Class: ShortCrypt
* Method: encryptStringToQRCodeAlphanumeric
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_ShortCrypt_encryptStringToQRCodeAlphanumeric
(JNIEnv *, jobject, jstring);
/*
* Class: ShortCrypt
* Method: encryptBinaryToQRCodeAlphanumeric
* Signature: ([B)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_ShortCrypt_encryptBinaryToQRCodeAlphanumeric
(JNIEnv *, jobject, jbyteArray);
/*
* Class: ShortCrypt
* Method: decryptQRCodeAlphanumericToBinary
* Signature: (Ljava/lang/String;)[B
*/
JNIEXPORT jbyteArray JNICALL Java_ShortCrypt_decryptQRCodeAlphanumericToBinary
(JNIEnv *, jobject, jstring);
/*
* Class: ShortCrypt
* Method: decryptQRCodeAlphanumericToString
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_ShortCrypt_decryptQRCodeAlphanumericToString
(JNIEnv *, jobject, jstring);
/*
* Class: ShortCrypt
* Method: close
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_ShortCrypt_close
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
我們等等要參考這個標頭檔的內容來撰寫Rust程式。
Rust程式實作
在src/lib.rs
檔案中撰寫以下程式:
use std::ptr;
use jni::JNIEnv;
use jni::objects::{JClass, JObject, JString, JValue};
use jni::sys::{jlong, jobject, jbyteArray, jstring};
use short_crypt::{ShortCrypt, Cipher};
#[inline]
fn throw_decrypt_exception<S: AsRef<str>>(env: &JNIEnv, msg: S) {
let decrypt_exception_class = env.find_class("ShortCrypt$DecryptException").expect("Should get the DecryptException class.");
env.throw_new(decrypt_exception_class, msg).expect("Should throw an DecryptException.");
}
#[inline]
fn throw_exception<S: AsRef<str>>(env: &JNIEnv, msg: S) {
let runtime_exception_class = env.find_class("java/lang/RuntimeException").expect("Should get the RuntimeException class.");
env.throw_new(runtime_exception_class, msg).expect("Should throw an RuntimeException.");
}
#[inline]
fn ensure_ptr_not_null_or_throw_exception<S: AsRef<str>>(env: &JNIEnv, ptr: jlong, msg: S) -> bool {
if ptr == 0 {
throw_exception(env, msg);
false
} else {
true
}
}
#[inline]
fn try_to_get_short_crypt_or_throw_exception<'a>(env: &JNIEnv, object: JObject) -> Option<&'a ShortCrypt> {
let sc_ptr = env.get_field(object, "sc", "J").expect("Should get a long value.").j().expect("Should get a long value.");
if ensure_ptr_not_null_or_throw_exception(env, sc_ptr, "This StringCrypt object is closed.") {
Some(unsafe {
&*(sc_ptr as *const ShortCrypt)
})
} else {
None
}
}
#[inline]
fn get_cipher_or_throw_exception(env: &JNIEnv, cipher: JObject) -> Option<Cipher> {
if cipher.is_null() {
throw_exception(&env, "The cipher is null.");
None
} else {
let base = env.get_field(cipher.clone(), "base", "B").expect("Should get a byte.").b().expect("Should get a byte.");
let body = env.get_field(cipher, "body", "[B").expect("Should get a byte array.").l().expect("Should get a byte array.");
let body = env.convert_byte_array(body.into_inner()).expect("Should get a byte array.");
Some((base as u8, body))
}
}
#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_sc_1new(env: JNIEnv, _class: JClass, key: JString) -> jlong {
let key: String = env.get_string(key).expect("Should get a string.").into();
let sc = ShortCrypt::new(key);
Box::into_raw(Box::new(sc)) as jlong
}
#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_encryptString(env: JNIEnv, object: JObject, plaintext: JString) -> jobject {
match try_to_get_short_crypt_or_throw_exception(&env, object) {
Some(sc) => {
let plaintext: String = env.get_string(plaintext).expect("Should get a string.").into();
let sc_cipher = sc.encrypt(&plaintext);
let cipher_class = env.find_class("ShortCrypt$Cipher").expect("Should get the Cipher class.");
let body = env.byte_array_from_slice(sc_cipher.1.as_slice()).unwrap();
let cipher = env.new_object(cipher_class, "(B[B)V", &[JValue::from(sc_cipher.0), JValue::from(JObject::from(body))]).expect("Should create a Cipher instance.");
cipher.into_inner()
}
None => {
ptr::null_mut()
}
}
}
#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_encryptBinary(env: JNIEnv, object: JObject, plaintext: jbyteArray) -> jobject {
match try_to_get_short_crypt_or_throw_exception(&env, object) {
Some(sc) => {
let plaintext = env.convert_byte_array(plaintext).unwrap();
let sc_cipher = sc.encrypt(&plaintext);
let cipher_class = env.find_class("ShortCrypt$Cipher").expect("Should get the Cipher class.");
let body = env.byte_array_from_slice(sc_cipher.1.as_slice()).unwrap();
let cipher = env.new_object(cipher_class, "(B[B)V", &[JValue::from(sc_cipher.0), JValue::from(JObject::from(body))]).expect("Should create a Cipher instance.");
cipher.into_inner()
}
None => {
ptr::null_mut()
}
}
}
#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_decryptToBinary(env: JNIEnv, object: JObject, cipher: JObject) -> jbyteArray {
match try_to_get_short_crypt_or_throw_exception(&env, object) {
Some(sc) => {
match get_cipher_or_throw_exception(&env, cipher) {
Some(cipher) => match sc.decrypt(&cipher) {
Ok(data) => {
env.byte_array_from_slice(data.as_slice()).unwrap()
}
Err(message) => {
throw_decrypt_exception(&env, message);
ptr::null_mut()
}
}
None => ptr::null_mut()
}
}
None => {
ptr::null_mut()
}
}
}
#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_decryptToString(env: JNIEnv, object: JObject, cipher: JObject) -> jstring {
match try_to_get_short_crypt_or_throw_exception(&env, object) {
Some(sc) => {
match get_cipher_or_throw_exception(&env, cipher) {
Some(cipher) => match sc.decrypt(&cipher) {
Ok(data) => {
env.new_string(String::from_utf8_lossy(data.as_slice())).unwrap().into_inner()
}
Err(message) => {
throw_decrypt_exception(&env, message);
ptr::null_mut()
}
}
None => ptr::null_mut()
}
}
None => {
ptr::null_mut()
}
}
}
#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_encryptStringToURLComponent(env: JNIEnv, object: JObject, data: JString) -> jstring {
match try_to_get_short_crypt_or_throw_exception(&env, object) {
Some(sc) => {
let data: String = env.get_string(data).expect("Should get a string.").into();
let result = sc.encrypt_to_url_component(&data);
env.new_string(result).unwrap().into_inner()
}
None => {
ptr::null_mut()
}
}
}
#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_encryptBinaryToURLComponent(env: JNIEnv, object: JObject, data: jbyteArray) -> jstring {
match try_to_get_short_crypt_or_throw_exception(&env, object) {
Some(sc) => {
let data = env.convert_byte_array(data).unwrap();
let result = sc.encrypt_to_url_component(&data);
env.new_string(result).unwrap().into_inner()
}
None => {
ptr::null_mut()
}
}
}
#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_decryptURLComponentToBinary(env: JNIEnv, object: JObject, url_component: JString) -> jbyteArray {
match try_to_get_short_crypt_or_throw_exception(&env, object) {
Some(sc) => {
let url_component: String = env.get_string(url_component).expect("Should get a string.").into();
match sc.decrypt_url_component(&url_component) {
Ok(data) => {
env.byte_array_from_slice(data.as_slice()).unwrap()
}
Err(message) => {
throw_decrypt_exception(&env, message);
ptr::null_mut()
}
}
}
None => {
ptr::null_mut()
}
}
}
#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_decryptURLComponentToString(env: JNIEnv, object: JObject, url_component: JString) -> jstring {
match try_to_get_short_crypt_or_throw_exception(&env, object) {
Some(sc) => {
let url_component: String = env.get_string(url_component).expect("Should get a string.").into();
match sc.decrypt_url_component(&url_component) {
Ok(data) => {
env.new_string(String::from_utf8_lossy(data.as_slice())).unwrap().into_inner()
}
Err(message) => {
throw_decrypt_exception(&env, message);
ptr::null_mut()
}
}
}
None => {
ptr::null_mut()
}
}
}
#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_encryptStringToQRCodeAlphanumeric(env: JNIEnv, object: JObject, data: JString) -> jstring {
match try_to_get_short_crypt_or_throw_exception(&env, object) {
Some(sc) => {
let data: String = env.get_string(data).expect("Should get a string.").into();
let result = sc.encrypt_to_qr_code_alphanumeric(&data);
env.new_string(result).unwrap().into_inner()
}
None => {
ptr::null_mut()
}
}
}
#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_encryptBinaryToQRCodeAlphanumeric(env: JNIEnv, object: JObject, data: jbyteArray) -> jstring {
match try_to_get_short_crypt_or_throw_exception(&env, object) {
Some(sc) => {
let data = env.convert_byte_array(data).unwrap();
let result = sc.encrypt_to_qr_code_alphanumeric(&data);
env.new_string(result).unwrap().into_inner()
}
None => {
ptr::null_mut()
}
}
}
#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_decryptQRCodeAlphanumericToBinary(env: JNIEnv, object: JObject, qr_code_alphanumeric: JString) -> jbyteArray {
match try_to_get_short_crypt_or_throw_exception(&env, object) {
Some(sc) => {
let qr_code_alphanumeric: String = env.get_string(qr_code_alphanumeric).expect("Should get a string.").into();
match sc.decrypt_qr_code_alphanumeric(&qr_code_alphanumeric) {
Ok(data) => {
env.byte_array_from_slice(data.as_slice()).unwrap()
}
Err(message) => {
throw_decrypt_exception(&env, message);
ptr::null_mut()
}
}
}
None => {
ptr::null_mut()
}
}
}
#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_decryptQRCodeAlphanumericToString(env: JNIEnv, object: JObject, qr_code_alphanumeric: JString) -> jstring {
match try_to_get_short_crypt_or_throw_exception(&env, object) {
Some(sc) => {
let qr_code_alphanumeric: String = env.get_string(qr_code_alphanumeric).expect("Should get a string.").into();
match sc.decrypt_qr_code_alphanumeric(&qr_code_alphanumeric) {
Ok(data) => {
env.new_string(String::from_utf8_lossy(data.as_slice())).unwrap().into_inner()
}
Err(message) => {
throw_decrypt_exception(&env, message);
ptr::null_mut()
}
}
}
None => {
ptr::null_mut()
}
}
}
#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_close(env: JNIEnv, object: JObject) {
let sc_ptr = env.get_field(object.clone(), "sc", "J").expect("Should get a long value.").j().expect("Should get a long value.");
if ensure_ptr_not_null_or_throw_exception(&env, sc_ptr, "This StringCrypt object has been closed.") {
unsafe {
libc::free(sc_ptr as *mut libc::c_void);
}
env.set_field(object, "sc", "J", JValue::from(0i64)).expect("Should set a long value.");
}
}
以上程式中,比較需要特別注意的部份有以下幾點:
- 由於Rust會自動在擁有者被消滅時,去
drop
其所擁有的實體。所以我們在堆疊中產生出來的ShortCrypt
結構實體,要先利用Box
結構體移到堆積上,接著再用Box
結構體的into_raw
關聯函數將其轉成指標(long
型態),存在Java的物件欄位中。 - 為了避免記憶體洩漏(memory leak),所以要提供一個
close
函數,並在這個函數中透過libc
提供的free
關聯函數來釋放儲存在堆積中的ShortCrypt
結構實體。 - JNI函數用到的
#[no_mangle]
屬性和pub extern
關鍵字,是為了要保留函數名稱,確保Java程式可以呼叫得到。 jni
這個crate中,有許多sys::jstring
和objects::JString
、sys::jobject
和objects::JObject
等名稱類似但大小寫不同的型別,它們是不同的型別,有用大寫的表示jni
這個crate有為它提供專門的結構來處理,只有用小寫的表示它只有做原生JNI型別的對應。不過不管有沒有大寫,它們都可以被用在JNI函數的參數或是回傳值型別上。建議回傳值型別都用只有小寫的那些,免得還要處理生命周期的問題。- Rust標準函式庫的
ptr
模組提供的null_mut
函數,可以讓JNI函數回傳null
。(只不過JNI函數的回傳值型別要用小寫的那些) - 在Rust程式中拋出Java的例外,並不會立刻中斷之後程式的執行,所以要將已進入拋出例外狀態的流程區分開來。
關於Java的簽名(signature)用字串表示的方式,可以參考以下表格:
種類 | 簽名字串 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
類別 | L類別路徑; |
陣列 | [型別名稱 |
方法 | (參數型別簽名)回傳值型別簽名 |
例如這個方法:
long f (int n, String s, int[] arr);
其簽名的字串為:
在JNI中,要使用欄位或是方法時幾乎都需要先透過簽名字串來尋找,所以這個格式一定要了解,才有辦法開發JNI程式。
編譯函式庫
由於Rust的函式庫預設的格式為.rlib
,並非C/C++使用的.a
、.so
、.dll
、.dylib
等。為了要編譯出Java語言能夠載入的函式庫,必須要去修改Cargo的設定檔,在[lib]
區塊中,加上crate-type
項目,將其的值設定為["cdylib"]
。如下:
[lib]
crate-type = ["cdylib"]
如果不設定crate-type
,Cargo會根據src
目錄中有無lib.rs
檔案來決定要不要加入rlib
,也就是crate-type = ["rlib"]
。
在crate-type
項目加入cdylib
,可以使Cargo在編譯程式專案時,將能用在C/C++程式的動態函式庫也跟著編譯出來。
將設定檔修改好後,執行cargo build --release
指令,就可以在target/release
目錄中找到動態函式庫檔案啦!目標平台如果是Linux的話,就會看到libshort_crypt_jni.so
檔案。
連結函式庫
在使用java
指令執行Java程式時,如果要載入動態函式庫,就要在java
指令的後面緊接-Djava.library.path=/path/to/library-folder
參數。
我們可以在Cargo程式專案根目錄的java
目錄中再新增一個ShortCryptExample.java
檔案來試試我們的ShortCrypt.java
能不能正常被使用。ShortCryptExample.java
檔案內容如下:
public class ShortCryptExample {
public static void main(final String[] args) throws Exception {
final ShortCrypt sc = new ShortCrypt("magickey");
final String urlComponent = sc.encryptStringToURLComponent("articles");
System.out.println(urlComponent);
System.out.println(sc.decryptURLComponentToString(urlComponent));
sc.close();
}
}
接著將終端機的工作目錄移動到java
目錄中,執行以下指令來編譯程式:
然後用以下指令來執行ShortCryptExample.class
:
這邊要注意的是,-D
參數的位置一定要在檔案輸入的參數之前,否則會沒有效果。
ShortCryptExample.class
的執行結果如下:
articles
在Android上使用JNI
交叉編譯
我們可以藉由交叉編譯,來編譯出能在Android系統上載入的動態函式庫。有關於交叉編譯Rust程式的方式,可以參考這篇文章:
連結函式庫
Android上的Java程式不像一般Windows、Linux、macOS等作業系統上的Java程式一樣要使用java
指令工具來執行,所以也就不用弄什麼-Djava.library.path
參數。我們只要將針對不同CPU架構編譯出來的動態函式庫(.so
檔),分類放在Android Studio程式專案內的app/src/main/jniLibs
目錄下的子目錄當中就好了。
子目錄都是用Android ABI來命名,列表如下:
目錄名稱 | 支援的指令集 | 對應的Rust編譯目標(cross) |
---|---|---|
armeabi-v7a | armeabi Thumb-2 VFPv3-D16 |
armv7-linux-androideabi |
arm64-v8a | AArch64 |
aarch64-linux-android |
x86 | x86 (IA-32) MMX SSE/2/3 SSSE3 |
i686-linux-android |
x86_64 | x86-64 MMX SSE/2/3 SSSE3 SSE4.1, 4.2 POPCNT |
x86_64-linux-android |