Android Rust JNI系列教程(三) Rust与Android互相调用

前言

RustJNI流程以及方法实际上和我们常见的C++ JNI是十分相似的.我们本章将使用Rust实现常见的JNI调用功能.关于更多的用法,可参考官方示例,github地址为https://github.com/jni-rs/jni-rs/blob/master/example/mylib/src/lib.rs.

基本交互功能实现

1. Java传String,返回byte数组

  1. 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很好的提供了JStringrust String方法以及&[u8]jbyteArray方法.
我们在Android里面测试一下:

  1. Android层声明一个native方法
public static native byte[] getByteFromString(String java_str);
  1. 调用
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. 回调(同步)

  1. 首先在Android层新建一个interface,本文为RustListener
public interface RustListener {
    void onStringCallback(String msg);

    void onVoidCallback();
}

interface定义了两个回调函数onStringCallbackonVoidCallback,其中onStringCallback传回一个String,onVoidCallback则不回传任何参数.

  1. Android层声明的native方法
public static native void  syncCallback(RustListener listener);
  1. 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");
           }
 }); 
  1. 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 StringJString的方法let j_string_hello = env.new_string(jni_string_hello).unwrap().结合上一小节的JStringrust String.我们已经完整学习了rustAndroid直接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. 回调(异步)

  1. 依然使用上一节的RustListener,声明native方法
public static native void  asyncCallback(RustListener listener);
  1. 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");
         }
});
  1. 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单例方法

  1. 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();
    }
}
  1. 添加一个方法,供rust调用
 public void logIdentityHashCode(){
     Log.d("NativeSingleton",System.identityHashCode(this)+" ");
 }
  1. 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());这部分代码是为了实现JValueJObject的转化,此方法是我在jni源码里找的,不知道会不会有更清晰的转化方法.
  • 本段也演示了call_static_method_uncheckedcall_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,结果会在当前目录下生成一个依赖图
Android Rust JNI系列教程(三) Rust与Android互相调用_第1张图片
cargo-deps可以配合cargo tree使用,帮助我们更好的分析依赖情况.

如果遇到没有cargo deps命令的情况,安装即可.安装命令如下:

cargo install cargo-deps

总结

本章主要介绍了androidrust进行互相调用的基础知识,包括常见的数据类型,回调,单例等.更多玩法请参考官方示例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

你可能感兴趣的:(android开发,rust,android,开发语言)