开启生长之旅!这是我参与「日新计划 12 月更文应战」的第2天,点击查看活动概况
系列(一):NativeBridge:基于webivew_flutter的JSBridge插件
系列(二):NativeBridge:完成原理解析
系列(三):App完成JSBridge的最佳计划
系列(四):NativeBridge:我在Pub上发布的第一个插件
Tip: 现在 NativeBridge 插件已迭代更新多个版别,该文章源码为 Tag: v0.0.1
前沿
上一篇文章《NativeBridge:基于webivew_flutter的JSBridge插件》有讲到 NativeBridge 的集成和使用。抓住时机,咱们今日解析下 NativeBridge 的完成原理和超时检测机制。
webview_flutter 插件剖析
在说 NativeBridge 之前,咱们先了解下 webview_flutter,毕竟 NativeBridge 是对其才能的拓宽。
webview_flutter 是供给一个 WebView Widget 的 Flutter 插件。在 iOS 上,WebView Widget 由 WKWebView 支撑;在 Android 上,WebView Widget 由 WebView 支撑。官方原文:
A Flutter plugin that provides a WebView widget.
On iOS the WebView widget is backed by a WKWebView; On Android the WebView widget is backed by a WebView.
JSBridge 的调用。其经过 javascriptChannels 完成对 H5 端 JSBridge 调用的支撑。代码如下:
WebView(
...
javascriptMode: JavascriptMode.unrestricted,
javascriptChannels: <JavascriptChannel>{
_toasterJavascriptChannel(context),
},
);
它最大的限制在于 App 端与 H5 端之间的通讯只支撑单向通讯,无法直接获取另一端的回来值。如官方 example 中的完成:
// (1)经过 runJavascript 履行H5端办法,向App端发送 userAgent 信息
Future<void> _onShowUserAgent(
WebViewController controller, BuildContext context) async {
// Send a message with the user agent string to the Toaster JavaScript channel we registered
// with the WebView.
await controller.runJavascript(
'Toaster.postMessage("User Agent: " + navigator.userAgent);');
}
// (2)接收H5端的音讯
JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
return JavascriptChannel(
name: 'Toaster',
onMessageReceived: (JavascriptMessage message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message.message)),
);
});
}
NativeBridge 的源码剖析
NativeBridge 本质上是对 webview_flutter 的单向通讯才能进行扩展和封装。
内部由 NativeBridgeImpl、NativeBridgeHelper 和 NativeBridge 三个类以及一个实体类 Message 构成,结构比较简单。
Message实体类。首要界说了需求调用的办法 api、数据 data 和用于履行回来操作的标识 callbackId。
Message({
required this.api, // 办法API
this.data, // 发送的数据
this.callbackId, // 回来操作标识
});
NativeBridgeImpl。是一个抽象类,界说了一套需求用户完成的协议。包含:javascript 的 name、支撑的 callMethodMap 集合和履行 JS 的 runJavascript() 办法。
abstract class NativeBridgeImpl {
NativeBridgeImpl({required this.name, required this.callMethodMap});
/// javascript channel name
final String name;
/// call method map
final Map<String, Function?> callMethodMap;
/// 履行JS
void runJavascript(String javaScriptString);
}
NativeBridge。完成 WebView 的 JavascriptChannel 类用于 JSBridge 的初始化,其中 name 和 onMessageReceived 都是经过 NativeBridgeImpl 署理完成。
class NativeBridge implements JavascriptChannel {
NativeBridge({required this.controller});
final NativeBridgeImpl controller;
@override
String get name => controller.name;
@override
JavascriptMessageHandler get onMessageReceived => (message) async {
...
controller.runJavascript("receiveMessage($json)");
}
};
}
NativeBridgeHelper。望文生义是 NativeBridge 的帮助类,首要完成发送音讯和接受音讯的处理,并依据 callbackId 标识进行 Future 的回调。
/// 发送音讯
static Completer sendMessage(Message message, NativeBridgeImpl nativeBridgeImpl) {
Completer completer = Completer();
var callbackId = _pushCallback(message.api, completer);
message.callbackId = callbackId;
// H5接受音讯
final res = messageToJson(message);
nativeBridgeImpl.runJavascript("receiveMessage($res)");
return completer;
}
/// 接收音讯
static void receiveMessage(String json) {
var map = jsonDecode(json);
var callbackId = map["callbackId"];
var data = map["data"];
var completer = _popCallback(callbackId);
completer?.complete(Future.value(data));
}
NativeBridge 双向通讯的完成原理
经过 example 的例子可以看到,咱们可以直接经过 NativeBridgeHelper 获取到 H5 端的回来值。那么它是如何完成的呢?答案是 Completer 。
var isHome = await NativeBridgeHelper.sendMessage(
Message(api: "isHome"),
_nativeBridgeController,
).future;
Completer。简单来说就是生成一个异步的 Future,允许咱们在接受到 H5 端的音讯后进行异步回调。就像网络恳求相同,发送恳求后等候服务器响应恳求并回来数据。(Tip:咱们用到的 dio 网络库也是经过 Completer 来完成异步响应)
用上面的获取 H5 端的 isHome 值为例。咱们先发送一条需求获取 isHome 的音讯,然后异步等候 H5 端接收音讯并进行处理,H5 端接收到音讯后,依据收到的 api 回复一条带着回来值的音讯给 App 端,App 端再经过 callbackId 查询到前面缓存的 Completer 履行 complete() 办法调用,最终获取到咱们想要的值。
履行流程如下:
(1)App 端依据发送的音讯创立 Completer 并缓存到 Completer 栈,给 H5 端发送音讯。
// native_bridge_helper.dart sendMessage()
Completer completer = Completer();
var callbackId = _pushCallback(message.api, completer);
message.callbackId = callbackId;
// H5接受音讯
final res = messageToJson(message);
nativeBridgeImpl.runJavascript("receiveMessage($res)");
(2)H5 端接收 App 端的音讯,依据音讯的 api 回复一条音讯。
// jsBridgeHelper.js receiveMessage()
if (message.api === 'isHome') {
this._postMessage(message.api, true.toString(), message.callbackId)
}
(3)App 端接收到 H5 端的音讯后,依据 callbackId 获取到缓存的 Completer 并履行 complete() 完成回调。
// native_bridge_helper.dart receiveMessage()
var map = jsonDecode(json);
var callbackId = map["callbackId"];
var data = map["data"];
var completer = _popCallback(callbackId);
completer?.complete(Future.value(data));
至此,完成了从 App 端获取 H5 端值的整个流程。
NativeBridge 的超时机制
就像网络恳求相同,咱们不能让代码履行一向阻塞在获取回来值的位置上。因为单向发送音讯是不可靠的,或许存在音讯丢失,或许 H5 端不响应音讯的状况。因而咱们需求类似网络恳求相同,新增超时机制。
在 NativeBridge 的 sendMessage() 办法中经过设置倒计时,一旦超过倒计时还没有响应时,咱们就将当时的 Completer 取出并履行 complete(Future.value(null)) 表明没有获取到对应的值。
// 增加回调异常容错机制,防止音讯丢失导致一向阻塞
Future.delayed(const Duration(milliseconds: 200), (){
var completer = _popCallback(callbackId);
completer?.complete(Future.value(null));
});
对于 H5 端的完成
对于 H5 端的完成和 App 端同理,只是将 Completer 替换成了 Promise,其他逻辑和处理都相同。详细完成参考 example/assets/test/jsBridgeHelper.js
总结
咱们先经过剖析 webview_flutter 插件的完成和存在的缺陷,引申出 NativeBridge 的效果和源码构成,再经过 Completer 来完成异步 future 的调用,最终介绍了增加超时机制的理由和详细完成。至此,咱们完成了整个插件的完成原理解析。