基于内存共享的Dart VM和JVM通信demo

929 阅读4分钟

前言

让dart虚拟机和Jvm虚拟机不使用MethodChannel,直接通过ffi和jni进行通信的小demo,用一块native内存双端映射,提升数据传输效率。

思路

dart支持ffi调用C/C++函数,java支持jni(安卓中的ndk)。所以我们用一个库同时支持ffi和jni,两者直接通信即可。

传递数据时由native申请内存,Java端将内存映射为:DirectByteBuffer对象,Dart端映射为Pointer对象,双方可以同时操作,读取。

思路很简单,但实现起来有一些问题:

  1. dart代码和安卓主线程不在一个线程,所以要注意线程安全
  2. MethodChannel调用原生方法时会切换到Platform Runner,dart代码在UI Runner,所以为异步,原生的执行不会影响dart代码的执行,我们的demo为了简单会直接在UI Runner线程中执行原生方法,所以会阻塞dart执行,即同步调用。
  3. 序列化反序列化就不涉及了,我们就演示下数据的传输。

demo实现

JNI端实现

问题:

  1. jni端调用java端方法必须要先获取到JNIEnv对象,但该对象是java端调用native方法虚拟机自动传入的,且无法跨线程使用,所以我们无法直接在UI Runner中获取到JNIEnv对象。

解决办法:

先提前在java端调用一次native方法初始化属性,通过env获取到JavaVM实例,然后将UI Runner attach到JVM Thread上,就可以在UI Runner中获取到env对象

  1. 同上,我们调用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),

      ),

 );

  }

}

运行截图: