如何实现 Flutter 同步调用 Native API

移动开发
这篇文章主要讲的是 iOS 的同步调用实现以及性能优化,Android 也已经实现同步调用中基本类型的自动转换。

[[352951]]

Flutter Channel 是一个异步调用通道,如果想在 Dart 侧同步获取到 Native 返回的结果,调用的时候加上 await 就可以了:

  1. final int result = await platform.invokeMethod('hello channel'); 

所以这篇文章到此为止了?

不!上面这行代码其实是个『假同步』,因为它只保证了 Dart 代码的同步执行,而 Native 代码与 Dart 并不在同一条线程执行。试想下,如果你通过 Flutter Channel 打日志,但由于打日志的消息是异步传递到 Native 的,最后日志顺序可能是错的。而通过日志来排查一些时序性相关的 Bug 时,日志的顺序很重要。

因为 Flutter Channel 设计之初就是异步的,使用 await 来回切换线程所带来的开销不小。而且协程的 await 语法具有传递性,上层调用方也需要使用 await,层层传递。

而 DartNative (https://github.com/dart-native/dart_native) 设计之初就是同步调用的,且也支持异步调用:

 

  1. // new DNTest instance and call hello method. 
  2. DNTest().hello('DartNative'); 

Why DartNative?

DartNative 是『真同步』,保证了执行顺序。同时也支持异步调用。

一行代码实现同步调用,告别 Flutter Channel 胶水代码带来的开发成本。

同步调用性能是 Flutter Channel 的数倍。分别使用 Flutter Channel 和 DartNative 调用 fooNSString: 方法,耗时相差三到四倍。性能数据可能在不同场景下有波动,可以通过执行 Benchmark 代码 来对比结果。

实现原理

下图以 Dart 同步调用 iOS Objective-C API 为例,描述了 DartNative 同步调用的原理。以一个字符串参数为例,讲述了从 Dart String 自动转为 Objective-C NSString 并传递给 hello: 方法的过程。返回值也是自动转换类型的,由于篇幅原因没在图片中描述。

在实现了基本的同步调用后,开发重点也转向了性能优化。

方法签名的优化

在 Dart 同步调用 Native 时,为了实现跨语言调用时参数和返回值类型的自动转换,需要先获取到 Native 的方法签名。这里做了两方面的性能优化:

  • 通过 DartFFI 调用 OC Runtime 获取方法签名占据了一定耗时。可以在 Dart 侧加一层 Cache 来减少通信和反射次数。
  • 方法签名字符串的构成是 “TypeEncoding+offset” 的组合,跨语言之间传递字符串的编解码的耗时较多,而只有 TypeEncoding 那部分才是类型自动转换所需要的。绝大部分类型对应的 TypeEncoding 都是固定的,于是只需要传递 TypeEncoding 的指针即可。

字符串转换的优化

Dart String 在与 Objective-C NSString 相互转换的过程中,数据传输的格式的选择至关重要。因为 Dart String 是使用 UTF16 编码的,所以 DartNative 使用 Uint16List 作为数据传输的格式。通过性能测试,使用 UTF16 来回传输字符串的总耗时(包含 Native 方法自身耗时)相比 UTF8 减少了 35% 左右,如果只计算通道自动类型转换耗时减少的比例会更多。

转换 Dart String 为 Objective-C NSString:

使用 DartFFI 在堆上创建 uint16_t 数组,将 Dart String 转为 UTF16 格式后装载进去。最终通过 perform 方法反射调用 stringWithCharacters:length: 方法来创建 NSString 对象。

  1. final units = value.codeUnits; 
  2. final Pointer<Uint16> charPtr = allocate<Uint16>(count: units.length + 1); 
  3. final Uint16List nativeString = charPtr.asTypedList(units.length + 1); 
  4. nativeString.setAll(0, units); 
  5. nativeString[units.length] = 0; 
  6. NSObject result = Class('NSString').perform( 
  7.     SEL('stringWithCharacters:length:'), 
  8.     args: [charPtr, units.length]); 
  9. free(charPtr); 

转换 Objective-C NSString 为 Dart String:

NSString 转为 UTF16 稍微麻烦一点。这里的方案是先转为 UTF16 的 NSData,然后将 uint16_t 数组的地址和字符长度(不是字节长度)返回给 Dart 侧。

  1. const void * 
  2. native_convert_nsstring_to_utf16(NSString *string, NSUInteger *length) { 
  3.     NSData *data = [string dataUsingEncoding:NSUTF16StringEncoding]; 
  4.     // UTF16, 2-byte per unit 
  5.     *length = data.length / 2; 
  6.     return data.bytes; 

Dart 拿到 uint16_t 数组后会转为 Uint16List 类型,并用它初始化一个 String 对象。

  1. Pointer<Uint64> length = allocate<Uint64>(); 
  2. Pointer<Void> result = convertNSStringToUTF16(ptr, length); 
  3. Uint16List list = result.cast<Uint16>().asTypedList(length.value); 
  4. free(length); 
  5. String str = String.fromCharCodes(list); 

后记

写了这么多 DartNative 的相关文章,终于轮到了介绍最基础最核心的同步调用功能。其实异步调用也是支持的,看来用 DartNative 来替换 Flutter Channel 的理由又多了。

这篇文章主要讲的是 iOS 的同步调用实现以及性能优化,Android 也已经实现同步调用中基本类型的自动转换。

责任编辑:未丽燕 来源: iOS开发
相关推荐

2009-10-20 16:48:30

C#委托

2024-02-20 01:53:01

ReactFlutter开发

2009-11-09 10:50:30

WCF异步调用

2021-01-28 11:40:34

Dubbo异步配置

2009-07-01 14:23:46

JavaScript异

2009-07-01 14:37:14

JavaScript异

2009-08-21 11:24:16

C#异步调用

2022-03-29 09:00:00

Angular框架REST API

2021-04-17 18:19:23

FlutterReact Nativ开发

2010-02-25 09:13:34

WCF异步调用

2022-07-01 08:14:28

Dubbo异步代码

2010-03-01 14:01:50

WCF服务异步调用

2022-09-27 12:01:56

Spring异步调用方式

2009-12-21 14:10:26

WCF异步调用

2023-02-09 07:15:52

开发FlutterReact

2022-09-28 14:54:07

Spring注解方式线程池

2023-11-30 09:18:27

2009-07-01 13:58:00

JavaScript异

2009-07-01 14:31:01

JavaScript异

2010-05-31 14:32:44

SVN自动同步
点赞
收藏

51CTO技术栈公众号