写点什么

如何在 Flutter 和 JavaScript 之间创建通信桥?

  • 2020-12-31
  • 本文字数:4172 字

    阅读完需:约 14 分钟

如何在 Flutter 和 JavaScript 之间创建通信桥?

我在之前的一篇文章中解释了如何在 Android 和 iOS 中创建通信桥,作为后续,我认为解释一下如何在 Flutter 中创建通信桥也是一个不错的想法。虽然这可能看起来是一件很简单的事情,但你很快就会意识到,要使这个功能正常工作需要一些工作。


首先,重要的是意识到(在撰写本文时)Flutter 还没有内置对嵌入式 WebView 的支持。这意味着,在 Kotlin 或 Swift 中的本地应用程序中你可以实例化一个 WebView 组件,而在 Flutter 中你不能直接将 WebView 组件添加到你的应用程序中。


在创建一个新的 Flutter 项目后,我们需要使用webview_flutter包来使得能够使用 WebView。我们会向 pubspec.yaml 文件中添加依赖:


dependencies:  flutter:    sdk: flutter  webview_flutter: ^1.0.7
复制代码


然后,我们需要运行 Pub get 或者在终端中:


flutter pub get
复制代码


然后,我们需要在 main.dart 文件中导入这个包:


import 'package:webview_flutter/webview_flutter.dart';
复制代码


如果你还没有清理初始项目的代码,现在可以着手清理了。在你删除所有的注释、浮动操作按钮以及与之相关的所有内容之后,你就会剩下以下内容(为了展示,我添加了一个文本部件):


import 'dart:convert';

import 'package:flutter/material.dart';import 'package:webview_flutter/webview_flutter.dart';

void main() { runApp(MyApp());}

class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Communication Bridge', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(title: 'Native - JS Communication Bridge'), ); }}

class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key);

final String title;

@override _MyHomePageState createState() => _MyHomePageState();}

class _MyHomePageState extends State<MyHomePage> {

WebViewController _controller;

@override Widget build(BuildContext context) { return Text( "Flutter JS-Native Communication Bridge"
复制代码


这段代码的显示结果如下:

添加本地文件


因为我们将使用一个嵌有 JavaScript 代码的本地 html 文件,我们需要在项目中创建它。Flutter 应用程序中的所有本地资源都需要存放在一个 assets 目录中。通过右键点击左侧面板,然后选择新建->目录,来在你的主项目层创建一个 assets 目录。这个目录需要是 android 目录的一个同级目录。



然后,继续在 assets 目录中创建 index.html 文件。


<html>

<head> <title>My Local HTML File</title> </head>

<body> <h1 id="title">Hello World!</h1> <script type="text/javascript"> function fromFlutter(newTitle) { document.getElementById("title").innerHTML = newTitle; sendBack(); }

function sendBack() { messageHandler.postMessage("Hello from JS"); } </script> </body></htm
复制代码


你会注意到,我们在 html 文件的 JavaScript 部分写了 2 个方法:


  1. fromFlutter - 我们从 flutter 调用这个方法,用一个字符串参数表示页面新标题

  2. sendBack - 我们会调用这个方法来与 Flutter 通信。在这个方法中,我们会发送一个字符串消息。


稍后,我们将看看 sendBack 的内容,在那之前,我们需要先在我们的应用程序中设置好 WebView。


别忘了在 pubspec.yaml 中的 assets 部分增加 index.html(使用正确的缩进)


dependencies:  flutter:    sdk: flutter  webview_flutter: ^1.0.7  cupertino_icons: ^1.0.0

dev_dependencies: flutter_test: sdk: flutter flutter: uses-material-design: true assets: - assets/index.html
复制代码


设置 WebView


由于我们已经将包导入了 main.dart 文件,因此需要将文本部件替换为一个 WebView 部件。


class _MyHomePageState extends State<MyHomePage> {

WebViewController _controller;

@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Webview')), body: WebView( initialUrl: 'about:blank', onWebViewCreated: (WebViewController webviewController) { _controller = webviewController; _loadHtmlFromAssets(); }, ), ); }

_loadHtmlFromAssets() async { String file = await rootBundle.loadString('assets/index.html'); _controller.loadUrl(Uri.dataFromString( file, mimeType: 'text/html', encoding: Encoding.getByName('utf-8')).toString()); }
复制代码


我们用一个 Scaffold 部件包裹 WebView(它的用途将在本文后面介绍),但是我们可以关注上面看到的 WebView 部件的不同字段:


  • initialUrl - 用来定义 WebView 指向哪里。这里我们决定将它指向无,因为我们将加载本地 html 文件。

  • onWebViewCreated - 一旦 WebView 被创建,我们将从包得到的一个回调。因为我们要保存从这个回调中获取的控制器实例,所以我们创建了一个私有成员(_controller)来存储它。


你还会注意到,我们创建了一个名为_loadHtmlFromAssets 的方法,顾名思义,它会将我们的本地 html 文件加载到 WebView 中


在这个方法中,我们使用我们的私有 WebViewController 实例,_controller,以及它的公开方法 loadUrl 来加载我们的本地 html 文件。由于这个方法中的逻辑,它的执行是异步的。


如果我们运行我们的应用程序,显示如下:



通信(Flutter -> WebView)


现在,让我们添加一些功能来调用我们在本地 html 文件中定义的 fromFlutter 方法。为此,我们将向我们的布局中增加一个浮动操作按钮(Floating Action Button,FAB),其 onPressed 方法调用 fromFlutter 方法。这也是使用 Scaffold 部件的背后原因,这样我们可以很容易地添加一个 FAB。


@override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(title: Text('Webview')),      body: WebView(        initialUrl: 'about:blank',        javascriptMode: JavascriptMode.unrestricted,        onWebViewCreated: (WebViewController webviewController) {          _controller = webviewController;          _loadHtmlFromAssets();        },      ),      floatingActionButton: FloatingActionButton(        child: const Icon(Icons.arrow_upward),        onPressed: () {          _controller.evaluateJavascript('fromFlutter("From Flutter")');        },      ),    );  }
复制代码


为了从 Flutter 向我们加载的 html 发起调用,我们使用 evaluateJavascript 方法。为了能使用这个方法,我们必须向我们的 WebView 增加另外一个属性,即 javascriptMode。上面,我们将这个属性设为 unrestricted(无限制的)。如果我们不设置它,我们就不能在 Flutter 和 WebView 之间通信。


反向通信(WebView -> Flutter)


还记得前面说过要讨论 senBack 方法的内容吗?现在是时候来看看了。


function sendBack() {  messageHandler.postMessage("Hello from JS");}
复制代码


在 sendBack 方法中,我们使用了一个称为 messageHandler 的对象和一个名为 postMessage 的附加方法。如果你在原生应用程序中创建过通信桥,你就会意识到,一旦你设置了一个通信桥,你就会向 Javascript 层的全局 window 对象添加一个对象用于通信。你可以随意给这个对象命名,只要你从 Javascript 向你的原生应用程序发起调用时使用那个名字就可以。


如何将这个对象添加到我们应用程序中的 Javascript 层呢?通过向我们的 WebView 部件添加一个 JavascriptChannels 属性:


class _MyHomePageState extends State<MyHomePage> {

WebViewController _controller; final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();

@override Widget build(BuildContext context) { return Scaffold( key: _scaffoldKey, appBar: AppBar(title: Text('Webview')), body: WebView( initialUrl: 'about:blank', javascriptMode: JavascriptMode.unrestricted, javascriptChannels: Set.from([ JavascriptChannel( name: 'messageHandler', onMessageReceived: (JavascriptMessage message) { _scaffoldKey.currentState.showSnackBar( SnackBar( content: Text(message) ) ); }) ]), onWebViewCreated: (WebViewController webviewController) { _controller = webviewController; _loadHtmlFromAssets(); }, ), floatingActionButton: FloatingActionButton( child: const Icon(Icons.arrow_upward), onPressed: () { _controller.evaluateJavascript('fromFlutter("From Flutter")'); }, ), );

复制代码


我们已经定义了一个 JavascriptChannel 和一个 onMessageReceived 处理器。我们给这个通道的命名是,messageHandler,我们用它来从我们加载的本地 html 文件向我们的原生层通信。



对于目光敏锐的人,你可以已经注意到新增了一个私有变量,_scaffoldKey。这是因为我们需要给我们的 Scaffold 部件增加一个主键,以便可以显示 Snackbar。


你可以从这里链接获取到本文描述的应用程序的源代码。


最后要注意的两点:


  1. webview_flutter包中的alert方法已经损坏

  2. 为了在 iOS 中使用这个包,你必须将如下主键添加到你的 info.plist 文件中:


<key>io.flutter.embedded_views_preview</key><string>yes</string>
复制代码


如果你想要了解更多关于 Flutter 和 WebViews 的信息,你可以看看这两个有用的资源:



原文链接


How To Create a Communication Bridge Between Flutter And JavaScript


2020-12-31 15:414211

评论

发布
暂无评论
发现更多内容

实用机器学习笔记二十四:模型调参

打工人!

学习笔记 机器学习算法 3月月更 模型调参

程序员硬核测评:全方位测评 GaussDB(for Redis) 和开源 Redis

华为云数据库小助手

性能分析 GaussDB GaussDB ( for Redis ) 华为云数据库 开源Redis

东方电机打造大国重器 携手天翼云挺起智能制造新脊梁 智能制造的力量 东方电机的数字化前行之路

天翼云开发者社区

【51单片机】点亮LED灯(四种形式)

謓泽

3月月更

VuePress 博客优化之增加 Valine 评论功能

冴羽

JavaScript 前端 vuepress 博客搭建 Alogia

华为SmartCare和AUTIN品牌升级:助力运营商走出5G发展迷宫

白洞计划

在充满挑战的时代天翼云以数字化加速市域社会治理现代化

天翼云开发者社区

操作系统面试总结

yuexin_tech

操作系统

以数字化为引领天翼云助力中安公司应急管理云平台上线

天翼云开发者社区

如何打造极速数据湖分析引擎

StarRocks

数据库 数据分析 StarRocks

写给20几岁的程序员

shellc

财富 35岁

URL重定向及跳转漏洞

喀拉峻

网络安全

DNA to Face,会是寻人缉凶的一颗“银弹”吗?

脑极体

Go实现并发聚合,批量提交功能

人生如梦

Go 并发 请求聚合

Python 获取字典中的第一个键

AlwaysBeta

Python 程序员 编程语言 字典

电商秒杀系统架构设计文档

皓月

「架构实战营」

翻译 | 解读首部 Kubernetes 纪录片

RadonDB

开源 Kubernetes RadonDB

基于云效代码管理的源码漏洞检测是怎样的?

阿里云云效

阿里云 代码 代码管理 源代码 Codeup

java高级用法之:调用本地方法的利器JNA

程序那些事

Java nio 程序那些事 3月月更

Linux中如何使用Htop监控工具?【网络安全】

H

Linux 网络安全

毕业总结

皓月

「架构实战营」

39 个 Java 集合框架面试题

逆锋起笔

java面试 死磕 Java 基础 3月月更 Java集合框架

产品帮助中心的制作方法及工具推荐

小炮

科技创新力强,旺链科技创始人刘涛荣登G60科创走廊创业榜单

旺链科技

区块链 产业区块链 创新创业

不为人知的网络编程(十四):拔掉网线再插上,TCP连接还在吗?一文即懂!

JackJiang

网络编程 TCP/IP TCP协议 即时通讯IM

恒源云(GpuShare)_医学图像分割:MT-UNet

恒源云

深度学习 机器翻译 transform

毕业总结

whoami

架构训练营

知识管理的成功案例大赏

小炮

人脸识别闸机惊艳美国运动员背后的黑科技是如何实现的?

天翼云开发者社区

用 vite 2 平滑升级 vue 2 + webpack 项目实战

CRMEB

重点操作之上线、恢复、部署 - 大屏云极简使用手册

shulinwu

如何在 Flutter 和 JavaScript 之间创建通信桥?_大前端_tomerpacific_InfoQ精选文章