开启生长之旅!这是我参与「日新计划 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 的单向通讯才能进行扩展封装

内部由 NativeBridgeImplNativeBridgeHelperNativeBridge 三个类以及一个实体类 Message 构成,结构比较简单。

NativeBridge:实现原理解析

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() 办法调用,最终获取到咱们想要的值。

履行流程如下:

NativeBridge:实现原理解析

(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 的调用,最终介绍了增加超时机制的理由和详细完成。至此,咱们完成了整个插件的完成原理解析。