前言
让dart虚拟机和Jvm虚拟机不使用MethodChannel,直接通过ffi和jni进行通信的小demo,用一块native内存双端映射,提升数据传输效率。
思路
dart支持ffi调用C/C++函数,java支持jni(安卓中的ndk)。所以我们用一个库同时支持ffi和jni,两者直接通信即可。
传递数据时由native申请内存,Java端将内存映射为:DirectByteBuffer对象,Dart端映射为Pointer对象,双方可以同时操作,读取。
思路很简单,但实现起来有一些问题:
- dart代码和安卓主线程不在一个线程,所以要注意线程安全
- MethodChannel调用原生方法时会切换到Platform Runner,dart代码在UI Runner,所以为异步,原生的执行不会影响dart代码的执行,我们的demo为了简单会直接在UI Runner线程中执行原生方法,所以会阻塞dart执行,即同步调用。
- 序列化反序列化就不涉及了,我们就演示下数据的传输。
demo实现
JNI端实现
问题:
- jni端调用java端方法必须要先获取到JNIEnv对象,但该对象是java端调用native方法虚拟机自动传入的,且无法跨线程使用,所以我们无法直接在UI Runner中获取到JNIEnv对象。
解决办法:
先提前在java端调用一次native方法初始化属性,通过env获取到JavaVM实例,然后将UI Runner attach到JVM Thread上,就可以在UI Runner中获取到env对象
- 同上,我们调用java方法时需要env->FindClass,该值仍然无法跨线程,只是局部的。
解决办法:
在初始化中先获取到对应类的id,然后转换为GlobalRef
代码实现:
MainActivity.kt
class MainActivity: FlutterActivity() {
init {
System.loadLibrary("nativelib")
setupNative()
}
companion object {
/**
* 用于初始化native端,设置必要的Jvm属性
*/
@JvmStatic
external fun setupNative();
/**
* 接收数据时调用
*/
@JvmStatic
fun receiveData(buffer: ByteBuffer){
//处理数据
}
}
}
nativelib.cpp
#include <jni.h>
#include "transfer.h"
const char* callClassPath = "com/guojm/demo/flutter_ffi_demo/MainActivity";
jclass clz;
jmethodID methodId;
void jvmCallback(JNIEnv* env,Addr addr,int64_t len){
//创建DirectByteBuffer
auto bufferObj = env->NewDirectByteBuffer(addr,len);
//调用java方法,传递参数
env->CallStaticVoidMethod(clz,methodId,bufferObj);
}
/**
* 注册回调
* 负责监听dart端的调用
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_guojm_demo_flutter_1ffi_1demo_MainActivity_setupNative(JNIEnv *env, jclass thiz) {
setJvmCallback(env,jvmCallback);
//获取methodId和classId
jclass local_clz = env->FindClass(callClassPath);
//,转换为GlobalRef
clz = static_cast<jclass>(env->NewGlobalRef(local_clz)) ;
methodId = env->GetStaticMethodID(clz,"receiveData", "(Ljava/nio/ByteBuffer;)V");
}
中间传输端
这里比较简单,不做线程转换的处理了,直接保存一个回调,获取env即可
transfer.h
//
// Created by guojm on 2022/1/26.
//
#pragma once
#include <jni.h>
using Addr = void*;
using JvmCallback = void (*)(JNIEnv*,Addr addr, int64_t len);
/**
* 发送数据到jvm
*/
void sendDataToJvm(Addr addr,int64_t len);
/**
* 发送数据到dart端
*/
void sendDataToDart(Addr addr,int len);
void setJvmCallback(JNIEnv* env,JvmCallback callback);
transfer.cpp
//
// Created by guojm on 2022/1/26.
//
#include "transfer.h"
static JvmCallback jvm_callback;
static JavaVM* jvm;
void setJvmCallback(JNIEnv* env,JvmCallback callback){
//设置callback和获取jvm实例
jvm_callback = callback;
env->GetJavaVM(&jvm);
}
void sendDataToJvm(Addr addr,int64_t len){
JNIEnv* jniEnv;
int result = jvm->AttachCurrentThread(&jniEnv, nullptr);
jvm_callback(jniEnv, addr, len);
}
void sendDataToDart(Addr addr,int64_t len){
//TODO...
}
Dart端实现
dart端实现很简单,不用对C++端做特殊要求,在dart代码里直接获取查找方法即可。
nativelib.cpp
//
// Created by guojm on 2022/1/26.
//
#include <cstdint>
#include "transfer.h"
#define DART_METHOD extern "C" __attribute__((visibility("default"))) __attribute__((used))
//分配一块儿内存
DART_METHOD char* dartAlloc(int64_t capacity){
auto* addr = new char[capacity];
return addr;
}
DART_METHOD void dartSendData(char* addr, int64_t len){
sendDataToJvm(addr,len);
}
main.dart
import 'dart:ffi' as ffi;
//声明两个原生函数的类型
typedef EXTERN_FUNC_ALLOC = ffi.Pointer<ffi.Uint8> Function(ffi.Int64 len);
typedef EXTERN_FUNC_SEND_DATA = ffi.Void Function(ffi.Pointer<ffi.Uint8> addr,ffi.Int64 len);
//声明两个对应的dart函数类型
typedef Alloc = ffi.Pointer<ffi.Uint8> Function(int len);
typedef SendData = void Function(ffi.Pointer<ffi.Uint8> addr,int len);
final lib = ffi.DynamicLibrary.open("libdartlib.so");
final Alloc alloc = lib.lookup<ffi.NativeFunction<EXTERN_FUNC_ALLOC>>("dartAlloc")
.asFunction();
final SendData sendData = lib.lookup<ffi.NativeFunction<EXTERN_FUNC_SEND_DATA>>("dartSendData")
.asFunction();
//分配一块儿内存
final pointer = alloc(10);
sendData(pointer,10);
测试
新增一段测试逻辑,java端修改数据后在dart端是立马可见的。
@JvmStatic
fun receiveData(buffer: ByteBuffer){
//随机生成一组数据
buffer.position(0)
for (i in 0 until buffer.capacity()){
buffer.put((Math.random() * 10).toInt().toByte())
}
}
申请一片内存,点击按钮发送到java端,由于是同步调用,所以我们可以立马再次读取,可以看到数据已经修改。
import 'package:flutter/material.dart';
import 'dart:ffi' as ffi;
typedef EXTERN_FUNC_ALLOC = ffi.Pointer<ffi.Uint8> Function(ffi.Int64 len);
typedef EXTERN_FUNC_SEND_DATA = ffi.Void Function(ffi.Pointer<ffi.Uint8> addr,ffi.Int64 len);
typedef Alloc = ffi.Pointer<ffi.Uint8> Function(int len);
typedef SendData = void Function(ffi.Pointer<ffi.Uint8> addr,int len);
final lib = ffi.DynamicLibrary.open("libdartlib.so");
final Alloc alloc = lib.lookup<ffi.NativeFunction<EXTERN_FUNC_ALLOC>>("dartAlloc")
.asFunction();
final SendData sendData = lib.lookup<ffi.NativeFunction<EXTERN_FUNC_SEND_DATA>>("dartSendData")
.asFunction();
//分配内存
final pointer = alloc(10);
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
//读取数据
String data = "";
for(int i = 0;i<10;i++){
data += "${pointer[i]}";
}
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'数据为:$data',
),
const Text(
"点击按钮,发生数据到原生端,并使原生端修改数据",
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){
//由于是同步调用,修改后立即可见,重新读取数据
sendData(pointer,10);
setState(() {});
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
运行截图: