網(wǎng)站備案要多長時(shí)間廣告精準(zhǔn)推廣平臺(tái)
前言
歡迎來到第二篇文章,這也是第二個(gè)難題,就是原有的移動(dòng)端本身一些頁面H5的形式去呈現(xiàn)(webview)
,例如某些需要?jiǎng)討B(tài)更換內(nèi)容的頁面,某些活動(dòng)頁面、支付頁面,不僅僅做頁面呈現(xiàn),還包括一些數(shù)據(jù)交互
,那么在項(xiàng)目初期,我們嘗試使用過了官方提供的webview,但是功能比較有限,因此我們選擇了flutter_inappwebview
這個(gè)插件
- 官網(wǎng)webview
https://pub.dev/packages/webview_flutter
- flutter_inappwebview
https://pub.dev/packages/flutter_inappwebview
本文以flutter_inappwebview
為案例,將陳述我將Flutter編譯成web之后,如果去處理flutter_inappwebview
的。
原理
我想在講之前,先講講什么是webview?
實(shí)際上對(duì)我來說,webview應(yīng)該相對(duì)于原生端而已(ios、android),是由原生提供的底層網(wǎng)頁環(huán)境容器
,去呈現(xiàn)網(wǎng)頁內(nèi)容,那么不同端,他們提供的環(huán)境當(dāng)然是不一樣的,因?yàn)榫W(wǎng)頁內(nèi)核不一樣,那么也會(huì)造成很多的差異,所以為了抹平這些差異,才會(huì)提供第三方插件,封裝公共的api供頂層調(diào)用,底層由各個(gè)平臺(tái)去進(jìn)行實(shí)現(xiàn)
。
例如我之前的文章提到過的數(shù)據(jù)交互,本質(zhì)上就是在當(dāng)前環(huán)境去注入某些方法、某些類去給網(wǎng)頁容器提供交互的能力。
那么對(duì)于flutter_inappwebview
而言,它給每一個(gè)網(wǎng)頁容器提供了一個(gè)控制器,這個(gè)控制器有一個(gè)addJavaScriptHandler
方法,可以連接到網(wǎng)頁容器,實(shí)現(xiàn)監(jiān)聽和傳輸數(shù)據(jù),同時(shí)給網(wǎng)頁容器放了一個(gè)公共的類window.flutter_inappwebview
類提供網(wǎng)頁傳輸數(shù)據(jù)到客戶端的能力。
那么我說webview應(yīng)該相對(duì)于原生端而已,因?yàn)镕lutter在編譯成web之后,本質(zhì)上就是web,那么之前的網(wǎng)頁就不能叫webview,應(yīng)該叫iframe。
那么我們的問題,實(shí)際上就是web和iframe的數(shù)據(jù)交互問題。
實(shí)現(xiàn)
那么,如果你明白本質(zhì)是web和iframe的數(shù)據(jù)交互問題,你就明白為什么addJavaScriptHandler
方法在web平臺(tái)沒有實(shí)現(xiàn)的原因,因?yàn)閴焊筒恍枰⑷?#xff0c;他們本身就已經(jīng)在一個(gè)環(huán)境下面了。
由于瀏覽器有同源策略,所以我們還是使用postMessgae的方法,但有一些地方需要注意。
實(shí)際上flutter_inappwebview有對(duì)web端的某些api進(jìn)行封裝,編譯成web,那么之前的iframe的配置在flutter_inappwebview的options里面也會(huì)存在。
InAppWebViewSettings getInAppWebViewGroupOptions() {return InAppWebViewSettings()..useShouldInterceptAjaxRequest = false..useShouldInterceptFetchRequest = false..supportZoom = true..clearCache = true..iframeName = 'my_ifram' //設(shè)定ifame的名稱,這個(gè)就是給web環(huán)境使用的..mediaPlaybackRequiresUserGesture = true..transparentBackground = true..allowsInlineMediaPlayback = false;}
同樣驗(yàn)證的方式,你可以編譯成web之后,之前的webview,在瀏覽器標(biāo)簽上也可以得知上iframe。
我們可以看編譯之后的產(chǎn)物:
- 首先id固定為flutter_inappwebview-0
- 有csp,防止內(nèi)部js腳本攻擊(部分瀏覽器不可用)
- 有sandbox模式,也就是沙箱模式,可以進(jìn)行配置,對(duì)iframe做一些限制
- src轉(zhuǎn)成hash鏈接
- 默認(rèn)為全屏模式
和iframe通信的方式網(wǎng)上有很多方法,我這里不過多贅述。
父頁面發(fā)送:
//獲取子iframe,通過name屬性或者直接拿第一個(gè)iframeweb.HTMLIFrameElement childIframe = web.document.getElementById("flutter_inappwebview-0") as web.HTMLIFrameElement;Map data = {'user': {'token': 1111}};JSString msg = jsonEncode(data).toJS;//給子ifram傳遞數(shù)據(jù)// childIframe.contentWindow?.postMessage(msg, '*'.toJS);//這種方式是不行的childIframe.contentWindowCrossOrigin?.postMessage(msg, '*'.toJS);
父頁面接收:
//監(jiān)聽子組件傳遞過來的消息web.window.addEventListener('message', callback.toJS);void callback(web.Event e) {print("收到j(luò)s交互=====${e}");}
iframe發(fā)送:
window.top.postMessage('hello', '*')
iframe接收:
window.addEventListener('message', (event) => {console.log("ssss", event.data)})
難點(diǎn)
條件編譯
盡管Flutter支持web端
但當(dāng)編譯成web端時(shí),此時(shí)如果使用到了io庫(Platform相關(guān))都會(huì)在編譯時(shí)報(bào)錯(cuò)。
當(dāng)編譯成原生端,使用web端相關(guān)的庫(web、js庫)同樣也會(huì)在編譯時(shí)報(bào)錯(cuò)。
那么你就需要進(jìn)行文件的替換工作,讓Flutter在編譯時(shí),根據(jù)不同的端去引入不同的文件。
比如這一段代碼:
web.window.addEventListener('message', callback.toJS);void callback(web.Event e) {print("收到j(luò)s交互=====${e}");}
這里我們需要一定要使用到web庫相關(guān)的內(nèi)容,才有window環(huán)境以及調(diào)用addEventListener,但是你在原生端,這段代碼會(huì)讓你在編譯時(shí)就報(bào)錯(cuò)了。
因此你需要去做文件替換。
自己創(chuàng)建兩個(gè)文件:
import './vm/ai_web_js.dart' if (kIsWeb) 'dart:js_interop';
import './vm/ai_web_window.dart' if (kIsWeb) 'package:web/web.dart' as web;
在原生端使用的是我們自己寫的文件,實(shí)現(xiàn)的是空方法。
在web端使用對(duì)應(yīng)的庫文件
鏈接為hash鏈接加載空白的問題
當(dāng)鏈接為hash鏈接時(shí),iframe會(huì)將src從鏈接轉(zhuǎn)換成另外的格式文件data:text/html
js有提供一個(gè)內(nèi)置的api為encodeURIComponent
比如有一個(gè)html為:
<div id="myDiv"><h1>Hello, World!</h1><p>This is a sample HTML code.</p>
</div>
通過api編碼之后變成了類似這樣:
"%3Cdiv%20class%3D%22s-skin-container%20s-isindex-wrap%20skin-gray-event%22%3E%3C%2Fdiv%3E%3Cdiv%20id%3D%22head%22%20class%3D%22%22%3E%3Cdiv%20id%3D%22s_top_wrap%22%20class%3D%22s-top-wrap%20s-isindex-wrap%20%22%20style%3D%22left%3A%200px%3B%22%3E%3Cdiv%20class%3D%22s-top-nav%22%3E%3C%2Fdiv%3E%3Cdiv%20class%3D%22s-center-box%22%3E%3C%2Fdiv%3E%3C%2Fdiv%3E%3Cdiv%20id%3D%22s-top-left%22%20class%3D%22s-top-left-new%20s-isindex-wrap%22%3E%3Cstyle%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20.aitab-image-long%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20width%3A%2049px%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20height%3A%2014px%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20margin-bottom%3A%20-2px%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20.aitab-image-short%20%7B%0A%20%20%20%20%20%20%20%20%20%
然后再加上前綴data:text/html,
這種特殊格式標(biāo)識(shí)變成URL:
const dataURL = "data:text/html," + encodedHTML;
放入iframe的src中去進(jìn)行處理。
但是當(dāng)我們的鏈接是hash鏈接,我們打包成web項(xiàng)目可以選擇hash路由或者h(yuǎn)istory路由,hash路由是通過監(jiān)聽hash值的變化去響應(yīng)不同的js數(shù)據(jù)在同一個(gè)html去做不同的渲染
。
那么這個(gè)時(shí)候,就要求你的html中的js必須能夠正常請(qǐng)求響應(yīng),才能觸發(fā)監(jiān)聽去做數(shù)據(jù)的呈現(xiàn)。
測試發(fā)現(xiàn),當(dāng)鏈接為hash鏈接時(shí),能正常展示html,但是內(nèi)部需要的js已經(jīng)不做網(wǎng)絡(luò)請(qǐng)求了,因?yàn)楦揪筒皇羌虞d鏈接,而是加載一個(gè)普通的文本
<!DOCTYPE html>
<html lang=zh-CN><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><title>aiyjcp</title><script>var coverSupport = 'CSS'in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))document.write('<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + (coverSupport ? ', viewport-fit=cover' : '') + '" />')</script><link rel=stylesheet href=./static/index.2da1efab.css></head><body><noscript><strong>Please enable JavaScript to continue.</strong></noscript><div id=app></div><script src=./static/js/chunk-vendors.bc6b93b6.js></script><script src=./static/js/index.d07f5f7d.js></script></body>
</html>
那么,至此我們就發(fā)現(xiàn)了問題了:
原因在于flutter_inappwebview會(huì)對(duì)url獲取html文本,進(jìn)行轉(zhuǎn)碼變成特殊的url格式(data:text/html格式),但是加載出html之后,內(nèi)部需要的js和css都不進(jìn)行網(wǎng)絡(luò)請(qǐng)求獲取,導(dǎo)致無法監(jiān)聽到hash值的變化以及所需要的資源,導(dǎo)致可能出現(xiàn)頁面空白(如果你使用hash路由),或者樣式丟失(如果你使用history路由)
那么就有三種解決方案:
- 第一種,不要讓插件對(duì)URL進(jìn)行轉(zhuǎn)碼
- 第二種,研究如何讓轉(zhuǎn)碼還原之后的html再次進(jìn)行網(wǎng)絡(luò)請(qǐng)求加載出需要的JS和CSS文件
- 第三種,再嵌套一層iframe,插件對(duì)第一層進(jìn)行轉(zhuǎn)碼還原之后,內(nèi)部還是一個(gè)iframe,這個(gè)時(shí)候內(nèi)部的iframe就不會(huì)對(duì)src進(jìn)行再次轉(zhuǎn)碼了,但是這種情況,數(shù)據(jù)通信又需要做改變,由父子關(guān)系,變成父到子再到孫子關(guān)系
問題出在配置上面:
之前在原生端對(duì)url做了處理:
key: webViewKey,initialUrlRequest: URLRequest(url: url.startsWith("http")? WebUri.uri(Uri.parse(url)): WebUri.uri(Uri.file(url)),
但是在web環(huán)境,這種方式會(huì)對(duì)url進(jìn)行轉(zhuǎn)碼,因此你需要這么寫在web環(huán)境
URLRequest(url: WebUri(url)
盡管這種方式解決了編譯和運(yùn)行的問題,但是卻失去了相應(yīng)的代碼提示,如果你有更好的方法,歡迎在下方提出,下一篇會(huì)解決Dio以及請(qǐng)求相關(guān)的問題