在 Android的开发中,我们有 JNI 使得 Java可以调用本地应用或库。 Flutter 在前不久发布了 Flutter2 ,更新了 FFI (我们是否可以把它叫做DNI呢?)进入了稳定状态,开发者可以更安心的使用其功能。 但是相关的文档依然很欠缺,导致使用起来有诸多的疑问,以及相关原理性的介绍比较少,所以整理记录一下。
一、Dart 同步调用 Native 方法 我们以最简单的demo为例,请求一个有参无返回值的C方法 在 C/C++中有如下函数:
1 2 3 4 5 extern "C" __attribute__((visibility("default" ))) __attribute__((used))void c_with_out_return_value (int value) { LOG_D("Got invoke value: %d" , value); }
Dart:
1 2 3 4 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 :
1 2 3 4 5 6 7 8 9 10 11 12 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 加载动态链接库,并返回句柄。
拿到对应的动态链接库的句柄之后,就能使用相关方法进行操作了。 句柄主要包含以下两个方法:
1 2 3 4 5 6 external Pointer<T> lookup<T extends NativeType>(String symbolName);external F lookupFunction<T extends Function , F extends Function >(String symbolName);
其中lookup()的最终实现主要使用了 dlsym
1 2 3 4 5 6 7 8 9 10 11 12 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(); 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:
1 2 3 4 void main() { initializeApi(NativeApi.initializeApiDLData); runApp(MyApp()); }
C++:
1 2 3 4 5 6 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 逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static const DartApiEntry dart_api_entries[] = {#define ENTRY(name, R, A) \ DartApiEntry{#name, reinterpret_cast<void (*)()> (name)}, DART_API_ALL_DL_SYMBOLS(ENTRY) #undef ENTRY DartApiEntry{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中定义的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #define DART_NATIVE_API_DL_SYMBOLS(F) \ \ \ F(Dart_PostCObject, bool, (Dart_Port_DL port_id, Dart_CObject * message)) \ F(Dart_PostInteger, bool, (Dart_Port_DL port_id, int64_t message)) \ ..... #define DART_API_DL_SYMBOLS(F) \ \ \ F(Dart_IsError, bool, (Dart_Handle handle)) \ F(Dart_IsApiError, bool, (Dart_Handle handle)) \ .....
其实这上面的逻辑很简单,主要是为了让业务中的代码能够进行动态链接,从而调用到 Flutter SDK 中相关方法。
3、第三步添加 ReceivePort 监听
1 2 3 4 5 6 7 8 9 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异步回调做准备。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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; 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 已经得到结果 对于代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void response (jint result) { Work work = [_callback,result] { if (_callback != nullptr ) { _callback( result); } else { LOG_E ("_callback == null" ); } }; 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()最终调用的方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 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 ; } return PortMap::PostMessage(std ::move(msg)); }
这里在向 Service Isolate发送事件,最终 Dart 成功接受到异步消息的回调。 关于 Isolate 这一块的处理可以参考:Async Coding With Dart: Isolates
三、Native 调用 Dart方法 ? 对于 JNI 里面,我们需要调用 Java的方法,利用Java反射机制调用即可,如下所示:
1 _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 flutter_ffi_tutorial