Hybrid基本原理

本文原創:wangtiegangjavascript

背景介紹

Hybrid App,俗稱混合應用,即混合了 Native技術 與 Web技術 進行開發的移動應用。兼具" Native App良好用戶交互體驗的優點 "和" Web App跨平臺開發的優點 "。html

Hybrid APP底層依賴於Native提供的容器(UIWebview),上層使用Html&Css&JS作業務開發,底層透明化、上層多多樣化,這種場景很是有利於前端介入,很是適合業務快速迭代。前端

相關方案

Hybrid App,俗稱混合應用,即混合了 Native技術 與 Web技術 進行開發的移動應用。如今比較流行的混合方案主要有三種,主要是在UI渲染機制上的不一樣:java

  1. 基於 WebView UI 的基礎方案,經過JSBridge完成H5和Native的通訊,賦予H5必定的端能力。是一種基於WebView UI的解決方案。
  2. 基於 Native UI 的方案,例如 React-Native、Weex,在賦予 H5 原生API能力的基礎上,進一步經過JSbridge將js解析爲虛擬DOM傳遞到Native,並使用原生進行渲染。

架構介紹

經過JSBridge,H5頁面能夠調用Native的api,Native也可調用H5頁面的方法或者通知H5頁面回調,從而實現雙向通訊,以下圖 android

frame.png

技術原理

JavaScript 通知 Native

基於 WebView 的機制和開放的 API, 實現這個功能有三種常見的方案:web

  1. API注入,原理其實就是 Native 獲取 JavaScript環境上下文,並直接在上面掛載對象或者方法,使 js 能夠直接調用,Android 與 IOS 分別擁有對應的掛載方式。具體方法以下:
class JsObject {
   @JavascriptInterface
   public String toString() { return "injectedObject"; }
}
webview.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new JsObject(), "injectedObject");
webView.loadData("", "text/html", null);
webView.loadUrl("javascript:alert(injectedObject.toString())");
複製代碼

可是此方法使用須要注意:json

  • 在Android <=4.1.2 (API 16),WebView使用WebKit瀏覽器引擎,並未正確限制addJavascriptInterface的使用方法,在應用權限範圍內,攻擊者能夠經過Java反射機制實現任意命令執行。
  • 在Android >=4.2 (API 17),WebView使用Chromium瀏覽器引擎,而且限制了Javascript對Java對象方法的調用權限,只有聲明瞭@JavascriptInterace註解的方法才能被Web頁面調用。
  1. WebView 中的 prompt/console/alert 攔截,一般使用 prompt,由於這個方法在前端中使用頻率低,比較不會出現衝突;api

  2. WebView URL Scheme 跳轉攔截;瀏覽器

第二三種機制的原理是相似的,都是經過對 WebView 信息冒泡傳遞的攔截,從而達到通信的,目前多使用後兩種方法。緩存

實現過程以下:

定製協議

經過特定的參數轉換方法,將傳入的數據,方法名一塊兒,拼接成一個url scheme,如

// 基本有用信息就是後面的callbackId,handlerName與data
API_Name:callbackId/handlerName?param0=xxx&param1=yyy
複製代碼

經過上面的定義來對應Native層不一樣的方法,並傳遞參數

消息發送

  1. 這裏不要使用 location.href 發送,由於其自身機制有個問題是同時併發屢次請求會被合併成爲一次,致使協議被忽略,而併發協議實際上是很是常見的功能。

咱們會使用建立 iframe 發送請求的方式。如

//建立隱藏iframe過程
var messagingIframe = document.createElement('iframe');
messagingIframe.style.display = 'none';
document.documentElement.appendChild(messagingIframe);

//觸發scheme
messagingIframe.src = uri;  
複製代碼
  1. 經過調用 onJsAlert(),onJsConfirm(),onJsPrompt(),方法回調攔截對話框消息
var data = {
    action:'xxxx',
    params:'xxxx',
    callback:'xxxx',
};
var jsonData = JSON.stringify([data]);
//發起彈框
window.prompt(jsonData);
複製代碼

一般考慮到安全性,須要在客戶端中設置域名白名單或者限制,避免公司內部業務協議被第三方直接調用。

消息的攔截

對應上面消息的發送,消息的攔截方法以下:

  1. 客戶端能夠經過 API 對 WebView 發出的請求進行攔截:

    IOS上: shouldStartLoadWithRequest

    Android: shouldOverrideUrlLoading

當解析到請求 URL 頭爲制定的協議時,便不發起對應的資源請求,而是 解析參數,並進行相關功能或者方法的調用,完成協議功能的映射

  1. 以onJsPrompt爲例,客戶端能夠經過 API 對 WebView 發出的請求進行攔截:

    IOS上: runJavaScriptTextInputPanelWithPrompt

    Android: WebChromeClient.onJsPrompt

    處理方式同上

參數傳遞

因爲 WebView 對 URL 會有長度的限制,所以常規的經過 scheme參數 進行傳遞的方式便具備一個問題,即當須要傳遞的參數過長時,可能會致使被截斷,例如傳遞base64或者傳遞大量數據時。因爲 Native 能夠直接調用 JS 方法並直接獲取函數的返回值,所以咱們只須要對每條協議標記一個惟一標識,並把參數存入參數池中,到時客戶端再經過該惟一標識從參數池中獲取對應的參數便可。

協議回調

當消息發送到Native 層後,咱們便須要處理對應的回調,一樣須要的處理過程:

  1. js 向業務層發送消息,並經過 window.addEventListener 註冊惟一的自定義事件,並綁定到對應的事件上
  2. Native 層收到後執行相應的業務方法
  3. Native 層執行完相應的業務,調用Bridge的dispatch 方法,發送到js層
  4. js 層獲取傳回的數據,經過 window.dispatchEvent 觸發相應的自定義事件,並執行對應的回調

Native 通知 JavaScript

因爲 Native 能夠算做 H5 的宿主,所以擁有更大的權限,上面也提到了 Native 能夠經過 WebView API直接執行 Js 代碼。

// IOS
webview.stringByEvaluatingJavaScriptFromString("alert('NativeCall')")

// Android 4.4-
webView.loadUrl("javascript:JSBridge.trigger('NativeCall')")

// Android 4.4+
mWebView.evaluateJavascript("javascript:JSBridge.trigger('NativeCall')", 	 new ValueCallback<String>() {
    @Override
    public void onReceiveValue(String value) {
        //此處爲 js 返回的結果
    }
});
複製代碼

android系統在低於4.4時,evaluateJavascript 是沒法使用的,所以單純的使用 loadUrl 沒法獲取 JS 返回值,這時咱們須要使用前面提到的 prompt 的方法進行兼容,讓 H5端 經過 prompt 進行數據的發送,客戶端進行攔截並獲取相應數據

離線資源

在線獲取h5資源總會遇到網絡問題,對於一些不會常常改變的資源放到本地緩存,能夠提升訪問速度和穩定性。

一般來講,H5資源分爲兩種,常常更新的業務代碼不常常更新的框架、庫代碼和公用組件代碼,爲了實現離線資源的共享,在H5打包時能夠採用分包的策略,將公用部分單獨打包,在本地也是單獨存放

爲了實現同一資源的線上和離線訪問, Native 層須要對 H5 請求的資源進行判斷,究竟是從線上獲取仍是本地獲取,這裏咱們暫時稱之爲 LocalUrlRouter。

接下來就是如何獲取離線資源了,咱們上面提到了 LocalUrlRouter,它負責線上資源到本地資源映射,咱們借鑑已有的映射規則:H5開發完成後會掃描H5項目而後生成一份線上資源和本地資源路徑的映射表(source-router.json)。每次請求資源時,LocalUrlRouter 會檢查映射表,若是本地有離線資源,則會返回本地資源,不然經過http請求獲取線上資源。

還能夠完善的地方資源包的更新,映射表對資源的緩存也能夠動態的增長等