共计 5642 个字符,预计需要花费 15 分钟才能阅读完成。
Flutter 是谷歌推出的挪动 UI 框架,能够疾速在 iOS 和 Android 上构建高质量的原生用户界面,被越来越多的开发者抉择和应用。拍乐云也提供了功能强大的 Pano Flutter SDK,性能稳固且易用,笼罩语音通话、视频通话、互动白板、互动直播、云端录制等各种性能。在之前的一篇《Pano Flutter SDK 全新公布》中,咱们给大家介绍了 SDK 的具体接入流程,明天将持续聊聊咱们 Pano Flutter SDK 的设计思路与实践经验。
1
总体构造
Pano 针对原生利用开发提供了齐备的高性能 SDK,所以 Pano Flutter SDK 采纳插件包的模式来封装咱们的 SDK。相似的,在 RN 中咱们采纳 NativeModule 来实现 Pano RN SDK。Pano 挪动端跨平台 SDK 的总体构造如下图所示:
SDK 分为三层构造,底层为 Pano 原生 SDK(iOS&Android)。基于原生 SDK 之上为桥接层,因为 Flutter 与 RN 中与原生层通信均为异步通信,且需应用特定的通信形式(Flutter 应用平台通道计划,RN 则应用原生模块计划),所以须要将跨平台调用进行转换能力调用原生 SDK 办法。因而桥接层将分为两个局部,原生 SDK 桥接与跨平台(Flutter&RN)桥接,以达到最大化代码复用的目标,将原生 SDK 接口二次封装成通用异步接口,在其上别离对接 Flutter 和 RN 的通信接口。SDK 最顶层则为跨平台层,对接原生层通信接口封装出 Flutter 或 RN 平台的性能接口。
尽管最终的构造比拟简洁明了,然而因为 Flutter 或 RN 对于视图更新机制与原生开发存在较大差别,以及跨平台层与原生层数据结构的不同等问题,导致 SDK 的设计与实现中存在许多波及数据转换、对象映射、内存治理等难点或坑点,接下来将联合 SDK 的设计思路与实践经验,针对其中几个典型的问题谈谈解决方案或须要留神的中央。
2
工作流程
Pano Flutter SDK 提供的 API 基本上与原生 SDK 放弃一一对应的关系,以便开发人员能够轻松的将对接原生 SDK 开发教训利用到 Flutter 中。但因为 Flutter 非凡的平台通道(Platform Channel)计划以及视图更新机制,所以并不是简略的将原生 SDK 接口进行透传封装,SDK 的调用流程如下图所示:
SDK 应用 Flutter 平台通道中 MethodChannel 与 EventChannel 来实现 Flutter 层与原生层通信,其中 MethodChannel 用于 Flutter 向原生层办法调用,EventChannel 则用于原生层向 Flutter 层进行数据流通信,这里次要是传递原生层回调音讯。当开发者调用 Flutter 层接口,SDK 应用对应的 MethodChannel 将办法名、参数传递到原生层,SDK 在这里实现了 Flutter Native Bridge 来专门解决这些调用。
倡议:当原生层接管到 MethodChannel 的办法调用时(例如:iOS 为 -[FlutterPlugin handleMethodCall:result:]),采纳反射调用(例如:iOS 中应用 NSSelectorFromString 获取 selector,而后通过 [NSObject performSelector:withObject:] 调用)Native SDK Bridge 办法,这样能够尽量将 Flutter 的逻辑与原生桥接层逻辑隔离,一方面做薄对接 Flutter 层逻辑,另一方面将须要常常追随原生 SDK 变动的原生桥接层逻辑与其它跨平台框架(如 RN)进行复用,缩小保护老本。
留神:Flutter 中没有现成的二进制数据类型,通常采纳 Uint8List 来代替,但通过平台通道转换后,在 iOS 端会转换成 FlutterStandardTypedData 类型,该类型不能主动转换为 NSData 类型,须要通过其属性 data 来获取理论的 NSData 对象。但在从原生层调用 Flutter 层时,能够间接传递 NSData 对象,其将会在 Flutter 层被主动转换为 Uint8List。
Flutter 中平台通道实际上是将传递的数据编码成音讯的模式,跨线程发送到该利用所在的宿主原生层。并且 Native SDK Bridge 对接原生 SDK,将原生 SDK 办法履行结束后的返回值通过 callback 返回时,也是将数据编码成音讯通过同样形式原路返回给 Flutter 层。整个过程的音讯和响应是异步的,这也就是 Flutter 层接口都设计成异步接口的起因。
留神:MethodChannel 类型中,调用原生办法应用 Future<T?> invokeMethod<T>(String method, [ dynamic arguments]),对于 SDK 返回 Flutter 反对的根本类型数据时,间接调用没有任何问题,例如当获取 SDK 版本号接口返回 String 类型,则 Flutter 层接口能够实现为:static Future<String> getSdkVersion() {
// iOS 中 NSString 和 Andorid 中 java.lang.String 都能够主动转换为 Flutter 的 String 类型
return _methodChannel.invokeMethod(‘getSdkVersion’);
}
但当返回非根本类型时,返回值就须要进行转换,例如开启音频接口因为可能有多种后果,所以返回值是枚举类型 ResultCode,如果间接依照以下写法实现将会报错:Future<ResultCode> startAudio() {
return _methodChannel.invokeMethod(‘startAudio’);// 谬误:返回值为 int 不会主动转换 Flutter 的枚举类型
}
须要减少转换逻辑,例如:Future<ResultCode> startAudio() {
return _methodChannel.invokeMethod(‘startAudio’).then((value) {
return ResultCodeConverter.fromValue(value).e as T; // ResultCodeConverter 为将 int 转换 ResultCode 的工具类
});
}
倡议:因为 SDK 中存在大量的返回 ResultCode 的办法,在每个接口实现处都减少转换代码繁琐且冗余,所以咱们对于这种状况能够提取一个公共模板办法,能很大水平晋升代码简洁度,例如:Future<T>_invokeMethod<T>(String method, [Map<String, dynamic> arguments]) {
if (T == ResultCode) {// 判断以后范型为 ResultCode 时,减少转换逻辑
return _methodChannel.invokeMethod(method, arguments).then((value) {
return ResultCodeConverter.fromValue(value).e as T;
});
} else {// 其余能够主动转换的状况则返回调用后果
return _methodChannel.invokeMethod(method, arguments);
}
}
以上是 Flutter 调用原生层的流程,那当原生层须要回调事件给 Flutter 层咱们应该怎么做呢?这时就须要利用 EventChannel 来实现。先看下 EventChannel 的根本流程:
原生层调用 setStreamHandler(iOS 为 -[FlutterEventChannel setStreamHandler:])注册 Handler 实现;
EventChannel 初始化实现后,通过 StreamHandler 的 onListen(iOS 为 -[FlutterStreamHandler onListenWithArguments:eventSink:])回调接口获取 eventSink 援用并保留;
Flutter 层调用 EventChannel 的 receiveBroadcastStream 注册监听;
原生层通过调用 eventSink 发送事件音讯。
倡议:EventChannel 因为是数据流通信,跟 MethodChannel 不同之处在于没有封装出针对办法回调的模型,但目前 SDK 中原生层向 Flutter 层均为办法回调,所以咱们将回调数据组装成特定格局的键值对,如:{
“methodName”: xxxx, // 回调办法名
“data”: [xxxx,xxxx…] // 回调参数列表
}
而后在 Flutter 层进行对立解析解决:void setEventHandler(RtcEngineEventHandler handler) {
_handler = handler;
…
_eventChannel.receiveBroadcastStream().listen((event) {
final eventMap = Map<dynamic, dynamic>.from(event);
final methodName = eventMap[‘methodName’] as String;
final data = List<dynamic>.from(eventMap[‘data’]);
_handler?.process(methodName, data);
});
}
至此通过以上计划,曾经能够封装原生 SDK 的绝大部分性能以 Flutter SDK 模式提供进来了。但还剩一个重要的问题须要解决,那就是如何设置原生层视图的逻辑。
3
设置原生视图
因为 Flutter 提供的平台通道计划实质上是以字节流的形式在线程间传递数据,所以对于原生层视图等非序列化的对象是不反对的。Flutter 在如何内嵌原生层视图的问题上,提供了平台视图(Platform-views)计划,开发者能够在 Flutter 层创立原生视图的映射(iOS 为 UiKitView,Android 为 AndroidView),并嵌入到 Widget 中。
那如何将生成的原生视图对象传递给原生层 SDK?在 Flutter 创立原生视图后,会返回视图对应惟一的 id,所以最直观的办法就是在 id 返回后,别离在原生层与 Flutter 层生成对应的 MethodChannel,组成键值对缓存起来,在调用时通过 id 查找 MethodChannel,而后通过 MethodChannel 传递办法调用音讯。但这样做有两个显著缺点:
MethodChannel 没有与 Widget 间接关联,在 Widget 销毁时须要手动革除键值对中的 MethodChannel;
采纳 id 作为原生视图的标识,因为短少有效性查看,可能导致调用到有效 MethodChannel 抛出异样。
并且通常原生 SDK 办法中是须要原生视图作为参数传入,但因为只能通过与视图对应的 MethodChannel 能力在原生层拜访到对应的原生视图对象,导致没法间接在 Flutter 层设计出相似原生 SDK 的办法。
倡议:Pano Flutter SDK 中咱们为了尽量放弃与原生 SDK 的接口一致性,采取了一种曲线救国的计划。在创立渲染视图 RtcSurfaceView(StatefulWidget)后,回调返回保留了 MethodChannel 的 ViewModel 对象:
class RtcSurfaceViewModel {
final MethodChannel _methodChannel;
Future<T> invokeMethod<T>(String method, [Map<String, dynamic> arguments]) {
if (T == ResultCode) {
return _methodChannel.invokeMethod(method, arguments).then((value) {
return ResultCodeConverter.fromValue(value).e as T;
});
} else {
return _methodChannel.invokeMethod(method, arguments);
}
}
RtcSurfaceViewModel(this._methodChannel);
}
而后依照须要原生视图的 SDK 办法,定义出对应的 Flutter 层接口,接管 ViewModel 作为参数,办法实现调用 ViewModel 的 MethodChannel 传递办法音讯,例如开启视频时调用 startVideo 接口定义如下:Future<ResultCode> startVideo(RtcSurfaceViewModel viewModel,
{RtcRenderConfig config}) {
config ??= RtcRenderConfig();
return viewModel.invokeMethod(‘startVideo’, {‘config’: config.toJson()});
}
在原生层视图对应的 MethodChannel 接管到办法调用,通过原生层外部缓存的 engine 对象,调用对应的 SDK 办法(如 startVideo),传入原生层视图实现接口调用。这样做,一方面让 MethodChannel 与 Widget 关联,另一方面在接口调用上也应用 ViewModel 对象保障了传值的有效性。并且接口上也根本与原生 SDK 放弃了一致性,升高了对接 SDK 的开发人员的了解老本,也兼顾了代码的保护老本。
4
结语
现今宽广开发往往会遇到各种各样跨平台开发的需要或问题,而拍乐云始终以来保持以开发者为先,和用户在一起。Pano Flutter SDK 全副开源,你能够通过 GitHub(https://github.com/PanoVideo/…)或 Gitee(https://gitee.com/pano-video/…)查看残缺源码。通过本篇介绍 Pano Flutter SDK 的跨平台 SDK 设计与实践经验,心愿能给大家带来一些帮忙与启发。粗体 粗体 粗体