突然插播:Flutter與JS互調
這是我參與更文挑戰的第9天,活動詳情檢視: 更文挑戰
所以要一鍋端了?
很順利的解決了iOS端,Swift與H5的互動問題,公司覺得你這光解決iOS的不行啊,最好是寫一個通用的WebView殼子,能滿足日常的iOS與Android雙端WebView載入H5除錯。
這不是要一鍋端的節奏?
既然是雙端除錯,那麼必然會想到使用跨平臺方案來寫這個殼子,基於技術背景,Flutter當仁不讓的成為了第一選擇,而選擇使用的Flutter中的WebView外掛就是webview_flutter!
別說,我為了這個突然的需求,也反反覆覆看了webview_flutter的官方文件和程式碼,自己寫了Demo來驗證。
簡單介紹一下webview_flutter外掛
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.
一個為Flutter提供WebView元件的外掛。
在iOS端是基於原生的WKWebView,而在Android則是基於系統的WebView。
從這段簡介可以看出,雖然這是一個Flutter外掛,但是實際上橋接的都是基於iOS與Android平臺系統層面的WebView元件,這樣做的好處就是在每一端都做到使用原生,只要中間通訊層邏輯一致並完好,那麼就可以展平了雙端WebView中H5與Flutter通訊的能力。
Flutter與JS互調使用與講解
在這裡我們先精簡一下官方給出的example例子。
``` import 'dart:async'; import 'dart:convert'; import 'dart:io';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebViewExample extends StatefulWidget { @override _WebViewExampleState createState() => _WebViewExampleState(); }
class _WebViewExampleState extends State
@override void initState() { super.initState(); if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView(); }
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter WebView example'),
),
// We're using a Builder here so we have a context that is below the Scaffold
// to allow calling Scaffold.of(context) so we can show a snackbar.
body: Builder(builder: (BuildContext context) {
return WebView(
initialUrl: 'http://www.baidu.com',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
onProgress: (int progress) {
print("WebView is loading (progress : $progress%)");
},
javascriptChannels:
JavascriptChannel _toasterJavascriptChannel(BuildContext context) { return JavascriptChannel( name: 'SeasonCallback', onMessageReceived: (JavascriptMessage message) { // ignore: deprecated_member_use Scaffold.of(context).showSnackBar( SnackBar(content: Text(message.message)), ); }); } } ```
這段程式碼最為核心的部分就是以下這段,請詳細看看我寫的註釋,詳細又重要!!!
如果在網頁裡面看不懂的,可以考慮下載webview_flutter的程式碼,一點點對照的看,並與前兩篇我文章。
return WebView(
/// 需要載入的網頁URL
initialUrl: 'http://www.baidu.com',
/// JS的模式
javascriptMode: JavascriptMode.unrestricted,
/// WebView生成成功時,會回撥一個webViewController,它非常的重要,後面會繼續說
onWebViewCreated: (WebViewController webViewController) {
/// 接受該控制器
_controller.complete(webViewController);
},
/// webView載入的回撥進度,相當於Swift中對WKWebView中的estimatedProgress的KVO
onProgress: (int progress) {
print("WebView is loading (progress : $progress%)");
},
/// JS通道,這裡相當於Flutter側監聽JS方法的集合,和Swift中通過userContentController註冊監聽JS控制代碼一致
javascriptChannels: <JavascriptChannel>{
/// 這個函式後面單獨說明
_toasterJavascriptChannel(context),
},
/// 這裡相當於Swift中WKNavigationDelegate代理方法中的
/// func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)方法,
/// 就是用於攔截跳轉的,和攔截微信和支付寶跳轉類似
navigationDelegate: (NavigationRequest request) {
/// 這裡做了跳轉攔截,如果跳轉的地址是油管,那麼將阻止載入
if (request.url.startsWith('http://www.youtube.com/')) {
print('blocking navigation to $request}');
return NavigationDecision.prevent;
}
print('allowing navigation to $request');
/// 其他的跳轉允許
return NavigationDecision.navigate;
},
/// 監聽網頁開始載入,相當於Swift中WKNavigationDelegate代理方法中的
/// func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!)方法
onPageStarted: (String url) {
print('Page started loading: $url');
},
/// 監聽網頁載入完畢,相當於Swift中WKNavigationDelegate代理方法中的
/// func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)方法
onPageFinished: (String url) async {
/// 通過Completer拿webView控制器
final webViewController = await _controller.future;
/// 通過webViewController執行JS方法,這裡是呼叫一個JS的彈窗,並且非同步獲取JS的回撥,這個回撥可能為空
final callback = await webViewController.evaluateJavascript("alert('Hello world');");
print(callback);
},
/// 是否支援手勢側滑
gestureNavigationEnabled: true,
);
可以看到,如果理解了Swift中的WKWebView的WKNavigationDelegate代理方法含義,Flutter中的WebView無非是換了些名稱,將delegate風格換成了callback風格而已的初始化函式。
而_toasterJavascriptChannel
就是具體化的如果註冊監聽JS方法控制代碼:
JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
return JavascriptChannel(
/// 註冊一個監聽控制代碼為SeasonCallback
/// 和Swift中func add(_ scriptMessageHandler: WKScriptMessageHandler, name: String)方法一致
name: 'SeasonCallback',
/// 監聽的SeasonCallback回撥,獲取JS傳到Flutter側的引數,和Swift中的WKScriptMessageHandler代理回撥一致,
/// func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)
/// 然後JS就可以呼叫Flutter側的函數了
onMessageReceived: (JavascriptMessage message) {
/// Flutter接受到JS的message,並調起Flutter函式
Scaffold.of(context).showSnackBar(
SnackBar(content: Text(message.message)),
);
});
}
回憶一下上一篇文章中的3個需求,如果在Flutter需要實現,應該怎麼寫呢?理解了上一篇文章並看懂了這篇文章的註釋,應該不在話下。
總結
-
Flutter中的webview_flutter本質上還是呼叫了雙端的原生WebView元件,所以對應的一致性較好,如果瞭解原生與JS的互調,那麼Flutter與JS互調依葫蘆畫瓢即可。
-
Swift中的多個代理回撥在Flutter中都通過Callback完成,這樣看起來緊湊,雖然可能在編碼中不易於看清楚,但是比較符合Flutter的宣告式程式設計風格,需要努力適應。
webview_flutter的使用注意事項與坑點:
注意事項
iOS端需要在info.plist檔案中新增配置與許可權:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>io.flutter.embedded_views_preview</key>
<true/>
Android端:
-
1.在路徑android/app/build.gradle新增最小SDK支援:
android { defaultConfig { minSdkVersion 19 } }
-
2.安卓端開啟混合檢視 ``` import 'dart:io';
import 'package:webview_flutter/webview_flutter.dart';
class WebViewExample extends StatefulWidget { @override WebViewExampleState createState() => WebViewExampleState(); }
class WebViewExampleState extends State
@override Widget build(BuildContext context) { return WebView( initialUrl: 'http://flutter.cn', ); } } ```
- 3.開啟http選項和隱私許可權,注意是在
專案/android/app/src/main/AndroidManifest.xml
檔案中進行新增 ```
```
坑點
如果你使用webview_flutter在Android端載入H5,而H5中正好需要呼叫系統相機或者是相簿的話,這個JS是無法得到相應的,而iOS端卻可以。
android webview 裡點選<input type=’file’>
沒有反應。
出現這個問題的原因是,官方的webview_flutter外掛沒有針對這一功能做適配。
詳細可以看flutter webview android h5 上傳檔案失敗解決辦法。
如果需要Android端有這個能力,需要在新增許可權的同時,改寫外掛在Android側的實現。
當然有另外一個外掛flutter_webview_plugin可以實現Android上傳這個功能,但是這個flutter_webview_plugin不管從易用性還是功能性,都不及webview_flutter。
我好希望有個一個大佬給教我改寫webview_flutter外掛,支援Android端的上傳功能,我按照網上的方法改寫了,然後崩潰了。。。
明天繼續
明天會講解Swift中,準確是OC中WebViewJavaScriptBridge的使用與注意實現,大家加油!
- WWDC22 | session 110357 | 邂逅Swift Regex
- WWDC22 | session110354 | Swift Regex簡介
- 突然插播:Flutter與JS互調
- Swift與H5互動:跳轉攔截完成支付功能
- 遠端控制,MQTT除錯
- 遠端控制,Server與App網路通訊初步調研
- Swift:基石庫——R.swift
- iOS無感知上拉載入更多功能實現
- Swift:解包的正確姿勢
- UI = f(State),在Swift中的一點思考
- Apple登入與實際業務結合的一點開發總結
- iOS工程無用資源圖片清理軟體推薦
- RxSwift學習——Schedulers (排程器)
- 重要!後面幾個月,iOS開發需要注意的3件事情
- 高德地圖才整改完了,Bug就來了
- 個人資訊保護法頒佈了,App的高德SDK整改
- Mac 程式碼統計工具:PPRows
- Swift:Moya中的Plugin和麵向協議程式設計
- 聊一下Objective-C中的id型別
- Swift:Array的map\compactMap\flatMap函式