使用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()}
}
- 方法的前两个参数
JNIEnv和JClass是必须的。 - 其中
JNIEnv是JVM接口,用来调用JVM方法。比如例子中的get_string和new_string方法。 JClass是静态方法的类。不会被使用,但仍需要有一个参数槽。JString和jstring类型说明:JString是jstring的生命周期表现形式。在使用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::Object、ReturnType::Primitive(Void)是方法的返回类型,对应代码中的Singleton、void。
同理,可以使用上述这一套调用任何存在的系统方法,例如调用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互相调用


