本文最后更新于:a few seconds ago
前言
网上这方面比较缺少或者不完整,因此记录一下整个流程。
增加一下关键字防止搜索不到,这篇教程能让有需要的人少几天的琢磨时间。
使用rust写安卓库
rust调用android
rust通过ffi调用kotlin
主要的流程
主要流程是通过全局缓存保存了android的环境变量。以此来实现在rust调用android方法。
尝试过的方法
- 直接通过rust闭包保存android环境,出现了方法不安全。✖️
- 通过线程通信,结合闭包传递信息给fni方法。出现了线程通信不安全。✖️
- 通过全局变量。也就是接下来的方法。 ✅
首先先看android部分如何进行加载
下面是android部分代码。主要是是加载库,然后注册函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package org.bfchain.rust.example
import android.app.IntentService import android.content.Intent import android.util.Log private const val TAG = "SERVICE" class DenoService : IntentService("DenoService") { companion object { init { System.loadLibrary("rust_lib") } } interface IHandleCallback { fun handleCallback(string: String) } external fun nativeSetCallback(callback: IHandleCallback) override fun onHandleIntent(p0: Intent?) {
nativeSetCallback(object : IHandleCallback { override fun handleCallback(string: String) { Log.d("handleCallback", "now rust says:" + string) } }) } }
|
nativeSetCallback
函数就是我们通过jni可以调用的方法,我们需要在rust通过全局缓存,把android
的状态都缓存下来,
以此来实现我们在rust可以直接调用android方法。
接下来编写rust部分的内容
先上代码,后面再解释。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| use crate::js_bridge::call_android_js; use android_logger::Config; use lazy_static::*; use log::{debug, error, info, Level}; use tokio;
use std::{ ffi::{c_void, CStr, CString}, sync::{mpsc, Mutex}, thread, };
use jni::{ objects::{GlobalRef, JObject, JString, JValue}, sys::{jint, jstring, JNI_ERR,JNI_VERSION_1_4}, JNIEnv, JavaVM, NativeMethod, };
lazy_static! { static ref JVM_GLOBAL: Mutex<Option<JavaVM>> = Mutex::new(None); static ref JNI_CALLBACK: Mutex<Option<GlobalRef>> = Mutex::new(None); }
macro_rules! jni_method { ( $method:tt, $signature:expr ) => {{ jni::NativeMethod { name: jni::strings::JNIString::from(stringify!($method)), sig: jni::strings::JNIString::from($signature), fn_ptr: $method as *mut c_void, } }}; }
#[no_mangle] #[allow(non_snake_case)] unsafe fn JNI_OnLoad(jvm: JavaVM, _reserved: *mut c_void) -> jint { let class_name: &str = "org/bfchain/rust/example/DenoService"; let jni_methods = [ jni_method!(nativeSetCallback, "(Lorg/bfchain/rust/example/DenoService$IHandleCallback;)V"), ];
let ok = register_natives(&jvm, class_name, jni_methods.as_ref());
let mut ptr_jvm = JVM_GLOBAL.lock().unwrap(); *ptr_jvm = Some(jvm); JNI_VERSION_1_4 }
#[no_mangle] pub fn nativeSetCallback(env: JNIEnv, _obj: JObject, callback: JObject) { let callback = env.new_global_ref(JObject::from(callback)).unwrap();
let mut ptr_fn = JNI_CALLBACK.lock().unwrap(); *ptr_fn = Some(callback); }
unsafe fn register_natives(jvm: &JavaVM, class_name: &str, methods: &[NativeMethod]) -> jint { let env: JNIEnv = jvm.get_env().unwrap(); let jni_version = env.get_version().unwrap(); let version: jint = jni_version.into();
let clazz = match env.find_class(class_name) { Ok(clazz) => clazz, Err(e) => { error!("java class not found : {:?}", e); return JNI_ERR; } }; let result = env.register_native_methods(clazz, &methods);
if result.is_ok() { info!("register_natives : succeed"); version } else { error!("register_natives : failed "); JNI_ERR } }
|
JNI_OnLoad
这个函数在你加载so库的时候会自动调用,并不用在android里声明。他返回的是JNI版本,你需要选择你支持的版本。
jni_method 的第二个参数是函数签名,如果你在android注册的函数不一样,需要自己修改函数签名。
如何查找函数的签名,直接google。
nativeSetCallback
env.new_global_ref
是必须的,他可以帮助我们创建全局引用,我们的方法就可以跨方法跨线程调用,这里面的callback就是我们需要传递的闭包函数。
回调函数入口
下面的函数是利用闭包去执行上面缓存的android
环境。call_method
里的handleCallback
函数,
我传递的参数是字符串,因此函数签名为(Ljava/lang/String;)V
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| pub fn call_java_callback(fun_type: &'static str) { android_logger::init_once( Config::default() .with_min_level(Level::Debug) .with_tag("myrust::handleCallback"), ); log::info!("i am call_java_callback xxxxxxx1{:?}",fun_type); call_jvm(&JNI_CALLBACK, move |obj: JObject, env: &JNIEnv| { let s = String::from(fun_type); let response: JString = env .new_string(fun_type) .expect("Couldn't create java string!"); match env.call_method( obj, "handleCallback", "(Ljava/lang/String;)V", &[JValue::from(JObject::from(response))], ) { Ok(jvalue) => { debug!("callback succeed: {:?}", jvalue); } Err(e) => { error!("callback failed : {:?}", e); } } }); }
fn call_jvm<F>(callback: &Mutex<Option<GlobalRef>>, run: F) where F: Fn(JObject, &JNIEnv) + Send + 'static, { let ptr_jvm = JVM_GLOBAL.lock().unwrap(); if (*ptr_jvm).is_none() { return; } let ptr_fn = callback.lock().unwrap(); if (*ptr_fn).is_none() { return; } let jvm: &JavaVM = (*ptr_jvm).as_ref().unwrap(); match jvm.attach_current_thread_permanently() { Ok(env) => { let obj = (*ptr_fn).as_ref().unwrap().as_obj(); run(obj, &env); if let Ok(true) = env.exception_check() { let _ = env.exception_describe(); let _ = env.exception_clear(); } } Err(e) => { debug!("jvm attach_current_thread failed: {:?}", e); } } }
|
jni部分的函数已经写完了,接下来直接去调用call_java_callback
函数就可以了。
调用call_java_callback
use crate::android::android_inter;
就是上面文件的位置。具体根据您项目位置导入。
下面的fun_type
参数就是要传递给android的,以此来调用android函数。
| #[cfg(target_os = "android")] use crate::android::android_inter; pub fn call_android(fun_type: &str) { let callback = Box::leak(String::from(fun_type).into_boxed_str()); #[cfg(target_os = "android")] android_inter::call_java_callback(callback); }
|
Box::leak(String::from(fun_type).into_boxed_str()); 可以把&str变量转化成静态&’static str
结束🐟
如果此文章对您有帮助请发个表情告诉我,如果有其他建议或者勘误请直接留言。
垂杨拂绿水,摇艳东风年。
「折杨柳」
李白