導語
随着移動端領域的快速發展,業務場景愈發複雜,為減少開發量和投入成本,混合應用(Hybrid App)占據了不少市場。混合應用承載H5頁面的容器就是webview,前端人員在開發過程中或多或少都需要與原生(Native App)之間互動,這個互動的橋梁就叫做JSBridge。同樣也有一套通用的調試方法來確定JSB通信順利。
什麼是WebView
WebView是原生應用用來展示網頁的view元件,本質上就是一款内置了webkit核心的無頭浏覽器(headless browser),提供了例如頁面前進後退、放大縮小、音頻控制等一般浏覽器具備的功能。
可以将WebView想象成html裡面的iframe标簽,h5頁面與原生應用的通信過程就像是iframe父子頁面之間的通信,隻不過iframe使用postMessage和message事件處理,而WebView使用的是JSBridge。
什麼是JSBridge
JSBridge是支援原生應用與H5應用雙向通信的“橋梁”。
- 原生->h5:向h5應用通知原生應用的相關狀态,觸發h5頁面的内容更新、消息發送、音頻播放等
-
h5->原生:向原生應用通知h5應用需要使用的功能,比如使用攝像頭、使用gps、喚起app等
因為原生應用與h5應用各自運作在獨立的環境中,他們通信的方式就像前端JSONP跨域請求一樣,通過原生暴露的一些特性讓雙端進行聯系。
Android WebView
Android WebView以4.4為分界點,采用了不同的核心:
- Android 4.4前:基于Webkit核心,H5的很多新特性不支援,且存在适配成本高、不安全、不穩定、耗流量、速度慢、視訊播放差、檔案能力差等問題。
- Android 4.4後:基于Chromium核心(google在webkit上的fork出來的項目),很多新的規範被支援,例如WebGL,Canvas2D,CSS3以及其他很多的HTML5特性,同時大大提升了WebView元件的性能。
js調用java
Android Webview共提供過三種JS調用java的接口:
- JavascriptInterface
- WebViewClient.shouldOverrideUrlLoading() 【官方已廢棄】
- WebChromeClient.onXXX()
JavascriptInterface
這是 Android 提供的 JS 與 Native 通信的官方解決方案。
- 首先Android RD需要實作一個類給 JavaScript 調用。
public class WebAppInterface {
@JavascriptInterface
public void foo(String str) {
// doing something
}
}
- 将這個WebAppInterface類添加到 WebView 的 JavaScriptInterface 中。
WebView webView = (WebView) findViewById(R.id.webview);
// 這裡的Android會被當做一個變量,注入到頁面的window中。
webView.addJavascriptInterface(new WebAppInterface(this), "Android");
- 最後就可以在 JS 中調用 Native 的方法了。
function callFoo(str: string) {
Android.foo(str);
}
WebViewClient.shouldOverrideUrlLoading()
攔截webview内的url變更,例如iframe.src或location.href,若設定WebViewClient且該方法傳回true,則可由代碼自定義邏輯。
一般采用URL Scheme的方式,它一種類似于URL的連結,原本用于喚起各類app而設計,主要特征是protocol和host一般是自定義的。
是以可以事先約定好特殊格式的 URL Scheme,觸發shouldOverrideUrlLoading攔截後,判斷其格式是否符合,然後 Native 執行其特定邏輯。
public class CustomWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(
WebView view,
String url
) {
if (isJsBridgeUrl(url)) {
// JSbridge的處理邏輯
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
}
WebChromeClient.onXXX()
WebChromeClient支援監聽浏覽器的很多方法,比如:
- Alert -> WebChromeClient.onJsAlert
- Confirm -> WebChromeClient.onJsConfirm
- Prompt -> WebChromeClient.onJsPrompt
- console.xxx -> WebChromeClient.onConsoleMessage
/**
* 當網頁調用alert()來彈出alert彈出框前回調,用以攔截alert()函數
*/
public boolean onJsAlert(WebView view, String url, String message,JsResult result)
/**
* 當網頁調用confirm()來彈出confirm彈出框前回調,用以攔截confirm()函數
*/
public boolean onJsConfirm(WebView view, String url, String message,JsResult result)
/**
* 當網頁調用prompt()來彈出prompt彈出框前回調,用以攔截prompt()函數
*/
public boolean onJsPrompt(WebView view, String url, String message,String defaultValue, JsPromptResult result)
/**
* 列印 console 資訊
*/
public boolean onConsoleMessage(ConsoleMessage consoleMessage)
以alert為例:
- H5頁面調用alert方法
- 觸發執行WebChromeClient.onJsAlert(),alert方法的入參message就是onJsAlert中的message參數
public class CustomWebChromeClient extends WebChromeClient {
@Override
public boolean onJsAlert(
WebView view,
String url,
String message,
JsResult result
) {
// doing something
// 傳回值true,表示攔截成功,false表示攔截失敗,繼續彈窗
return true;
}
}
Java 調用 js
根據android版本,有2種方式:
- loadUrl:全部支援
- evaluateJavascript:安卓4.4版本之後
loadUrl(String)
WebView.evaluateJavascript(String, IValueCallback)
webView.evaluateJavascript("javascript:" + javaScriptString, new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
// 執行完成後的回調
}
});
IOS WebView
IOS8 以前使用的是UIWebview,但是它存在不少問題:加載速度慢、記憶體洩漏、記憶體占用高、超出記憶體限制會被kill掉或者強制觸發gc。感興趣可閱讀《IOS混合應用切換app閃屏bug總結》
是以在WWDC 2014 大會上,推出了WKWebView。它代替了 UIKit 中的 UIWebView 和 AppKit 中的 WebView,提供了統一的跨雙平台 API。擁有 60fps 滾動重新整理率、内置手勢、高效的 app 和 web 資訊交換通道、和 Safari 相同的 JavaScript 引擎。
使用messageHandlers 調用swift
- 設定messageHandler
[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"showAlert"];
// 為了避免循環引用,導緻控制器無法被釋放,還需要移除
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"showAlert"];
- webview會向window全局對象上注入webkit,調用messageHandlers即可實作調用swift
使用UserScript來注入 JavaScript
WKUserScript 允許在正文加載之前或之後注入到頁面中,它以 JavaScript 源碼形式初始化,初始化時還可以傳入是在加載之前還是結束時注入,以及腳本影響的是這個布局還是僅主要布局。舉一個簡單例子:
let source = "document.body.innerHTML = 'helloWorld'";
// 注入腳本 在文檔加載完成後執行
let userScript = WKUserScript()
let userScript = WKUserScript(source: source, injectionTime: WKUserScriptInjectionTimeAtDocumentStart, forMainFrameOnly: true)
let userContentController = WKUserContentController()
userContentController.addUserScript(userScript)
let configuration = WKWebViewConfiguration()
configuration.userContentController = userContentController
self.webView =WKWebView(frame: self.view.bounds, configuration: configuration)
// url scheme
this.sendMessageQueue.push(msgJSON);
this._dispatchUrlMsg("" + this.bridgeScheme + this.dispatchMsgPath);
}
} catch (e) {
console.error(e);
}
相關文檔
前端工程師所需要了解的WebView
關于shouldOverrideUrlLoading方法的一些考證
了解WebKit和Chromium: WebKit, WebKit2, Chromium和Chrome
WebViewClient.ShouldOverrideUrlLoading Method
IOS混合應用切換app閃屏bug總結