原文发布于我的博客:https://julis.wang

一、前言

在 Android的开发中,我们有 JNI 使得 Java可以调用本地应用或库。
Flutter 在前不久发布了 Flutter2,更新了 FFI (我们是否可以把它叫做DNI呢?)进入了稳定状态,开发者可以更安心的使用其功能。
但是相关的文档依然很欠缺,导致使用起来有诸多的疑问,以及相关原理性的介绍比较少,所以整理记录一下。

二、Dart 同步调用 Native 方法

我们以最简单的demo为例,请求一个有参无返回值的C方法
在 C/C++中有如下函数:

extern "C" __attribute__((visibility("default"))) __attribute__((used))void c_with_out_return_value(int value) {LOG_D("Got invoke value: %d", value);
}

Dart:

final DynamicLibrary ffiLib = Platform.isAndroid ? DynamicLibrary.open('lib_invoke.so') : DynamicLibrary.process();final cMethod = ffiLib.lookupFunction<Void Function(Int32 value), void Function(int value)>('c_with_out_return_value');
cMethod(123);

这样一次调用就完成了一次调用,传递了123到Native并执行了一次打印,同理相关有参有返回值的请求也都是这样做到的,那 Dart 和 Native内部具体怎样实现的呢?

DynamicLibrary.open() 最终执行的逻辑如下, 源码位于ffi_dynamic_library.cc:

static void* LoadExtensionLibrary(const char* library_file) {#if defined(HOST_OS_LINUX) || defined(HOST_OS_MACOS) ||                        \defined(HOST_OS_ANDROID) || defined(HOST_OS_FUCHSIA)void* handle = dlopen(library_file, RTLD_LAZY);if (handle == nullptr) {char* error = dlerror();const String& msg = String::Handle(String::NewFormatted("Failed to load dynamic library (%s)", error));Exceptions::ThrowArgumentError(msg);}return handle;……

可以看到最终使用 dlopen 加载动态链接库,并返回句柄。

拿到对应的动态链接库的句柄之后,就能使用相关方法进行操作了。
句柄主要包含以下两个方法:

//在内存中查找对应符号名的地址,与dlsym()功能相同
external Pointer<T> lookup<T extends NativeType>(String symbolName);//1、去动态库中查找对应名称的函数
//2、将 Native 类型的 C/C++ 函数转化为 Dart 的 Function 类型
external F lookupFunction<T extends Function, F extends Function>(String symbolName);

其中lookup()的最终实现主要使用了 dlsym

static void* ResolveSymbol(void* handle, const char* symbol) {#if defined(HOST_OS_LINUX) || defined(HOST_OS_MACOS) ||                       defined(HOST_OS_ANDROID) || defined(HOST_OS_FUCHSIA)dlerror();  // Clear any errors.void* pointer = dlsym(handle, symbol);if (pointer == nullptr) {char* error = dlerror();const String& msg = String::Handle(String::NewFormatted("Failed to lookup symbol (%s)", error));Exceptions::ThrowArgumentError(msg);}return pointer;

三、Dart 异步调用 Native 方法

在很多场景我们不能像上述同步方法那样,dart 进行一次请求之后立马得到结果,可能会有一些耗时操作,为了不让 Flutter 的UI线程卡住,我们进行异步请求。那如何实现异步请求呢?
对于异步实现,官方并没有很明确的文档,都得靠自己琢磨,在官方的讨论中 https://github.com/dart-lang/sdk/issues/37022 以及 https://github.com/flutter/flutter/issues/63255 提到一些解决方案:

1.In your C++ code include include/dart_api_dl.h and include/dart_api_dl.cc from here https://github.com/dart-lang/sdk/blob/master/runtime/include/ (they also depend on include/internal/*).

2.From Dart call Dart_InitializeApiDL passing NativeApi.initializeApiDLData as an argument.

3.On Dart side create a ReceivePort and pass port number of the corresponding SendPort to the native side (port.sendPort.nativePort).

4.Now on C++ side you can use Dart_PostCObject_DL to send messages back to Dart side from any thread.

按上述的操作进行实现,接下来具体分析一些里面的逻辑原理。
1、导入include/dart_api_dl.h include/dart_api_dl.cc 相关的文件并在 CMakeList.txt进行相关配置
2、从dart中 调用Native中 Dart_InitializeApiDL

Dart:

void main() {initializeApi(NativeApi.initializeApiDLData);runApp(MyApp());
}

C++:

// Initialize `dart_api_dl.h`
DART_EXPORT intptr_t InitDartApiDL(void *data) {LOG_D("InitDartApiDL");return Dart_InitializeApiDL(data);
}

在 initializeApi(NativeApi.initializeApiDLData) 中 initializeApi 向 Native请求 DART_EXPORT intptr_t InitDartApiDL(void *data)方法,传入的参数就是在 dart_api_dl.h
DART_NATIVE_API_DL_SYMBOLS 以及 DART_API_DL_SYMBOLS 中的方法。

NativeApi.initializeApiDLData 逻辑:

static const DartApiEntry dart_api_entries[] = {#define ENTRY(name, R, A)                                                      \DartApiEntry{#name, reinterpret_cast<void (*)()>(name)},DART_API_ALL_DL_SYMBOLS(ENTRY)
#undef ENTRYDartApiEntry{nullptr, nullptr}};static const DartApi dart_api_data = {DART_API_DL_MAJOR_VERSION, DART_API_DL_MINOR_VERSION, dart_api_entries};DEFINE_NATIVE_ENTRY(DartApiDLInitializeData, 0, 0) {return Integer::New(reinterpret_cast<intptr_t>(&dart_api_data));
}

dart_api_dl中定义的方法:

#define DART_NATIVE_API_DL_SYMBOLS(F)                                          \/***** dart_native_api.h *****/                                              \/* Dart_Port */                                                              \F(Dart_PostCObject, bool, (Dart_Port_DL port_id, Dart_CObject * message))    \F(Dart_PostInteger, bool, (Dart_Port_DL port_id, int64_t message))           \.....// dart_api.h symbols can only be called on Dart threads.
#define DART_API_DL_SYMBOLS(F)                                                 \/***** dart_api.h *****/                                                     \/* Errors */                                                                 \F(Dart_IsError, bool, (Dart_Handle handle))                                  \F(Dart_IsApiError, bool, (Dart_Handle handle))                               \.....

其实这上面的逻辑很简单,主要是为了让业务中的代码能够进行动态链接,从而调用到 Flutter SDK 中相关方法。

3、第三步添加 ReceivePort 监听

class Work extends Opaque {}void requestExecuteCallback(dynamic message) {final int workAddress = message as int;final Pointer<Work> work = Pointer<Work>.fromAddress(workAddress);executeCallback(work);
}final ReceivePort interactiveCppRequests = ReceivePort()..listen(requestExecuteCallback);

向 Native 发送带有 interactiveCppRequests.sendPort.nativePort 的数据,为native异步回调做准备。


Future<int> platformAsync(int value1, int value2) {final Completer<int> completer = Completer<int>();final String cid = uuid.v1();final Pointer<Utf8> cidPtr = cid.toNativeUtf8();completerMapping[cid] = completer;//生成一个nativePort,为native异步回调做准备final int nativePort = interactiveCppRequests.sendPort.nativePort; final cMethod = ffiLib.lookupFunction<Int32 Function(Pointer<Utf8> cId, Int64 sendPort, Int32 value1, Int32 value2,Pointer<NativeFunction<callback_type>> callbackBlock),int Function(Pointer<Utf8> cId, int sendPort, int value1, int value2,Pointer<NativeFunction<callback_type>> callbackBlock)>('platform_async');cMethod(cidPtr, nativePort, value1, value2, Pointer.fromFunction<callback_type>(_callbackBlocking));return completer.future;
}

4、当异步执行完成之后,在 Native 执行 Dart_PostCObject_DL 通知 Dart 已经得到结果
对于代码如下:

void response(jint result) {Work work = [_callback,result] {if (_callback != nullptr) {_callback( result);} else {LOG_E("_callback == null");}};// Copy to heap to make it outlive the function scope.const Work *work_ptr = new Work(work);NotifyDart(send_port, work_ptr);
}void NotifyDart(Dart_Port send_port, const Work *work) {const auto work_address = reinterpret_cast<intptr_t>(work);Dart_CObject dart_object;dart_object.type = Dart_CObject_kInt64;dart_object.value.as_int64 = work_address;const bool result = Dart_PostCObject_DL(send_port, &dart_object);if (!result) {LOG_D("FFI C  :  Posting message to port failed.");}
}

上面的代码最核心的就是Dart_PostCObject_DL()这里真正调用的还是,Dart_PostCObject(),加_DL()表示动态链接的方法,为了防止与原先符号冲突。

All symbols are postfixed with _DL to indicate that they are dynamically

linked and to prevent conflicts with the original symbol.

我们继续看看 Dart_PostCObject()真正做了什么,Dart_PostCObject()最终调用的方法如下:


static bool PostCObjectHelper(Dart_Port port_id, Dart_CObject* message) {ApiMessageWriter writer;std::unique_ptr<Message> msg =writer.WriteCMessage(message, port_id, Message::kNormalPriority);if (msg == nullptr) {return false;}// Post the message at the given port.return PortMap::PostMessage(std::move(msg));
}

这里在向 Service Isolate发送事件,最终 Dart 成功接受到异步消息的回调。
关于 Isolate 这一块的处理可以参考:Async Coding With Dart: Isolates

四、Native 调用 Dart方法 ?

对于 JNI 里面,我们需要调用 Java的方法,利用Java反射机制调用即可,如下所示:

_env->CallStaticVoidMethod(j_class, j_method, arg1, arg2);

如果 Native 想要调用 Dart代码有类似的代码可以用么?翻遍了 Flutter相关的文档,都没有找到对应的方法可以直接去调用 Dart的方法,Dart Engine内部有 dart_api.h提供了Dart_invoke()方法,但单纯的导入 .h文件在项目中是无法链接到对应的方法的,这也就是为什么需要导入ffi_runtime_lib相关的文件并执行 Dart_InitializeApiDL(),通过动态链接使得代码能够去调用 Dart 封装的相关方法。

所以参考 Dart中 InitDartApiDL的方法,我们先对 Dart 中的函数进行注册,传递对应方法的指针,然后在 Native 中即可调用,理论上可行,后续会补上相关 demo。
当然这只是一种骚操作,如果有更好的方法能够用 Native 调用 Dart 欢迎讨论。

五、总结

文章记录了 Dart 同步和异步调用 Native 相关的使用,异步具体的使用比上述的代码复杂,因为需要一个中介记录异步相关的回调方法,当得到真正的结果之后,利用id查找到对应的方法再执行回调方法。FFI 在 Native中执行 dart 方法,暂时没有比较好的解决方案
FFI 调用可查看Demo : https://github.com/VomPom/flutter_ffi_tutorial

Flutter FFI实践相关推荐

  1. Flutter 最佳实践 | 专家直播答疑

    什么是程序开发的艺术? 这里引用<梦断代码>中的一句话:"程序开发应该是一种艺术行为,程序员编写代码更多的不是为了客户,而是为了自己对新技术突破的快感,对程序bug消除后的兴奋. ...

  2. 《Flutter in action》开放下载!闲鱼Flutter企业级实践精选

    复制链接到浏览器 https://yq.aliyun.com/download/3792?utm_content=g_1000081730 下载. 闲鱼是国内最早使用Flutter的团队,也是Flut ...

  3. 今日头条 字节跳动 Flutter架构实践

    今日头条 字节跳动 Flutter架构实践 1 移动跨平台技术探究 ◆ 为什么需要跨平台? 今日头条 字节跳动 Flutter架构实践 ◆ 跨平台技术是如何发展起来的? 今日头条 字节跳动 Flutt ...

  4. 01、Flutter FFI 最简示例

    Flutter FFI 学习笔记系列 <Flutter FFI 最简示例> <Flutter FFI 基础数据类型> <Flutter FFI 函数> <Fl ...

  5. 03、Flutter FFI 函数

    Flutter FFI 学习笔记系列 <Flutter FFI 最简示例> <Flutter FFI 基础数据类型> <Flutter FFI 函数> <Fl ...

  6. Flutter 沙龙回顾 | 跨平台技术趋势及字节跳动 Flutter 架构实践

    11 月 23 日,字节跳动技术沙龙 | Flutter 技术专场 在北京后山艺术空间圆满结束.我们邀请到字节跳动移动平台部 Flutter 架构师袁辉辉,Google Flutter 团队工程师 J ...

  7. flutter release 版本 调试_腾讯课堂Flutter工程实践系列——接入篇

    前言 课堂目前的技术栈是React Native + Hybird + Native,随着技术的演进多端融合的趋势越来越明显,而RN的弊端也突显出来,jsBridge性能不是最优,占用前端人力,定位问 ...

  8. flutter 项目实践2

    本片文章来自与我自己的有道云笔记 要看图片请点击链接 文档:Day 4_3 项目实践2.md 链接:http://note.youdao.com/noteshare?id=f28e3058fea4d2 ...

  9. 美团外卖Flutter动态化实践

    此文转载自:https://my.oschina.net/meituantech/blog/4325381 LiteOS Studio图形化调测能力,物联网打工人必备!>>> 一.前 ...

  10. 万字长文,请耐心看完!美团外卖Flutter动态化实践,5w+人已阅

    作者:尚先 杨超 松涛 原文链接:https://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651752194&idx=1& ...

最新文章

  1. Elasticsearch 6.3.1、Head插件 安装及配置
  2. MYSQL体系结构-来自期刊
  3. Appcan页面跳转
  4. fetch vue读取json文件_前端笔记——尝试理解并在JavaScript中使用Fetch()
  5. pdf转换为word小工具,挺好
  6. 名片识别信息分类python_python文字识别
  7. mybatis 配置文件中,collection 和 association 的对应关系
  8. [转]html导出到excel数据格式不正确解决方法
  9. section和div有什么区别?
  10. 《21天学通Java(第6版)》—— 1.2 面向对象编程
  11. 如何选型音视频即时通讯产品
  12. iOS 微信授权登录
  13. u盘虚拟启动cd linux,CDlinux系统用U盘搭建启动盘
  14. 佳木斯大学计算机专业宿舍,佳木斯大学管理学院宿舍
  15. 计算机网络基础之数据链路层的功能与服务
  16. Excel表列名称(4)
  17. 腾讯网页游戏微端服务器进程,彻底卸载腾讯网页游戏微端服务程序和腾讯游戏盒子的方法...
  18. FreeBSD 背景
  19. map容器/multimap容器
  20. is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto

热门文章

  1. Android 语言码_国家码
  2. 漫话:如何给女朋友解释什么是RPC
  3. WARN: Establishing SSL connection without server‘s identity verification is not recommended
  4. 【Java】使用AOP进行异常处理与日志记录
  5. 自主研发国产高端企业云服务器,浪潮商用机器如何聚沙成塔?
  6. 关于phyton中print函数的问题
  7. 用计算机观察声音的波形,用计算机观察声音的波形
  8. 深圳市已获取支付牌照公司
  9. WPF 3D 贴图: 为你的二次元老婆们做个3D画廊
  10. git中fatal: Authentication failed for 的问题