天天看點

JSBridge——如何與原生打交道

導語

随着移動端領域的快速發展,業務場景愈發複雜,為減少開發量和投入成本,混合應用(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應用雙向通信的“橋梁”。

JSBridge——如何與原生打交道
  • 原生->h5:向h5應用通知原生應用的相關狀态,觸發h5頁面的内容更新、消息發送、音頻播放等
  • h5->原生:向原生應用通知h5應用需要使用的功能,比如使用攝像頭、使用gps、喚起app等

    因為原生應用與h5應用各自運作在獨立的環境中,他們通信的方式就像前端JSONP跨域請求一樣,通過原生暴露的一些特性讓雙端進行聯系。

    JSBridge——如何與原生打交道

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總結

繼續閱讀