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 }
});