|
|
|
|
公众号矩阵

如何实现 Flutter 同步调用 Native API

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

作者:杨萧玉来源:iOS开发|2020-11-18 19:11

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 也已经实现同步调用中基本类型的自动转换。

【编辑推荐】

  1. 苹果解决了三个被频繁利用的iOS 0 day漏洞
  2. 苹果macOS史诗级进化:全新UI设计 可直接运行iOS App
  3. 苹果发布iOS 14.3后火速撤回:原因未知
  4. 效率王者!五大工具软件包助力高效Flutter开发
  5. 微信 7.0.18 又上线新功能,iOS 14.3 被攻破
【责任编辑:未丽燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

订阅专栏+更多

云原生架构实践

云原生架构实践

新技术引领移动互联网进入急速赛道
共3章 | KaliArch

29人订阅学习

数据中心和VPDN网络建设案例

数据中心和VPDN网络建设案例

漫画+案例
共20章 | 捷哥CCIE

193人订阅学习

搭建数据中心实验Lab

搭建数据中心实验Lab

实验平台Datacenter
共5章 | ITGO(老曾)

119人订阅学习

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO官微