前言
Rust
的JNI
流程以及方法实际上和我们常见的C++
JNI
是十分相似的.我们本章将使用Rust
实现常见的JNI
调用功能.关于更多的用法,可参考官方示例,github地址为https://github.com/jni-rs/jni-rs/blob/master/example/mylib/src/lib.rs
.
基本交互功能实现
1. Java传String,返回byte数组
rust
代码如下:
#[no_mangle]
pub extern "system" fn Java_com_jni_rust_RustNative_getByteFromString(env: JNIEnv, _: JClass, java_str: JString) -> jbyteArray {
let input: String = env.get_string(java_str).unwrap().into();
let input_array = input.as_bytes();
let output = env.byte_array_from_slice(input_array).unwrap();
output
}
jni
很好的提供了JString
转rust
String
方法以及&[u8]
转jbyteArray
方法.
我们在Android
里面测试一下:
Android
层声明一个native
方法
public static native byte[] getByteFromString(String java_str);
- 调用
byte[] resultArray = RustNative.getByteFromString("this is a java str");
Log.d(TAG, Arrays.toString(resultArray));
Log.d(TAG, new String(resultArray));
将rust
生成的so
文件放到Android
工程的libs/arm64-v8a
下,测试一下结果.
完整代码将放在文末.
2. 回调(同步)
- 首先在
Android
层新建一个interface
,本文为RustListener
public interface RustListener {
void onStringCallback(String msg);
void onVoidCallback();
}
interface
定义了两个回调函数onStringCallback
和onVoidCallback
,其中onStringCallback
传回一个String
,onVoidCallback
则不回传任何参数.
Android
层声明的native
方法
public static native void syncCallback(RustListener listener);
Android
层调用
RustNative.syncCallback(new RustListener() {
@Override
public void onStringCallback(String msg) {
Log.d(TAG, "sync callback: " + msg);
}
@Override
public void onVoidCallback() {
Log.d(TAG, "sync callback");
}
});
rust
代码
#[no_mangle]
pub extern "system" fn Java_com_jni_rust_RustNative_syncCallback(env: JNIEnv, _: JClass, callback: JObject) {
let hello = "hello syncCallback";
let jni_string_hello = JNIString::from(hello);
let j_string_hello = env.new_string(jni_string_hello).unwrap();
let j_value_hello = JValue::from(j_string_hello);
env.call_method(callback, "onStringCallback", "(Ljava/lang/String;)V", &[j_value_hello]).unwrap();
env.call_method(callback, "onVoidCallback", "()V", &[]).unwrap();
}
- 其中,
callback: JObject
即是我们从Android
层传过来的RustListener
对象. - 在这段,我们又了解到了
rust
String
转JString
的方法let j_string_hello = env.new_string(jni_string_hello).unwrap()
.结合上一小节的JString
转rust
String
.我们已经完整学习了rust
与Android
直接String
类型互相转化的方式. - 再看看
rust
调用Android
方法的函数env.call_method(obj,name,sig,args)
.
obj:java
对象.
name:Android
方法名.
sig: 方法签名.
args: 是一个&[JValue]
,它就是我们的参数列表了.如果不含参数,则放置一个空的数组即可&[]
. - 还有一个
rust
调用Android
方法的函数call_method_unchecked
,整体流程和我们C++
方式调用Android
方法的流程非常相似,大家有兴趣可以自行进一步了解,我们也将在后面第4小节的例子中简单使用一下.
3. 回调(异步)
- 依然使用上一节的
RustListener
,声明native
方法
public static native void asyncCallback(RustListener listener);
Android
端调用
RustNative.asyncCallback(new RustListener() {
@Override
public void onStringCallback(String msg) {
Log.d(TAG, "async callback: " + msg);
}
@Override
public void onVoidCallback() {
Log.d(TAG, "async callback");
}
});
rust
代码
#[no_mangle]
pub extern "system" fn Java_com_jni_rust_RustNative_asyncCallback(env: JNIEnv, _: JClass, callback: JObject) {
let jvm = env.get_java_vm().unwrap();
let callback = env.new_global_ref(callback).unwrap();
let (tx, rx) = mpsc::channel();
let _ = thread::spawn(move || {
tx.send(()).unwrap();
let env = jvm.attach_current_thread().unwrap();
let hello = "hello syncCallback";
let jni_string_hello = JNIString::from(hello);
let j_string_hello = env.new_string(jni_string_hello).unwrap();
let j_value_hello = JValue::from(j_string_hello);
for _i in 0..6 {
env.call_method(&callback, "onStringCallback", "(Ljava/lang/String;)V", &[j_value_hello]).unwrap();
thread::sleep(Duration::from_millis(2000));
}
});
rx.recv().unwrap();
}
这段代码的功能是每2s
回调一次onStringCallback
.下面我们简要的分析一下代码.
-
let jvm = env.get_java_vm().unwrap();
由于JNIEnv
不能跨线程使用, 所以在这里先获取JavaVM
,在线程内再由JavaVM
来获取JNIEnv
. -
let callback = env.new_global_ref(callback).unwrap();
获取一个callback
的全局引用,防止callback
被回收. -
let env = jvm.attach_current_thread().unwrap();
使用JavaVM
接口将JNIEnv
附加到当前线程.
后续就可以正常使用JNIEnv
调用Android
方法了.
4. rust
调用Android
单例方法
- 在
Android
层新建一个单例类
public class NativeSingleton {
private NativeSingleton() {
}
public static NativeSingleton getInstance() {
return NativeSingleton.SingletonHolder.sInstance;
}
private static class SingletonHolder {
private static final NativeSingleton sInstance = new NativeSingleton();
}
}
- 添加一个方法,供
rust
调用
public void logIdentityHashCode(){
Log.d("NativeSingleton",System.identityHashCode(this)+" ");
}
rust
调用单例方法
#[no_mangle]
pub unsafe extern fn Java_com_jni_rust_RustNative_singleton(env: JNIEnv, _: JClass) {
let clz = match env.find_class("com/jni/rust/NativeSingleton") {
Ok(class) => { class }
Err(_) => {
panic!("can't find class NativeSingleton");
}
};
let instance_method_id = match env.get_static_method_id(clz, "getInstance", "()Lcom/jni/rust/NativeSingleton;") {
Ok(ins) => {ins}
Err(_) => {
panic!("can't find method NativeSingleton.getInstance");
}
};
let instance = match env.call_static_method_unchecked(clz, instance_method_id, ReturnType::Object, &[]) {
Ok(obj) => {obj}
Err(_) => {
panic!("can't call method getInstance");
}
};
let instance_obj = JObject::from(instance.l().unwrap());
let log_identity_hashcode = match env.get_method_id(clz, "logIdentityHashCode", "()V") {
Ok(get) => {get}
Err(_) => {
panic!("can't call method logIdentityHashCode");
}
};
env.call_method_unchecked(instance_obj, log_identity_hashcode, ReturnType::Primitive(Void), &[]).unwrap();
}
- 为了演示,本段代码使用了
match
表达式,在遇到Err
时直接做了panic
.在实际应用中最好能做一些处理,使整个项目更健壮些. let instance_obj = JObject::from(instance.l().unwrap());
这部分代码是为了实现JValue
到JObject
的转化,此方法是我在jni
源码里找的,不知道会不会有更清晰的转化方法.- 本段也演示了
call_static_method_unchecked
和call_method_unchecked
方法的使用,如遇相同场景大家可以拿来参考.
扩展知识
1. 显示依赖关系树
cargo tree
显示结果
$ cargo tree
rust_jni_demo v0.1.0 (/home/txs/xxx/xxx/rust_jni_demo)
├── android_logger_lite v0.1.0
└── jni v0.20.0
├── cesu8 v1.1.0
├── combine v4.6.6
│ ├── bytes v1.3.0
│ └── memchr v2.5.0
├── jni-sys v0.3.0
├── log v0.4.17
│ └── cfg-if v1.0.0
└── thiserror v1.0.37
└── thiserror-impl v1.0.37 (proc-macro)
├── proc-macro2 v1.0.47
│ └── unicode-ident v1.0.5
├── quote v1.0.21
│ └── proc-macro2 v1.0.47 (*)
└── syn v1.0.105
├── proc-macro2 v1.0.47 (*)
├── quote v1.0.21 (*)
└── unicode-ident v1.0.5
[build-dependencies]
└── walkdir v2.3.2
└── same-file v1.0.6
依赖关系一目了然.
cargo-deps
具体命令cargo deps | dot -Tpng > dep.png
,结果会在当前目录下生成一个依赖图
cargo-deps
可以配合cargo tree
使用,帮助我们更好的分析依赖情况.
如果遇到没有cargo deps
命令的情况,安装即可.安装命令如下:
cargo install cargo-deps
总结
本章主要介绍了android
与rust
进行互相调用的基础知识,包括常见的数据类型,回调,单例等.更多玩法请参考官方示例https://github.com/jni-rs/jni-rs/blob/master/example/mylib/src/lib.rs
.
Android
项目地址:https://github.com/tangxuesong6/Android_Rust_JNI_Demo
rust
项目地址:https://github.com/tangxuesong6/Rust_JNI_Demo