實操|在 Flutter 中創建通信橋

2021-01-06 InfoQ技術實驗室

我在之前的一篇文章中解釋了如何在 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 個方法:

fromFlutter - 我們從flutter調用這個方法,用一個字符串參數表示頁面新標題sendBack - 我們會調用這個方法來與Flutter通信。在這個方法中,我們會發送一個字符串消息。稍後,我們將看看 sendBack 的內容,在那之前,我們需要先在我們的應用程式中設置好 WebView。

別忘了在pubspec.yaml中的assets部分增加index.html(使用正確的縮進)

dependencies: flutter: sdk: flutter webview_flutter: ^1.0.7 cupertino_icons: ^1.0.0dev_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。

你可以從這裡連結獲取到本文描述的應用程式的原始碼。

最後要注意的兩點:

webview_flutter包中的alert方法已經損壞為了在iOS中使用這個包,你必須將如下主鍵添加到你的info.plist文件中:<key>io.flutter.embedded_views_preview</key><string>yes</string>如果你想要了解更多關於 Flutter 和 WebViews 的信息,你可以看看這兩個有用的資源:

The Power Of WebViews In FlutterWebView_Flutter Package原文連結

How To Create a Communication Bridge Between Flutter And JavaScript

延伸閱讀:

Vue.js最佳靜態站點生成器對比-InfoQ

關注我並轉發此篇文章,即可獲得學習資料~若想了解更多,也可移步InfoQ官網,獲取InfoQ最新資訊~

相關焦點

  • Flutter 混合開發與原生通信MethodChannel
    端主動調用,也可以Flutter主動調用,屬於雙向通信。此方式為最常用的方式, Native 端調用需要在主線程中執行。BasicMessageChannel:用於使用指定的編解碼器對消息進行編碼和解碼,屬於雙向通信,可以 Native 端主動調用,也可以Flutter主動調用。
  • 一篇看懂Android與Flutter之間的通信
    Flutter作為一種跨平臺解決方案,經常會作為一個模塊嵌入到原生Android與iOS應用中,Flutter與Android原生端的通信必不可少。所以本文就來講述一下Android如何與flutter進行通信。
  • Flutter中嵌套Android布局
    本文具體demo效果如下:開發首先創建flutter項目,在項目中定義好flutter需要展示布局: @override Widget build(BuildContext context創建Android中的布局: <io.flutter.embedding.android.FlutterView android:id="@+id/flutter_view" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight
  • 創建並運行自己的第一個 Flutter項目
    最近在學習flutter,畢竟是第一次運行flutter項目,中間遇到很多問題,今天就把自己如何創建並運行flutter項目,及創建的過程中遇到的一些問題都總結出來,方便後來人查看。先來講講flutter的下載與安裝。
  • Flutter與Android iOS 的雙向通信
    Flutter 與 Android iOS 原生的通信有以下三種方式 BasicMessageChannel 實現 Flutter 與 原生(Android 、iOS)雙向通信MethodChannel 實現 Flutter 與 原生原生(Android 、iOS)雙向通信
  • 使用 Flutter 創建 App
    使用 snapd 安裝要使用 Snapd 在 Ubuntu 或類似發行版上安裝 Flutter,請在終端中輸入以下內容:$ sudo snap install flutter --classic$ sudo snap install flutter –classicflutter
  • 在 Flutter 中使用 TFLite 插件實現文字分類
    本文中,我們將使用 tflite_flutter 構建一個文字分類 Flutter 應用,帶您體驗 tflite_flutter 插件。首先從新建一個 Flutter 項目text_classification_app開始。
  • Flutter篇:跨平臺技術詳解篇
    目前混合開發框架的典型代表有:Cordova、Ionic和微信小程序混合開發的技術點就是:JSBridge,WebView是JavaScript與原生API之間通信的橋梁,主要負責JavaScript與原生之間傳遞調用消息,而消息的傳遞必須遵守一個標準的協議,它規定了消息的格式與含義,我們把依賴於WebView的用於在JavaScript與原生之間通信並實現了某種消息傳輸協議的工具稱之為
  • 在Flutter中使用一個全新插件高效且靈活實現文字分類
    本文中,我們將使用 tflite_flutter 構建一個 文字分類 Flutter 應用 帶您體驗 tflite_flutter 插件,首先從新建一個 Flutter 項目 text_classification_app 開始。
  • flutter中dio網絡get請求使用總結
    1 添加依賴可訪問flutter國內倉庫https://pub.flutter-io.cn/packages/diodio用來在flutter跨平臺開發中訪問網絡的框架,在使用的時候,我們首先是引入依賴
  • Flutter 二維碼掃描插件
    : ^1.0.1導入頭文件 import 'package:flutter_plugin_qrcode/flutter_plugin_qrcode.dart';'dart:async';import 'package:flutter/services.dart';import 'package:flutter_plugin_qrcode/flutter_plugin_qrcode.dart';void main() => runApp(MyApp());class MyApp extends StatefulWidget {
  • flutter中dio網絡get請求使用總結
    1 添加依賴可訪問flutter國內倉庫https://pub.flutter-io.cn/packages/diodio用來在flutter跨平臺開發中訪問網絡的框架,在使用的時候,我們首先是引入依賴dependencies: dio
  • 谷歌的flutter大賽-Flutter Clock競賽
    使用flutter開發創建漂亮的錶盤界面,使用Flutter為Lenovo Smart Clock構建漂亮的錶盤UI,有機會贏得iMac Pro,Lenovo Smart Display或Lenovo Smart Clock。所有項目必須在太平洋標準時間2020年1月20日晚上11:59(GMT-8)之前提交。
  • Flutter UiKitView 嵌入iOS原生View
    本篇文章 中寫到的是 flutter 通過 UiKitView 調用了ios 原生的 UILabel 案例。flutter 中嵌套使用ios原生組件的流程基本上可以描述為:1 info.plist文件設置2 ios 端實現原生組件PlatformView提供原生view3 ios 端創建PlatformViewFactory
  • Flutter 中文文檔:Widget 測試介紹
    dev_dependencies:  flutter_test:    sdk: flutter2. 創建一個測試用的 Widget接下來,我們需要創建一個可以測試的 widget!在此例中,我們創建了一個 widget 顯示一個標題和信息。
  • Flutter和桌面應用的最新進展
    例如,如果您運行的是最新版 Flutter,現在可以使用 flutter build windows 命令,將 Flutter 應用編譯為 Windows 可執行文件。此操作使用我們在生產環境中的 AOT 編譯器來創建原生 x64 機器代碼,而這些代碼可分發至那些未安裝 Flutter 的設備上。
  • 為什麼我們應該使用 Flutter?環信Flutter SDK初體驗
    感興趣的同學可以關注 GitHub:https://github.com/flutter/flutterFlutter 的優勢相比較目前的混合開發方案,Flutter 提供了大量的文檔,能非常快速且友好的讓你加入到這個大家庭。
  • Flutter教程從零構建電商應用(一)
    在這個系列中,我們將學習如何使用google的移動開發框架flutter創建一個電商應用。本文是flutter框架系列教程的第一部分,將學習如何安裝Flutter開發環境並創建第一個Flutter應用,並學習Flutter應用開發中的核心概念,例如widget、狀態等。
  • 在 macOS 上安裝和配置 Flutter 開發環境
    >通過運行以下命令來驗證 flutter/bin 文件夾是否已經添加到 PATH 環境變量中:echo $PATH驗證 flutter 命令是否可用,可以執行下面的命令檢測:which flutter配置 iOS 模擬器open -a Simulator創建並運行一個簡單的 Flutter 應用通過運行以下命令來創建一個新的 Flutter 應用:
  • Flutter框架的安裝與環境配置
    C) 配置環境變量選擇剛剛解壓的flutter目錄,找到bin目錄並進入,然後複製地址欄中的路徑;然後右鍵我的電腦選擇屬性-》高級系統設置-》環境變量-》在系統變量中選擇Path,新建一條變量添加路徑D) 測試是否添加成功Win+R輸入cmd