Flutter支持webview并实现双向通信

在 flutter 项目开发中,避免不了在 app 中会使用一些 H5页面,所以需要在 app 中嵌入 webview;

安装依赖

首先要在项目中安装 webview_flutter 插件,我这里安装的版本是:

webview_flutter: ^4.0.2

添加 Bridge

集成 webview 可以查看官方文档案例(此处就不单独再说明)。
创建一个 WebViewController,注意这个对象每个 webview 窗口是独立的,打开了两个网页就是两个 controller,所以数据不会互通,它可以控制 webview 的所有细节,例如导航、通信等等。

// 这里是创建controller的简单例子,具体详细参数查看官方文档
final controller =  WebViewController.fromPlatformCreationParams(params);

通过给 controller 添加 JavaScriptChannel 来处理消息和发送消息,该方法为 webview 中打开的网页添加一个全局对象(这个名称是可以自定义的),例如下面的代码:

controller.addJavaScriptChannel(
    // 会在H5的window上挂载一个FlutterBridge对象,该对象包含一个postMessage方法发送数据给flutter
    "FlutterBridge",
    onMessageReceived: (JavaScriptMessage message) {  
      // 处理H5发来的消息以及数据响应  
      // message 仅仅包含一个字符串类型的字段,就是 message.message
    }
);

我们可以在 onMessageReceived 中去解析 message,并做出一些响应,但是目前来看是单向的数据流通,如果我们想在 flutter 处理消息之后给网页一个结果,我们就要做一些其他的处理了;

消息处理

首先我们顶一个通用的消息 JSON 结构,当然这个结构按照自己的需求和喜好去自定义即可,以下面为例子:

import 'dart:convert';

class JsMessageData {
  const JsMessageData(this.type, this.params, this.callback);
  // 事件类型,一般这个是方法的名称
  final String type;
  // 参数
  final Map<String, dynamic> params;
  // 回调函数名,如果网页需要返回结果就需要传递这个函数
  final String callback;
  // fromJson, 输入是string
  factory JsMessageData.fromJson(String jsonStr) {
    final json = jsonDecode(jsonStr);
    return JsMessageData(
      json["type"],
      json["params"] ?? {},
      json["callback"] ?? "",
    );
  }
}

我们在下面尝试来实现 onMessageReceived 方法,来处理一下消息;

// 这里的逻辑可以抽取到单独的模块中进行维护
FutureOr hello(name: string) {
    return { "hello": name };
}
final onMessageReceived = (JavaScriptMessage message) async {
    // 这里解析出来真实的消息对象
    final data = JsMessageData.fromJson(message.message);
    // 获取js传入的回调函数名称,这里很像jsonp的实现
    final callbackName = data.callback;
    // 定义返回的结果
    dynamic result = {};
    // 执行flutter中定义的方法
    if (data.type == 'hello') {
        result = await hello(data.params["name"]);
    }
    // 判断是否有回调函数,有的话就回调结果给网页调用者
    if (callbackName.isNotEmpty) {
        // 最终还是通过JSON的方式返回给H5
        controller.runJavaScript("$callbackName(${jsonEncode(result)})");
    }
}

H5中调用

接下来我们在 H5中尝试调用,因为 flutter 接受到的消息是字符串类型,所以我们将要传输的数据转换成 JSON 字符串后再传递给 flutter,然后在 flutter 端再解析出来。

interface IMessage {  
  type: string;  // 消息类型
  params?: Record<string, any>;  
}
// 定义Bridge
class Bridge {
    async sendWithResult(message: IMessage): Promise<any> {  
      return new Promise(resolve => {  
        // 随机生成一个函数名称  
        const callbackName = `callback_${Math.floor(  
          Math.random() * 100000  
        )}`;  
        // 将函数挂载到window上  
        window[callbackName] = (data: any) => {  
          // 执行回调  
          resolve(data);  
          // 执行完毕后删除  
          delete window[callbackName];  
        };  
        window.FlutterBridge?.postMessage(  
          JSON.stringify({  
            ...message,  
            callback: `window.${callbackName}`  
          })  
        );  
      });  
    }
}

// 业务中使用
const bridge = new Bridge();
bridge.sendWithResult({
    type: 'hello',
    params: {
        name: 'zhangsan'
    }
}).then(resp => {
    // resp => { "hello": name }
});

留下回复