> 文章列表 > 使用jni-rs实现Rust与Android代码互相调用

使用jni-rs实现Rust与Android代码互相调用

使用jni-rs实现Rust与Android代码互相调用

本篇主要是介绍如何使用jni-rs。有关jni-rs内容基于版本0.20.0,新版本写法有所不同。

入门用法

在Rust库交叉编译以及在Android与iOS中使用中我简单说明了jni-rs及demo代码,现在接着补充一些详细内容。

首先贴上之前的示例代码:

use std::os::raw::{c_char};
use std::ffi::{CString, CStr};#[no_mangle]
pub extern fn rust_greeting(to: *const c_char) -> *mut c_char {let c_str = unsafe { CStr::from_ptr(to) };let recipient = match c_str.to_str() {Err(_) => "there",Ok(string) => string,};CString::new("Hello ".to_owned() + recipient).unwrap().into_raw()
}#[cfg(target_os="android")]
#[allow(non_snake_case)]
pub mod android {extern crate jni;use super::*;use self::jni::JNIEnv;use self::jni::objects::{JClass, JString};use self::jni::sys::{jstring};#[no_mangle]pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_greeting(env: JNIEnv,_class: JClass,java_pattern: JString) -> jstring {// Our Java companion code might pass-in "world" as a string, hence the name.let world = rust_greeting(env.get_string(java_pattern).expect("invalid pattern string").as_ptr());// Retake pointer so that we can use it below and allow memory to be freed when it goes out of scope.let world_ptr = CString::from_raw(world);let output = env.new_string(world_ptr.to_str().unwrap()).expect("Couldn't create java string!");output.into_raw()}
}
  • 方法的前两个参数JNIEnvJClass是必须的。
  • 其中JNIEnv是JVM接口,用来调用JVM方法。比如例子中的get_stringnew_string方法。
  • JClass是静态方法的类。不会被使用,但仍需要有一个参数槽。
  • JStringjstring类型说明:JStringjstring的生命周期表现形式。在使用jni-rs编写JNI代码时,建议使用JString类型,因为它提供了更好的类型安全性和方便性,而不必担心内存分配和释放问题。当需要将JString类型转换为jstring类型时,可以使用JString::into_raw()方法。例如上面的例子,可以使用JString作为返回值,那么最后一行可以直接使用output

jni-rs中的类型与Java中的类型对应如下:

jni-rs Java
jboolean boolean
jbyte byte
jchar char
jshort short
jint int
jlong long
jfloat float
jdouble double
jstring、JString String
jobject、JObject Object
jclass、JClass Object
jarray 数组
jbooleanArray boolean[]
jbyteArray byte[]
jcharArray char[]
jshortArray short[]
jintArray int[]
jlongArray long[]
jfloatArray float[]
jdoubleArray double[]
jobjectArray Object[]

上面类型基本与C/C++写法一致,需要注意的是有几个首字母大写形式的类型。


另外补充一点,上面的例子中存在一个Rust方法rust_greeting。为了调用它,我们用到了as_ptr方法将JavaStr转为c_char类型。然后返回c_char结果再转为JString,最后调用into_raw转换为jstring类型返回。之所以这么麻烦,因为我们这里考虑到iOS直接调用rust_greeting的情况。如果不考虑iOS,只是用于Android端,那么可以简化为:

    #[no_mangle]pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_greeting(env: JNIEnv,_class: JClass,java_pattern: JString) -> jstring {let world: String = env.get_string(java_pattern).expect("invalid pattern string").into();let output = env.new_string(format!("Hello, {}!", world)).expect("Couldn't create java string!");output.into_raw()}

进阶用法

上面主要是Android调用Rust代码。下面来看如何在Rust中调用Android的代码。

首先我们需要了解JNI字段描述符,JNI字段描述符是一种对函数返回值和参数的编码。

描述符 类型 例子
V void -
I int (I)V 表示 void function(int param)
B btye (B)V 表示 void function(btye param)
C char -
D double -
F float -
J long -
S short -
Z boolean -
[element_type 一维数组 ([I)V 表示 void function(int[] param)
L classname ; Ljava/lang/String; 表示String
  • 注意对象类型,以L开头,以;结尾。
  • 如果是二维数组,就是两个[符号。比如[[I就表示int[][]

1.回调

我们先实现Android端,添加一个传入接口CallBack的方法:

public class RustGreetings {...private static native void greetingCallBack(final String pattern, CallBack callBack);public void sayHelloCallBack(String to, CallBack callBack) {greetingCallBack(to, callBack);}
}public interface CallBack {void result(String str);
}

对应的Rust部分代码如下:

#[no_mangle]
pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_greetingCallBack(env: JNIEnv,_class: JClass,java_pattern: JString,callback: JObject
) {let world = rust_greeting(env.get_string(java_pattern).expect("invalid pattern string").as_ptr());let world_ptr = CString::from_raw(world);let output = env.new_string(world_ptr.to_str().unwrap()).expect("Couldn't create java string!");// 延时1sthread::sleep(Duration::from_millis(1000));env.call_method(callback, "result", "(Ljava/lang/String;)V", &[JValue::from(output)]).unwrap();
}
  • CallBack对应的类型是JObject
  • call_method是Rust调用Android方法的函数。
  • result是调用方法名,(Ljava/lang/String;)V括号内是调用方法的参数类型String,V是方法返回值。
  • &[JValue::from(output)]是传入的参数。没有参数就写&[]

调用代码测试一下:

val rust = RustGreetings()
System.out.println(rust.sayHello("World"))
rust.sayHelloCallBack("Rust") { str ->System.out.println(str)
}

调用结果:
在这里插入图片描述

2.指针

有时我们需要将Rust中产生的数据供多个方法使用。一种方法是使用全局变量,比如之前文中提到的lazy_static,我们将结构体保存在Map中,使用时从中获取。另一种方法我们可以将数据的指针返给Android端,后面使用时,可以再传给Rust端使用。下面我们介绍一下第二种方法。

这里我就直接用jni-rs的demo来说明。

public class RustGreetings {...private static native long counterNew(HelloWorld callback);private static native void counterIncrement(long counterPtr);private static native void counterDestroy(long counterPtr);
}public class HelloWorld {public void counterCallback(int count) {System.out.println("counterCallback: count = " + count);}
}

添加了三个方法,counterNew用来传入回调对象。counterIncrement是计数器的自增方法。counterDestroy来释放计数器。

对应Rust代码如下:

    struct Counter {count: i32,callback: GlobalRef,}impl Counter {pub fn new(callback: GlobalRef) -> Counter {Counter {count: 0,callback: callback,}}pub fn increment(&mut self, env: JNIEnv) {self.count = self.count + 1;env.call_method(&self.callback,"counterCallback","(I)V",&[self.count.into()],).unwrap();}}#[no_mangle]pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_counterNew(env: JNIEnv,_class: JClass,callback: JObject,) -> jlong {let global_ref = env.new_global_ref(callback).unwrap();let counter = Counter::new(global_ref);Box::into_raw(Box::new(counter)) as jlong}#[no_mangle]pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_counterIncrement(env: JNIEnv,_class: JClass,counter_ptr: jlong,) {let counter = &mut *(counter_ptr as *mut Counter);counter.increment(env);}#[no_mangle]pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_counterDestroy(_env: JNIEnv,_class: JClass,counter_ptr: jlong,) {let _boxed_counter = Box::from_raw(counter_ptr as *mut Counter);}
  • 代码通过将计数器结构体Counter的指针返回给Android端,实现了计数自增功能。
  • 指针我们通过Box生成,它的作用是将你的数据存储在堆上,然后在栈中保留一个智能指针指向堆上的数据。
  • 调用from_raw函数后,释放分配的内存.

调用部分代码:

long counterPtr = counterNew(new HelloWorld());for (int i = 0; i < 5; i++) {counterIncrement(counterPtr);
}counterDestroy(counterPtr);

结果如下:
在这里插入图片描述

3.单例

Android端示例代码如下:

package com.weilu.demo;import android.util.Log;public class Singleton {private Singleton() {}public static Singleton getInstance() {return Singleton.SingletonHolder.INSTANCE;}private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}public void sayHello(){Log.d("RustDemo","Hello!");}
}

一个简单的单例,里面有一个sayHello方法。

然后对应的Rust部分代码如下:

 #[no_mangle]
pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_sayHello(env: JNIEnv,_class: JClass
) {let class = env.find_class("com/weilu/demo/Singleton").expect("can't find class Singleton");let instance_method_id = env.get_static_method_id(class, "getInstance", "()Lcom/weilu/demo/Singleton;").expect("can't find method Singleton.getInstance");let instance = env.call_static_method_unchecked(class, instance_method_id, ReturnType::Object, &[]).expect("can't call method getInstance");let instance_obj = JObject::from(instance.l().unwrap());let say_hello = env.get_method_id(class, "sayHello", "()V").expect("can't call method sayHello");env.call_method_unchecked(instance_obj, say_hello, ReturnType::Primitive(Void), &[]).unwrap();
}
  • find_class,顾名思义查找Class。这里是获取Singleton类。
  • get_static_method_id,获取静态方法id。这里是获取getInstance方法。
  • call_static_method_unchecked,调用静态方法。这里是调用getInstance方法,获取Singleton单例。
  • get_method_id,获取方法id。这里是获取sayHello方法。
  • call_method_unchecked,调用方法。这里是调用sayHello方法。
  • ReturnType::ObjectReturnType::Primitive(Void)是方法的返回类型,对应代码中的Singletonvoid

同理,可以使用上述这一套调用任何存在的系统方法,例如调用Java中的 System.currentTimeMillis(); 方法获取时间戳:

#[no_mangle]
pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_currentTimeMillis(env: JNIEnv,_class: JClass
) -> jlong {let system = env.find_class("java/lang/System").unwrap();let result = env.call_static_method(system, "currentTimeMillis", "()J", &[]).unwrap();let time = result.j().unwrap();time as jlong
}

参考

  • JNI字段描述符
  • Android Rust JNI系列教程(三) Rust与Android互相调用