隨著 Web 技術和移動設備的快速發展,Hybrid 技術已經成為一種最主流最常見的方案。一套好的 Hybrid架構方案 能讓 App 既能擁有極致的體驗和性能,同時也能擁有 Web技術 靈活的開發模式、跨平台能力以及熱更新機制,想想是不是都雞凍不已。。??。本系列文章是公司在這方面實踐的一個總結,包含了原理解析、方案選型與實現、實踐優化等方面。
大家可以到github上和我進行討論哈!
第二篇實戰篇
Hybrid App,俗稱混合應用,即混合了 Native技術 與 Web技術 進行開發的移動應用。現在比較流行的混合方案主要有三種,主要是在UI渲染機制上的不同:
以上的三種方案,其實同樣都是基於 JSBridge 完成的通訊層,第二三種方案,其實可以看做是在方案一的基礎上,繼續通過不同的新技術進一步提高了應用的混合程度。因此,JSBridge 也是整個混合應用最關鍵的部分,例如我們在設置微信分享時用到的 JS-SDK,wx對象 便是我們最常見的 JSBridge:
任何技術方案的選型,其實都應該基於使用場景和現有條件。基於公司現有情況的幾點考慮,在方案一上進一步優化,更加適合我們的需求。
因此,如何既能利用 H5 強大的開發和迭代能力,又能賦予 H5 強大的底層能力和用戶體驗,同時能復用現有的成熟 Native組件,便成為了我們最大的需求點 -- 一套完整又強大的 Hybrid技術架構方案。??
Hybrid App的本質,其實是在原生的 App 中,使用 WebView 作為容器直接承載 Web頁面。因此,最核心的點就是 Native端 與 H5端 之間的雙向通訊層,其實這裡也可以理解為我們需要一套跨語言通訊方案,來完成 Native(Java/Objective-c/...) 與 JavaScript 的通訊。這個方案就是我們所說的 JSBridge,而實現的關鍵便是作為容器的 WebView,一切的原理都是基於 WebView 的機制。
基於 WebView 的機制和開放的 API, 實現這個功能有三種常見的方案:
第二三種機制的原理是類似的,都是通過對 WebView 信息冒泡傳遞的攔截,從而達到通訊的,接下來我們主要從 原理-定製協議-攔截協議-參數傳遞-回調機制 5個方面詳細闡述下第三種方案 -- URL攔截方案。
在 WebView 中發出的網路請求,客戶端都能進行監聽和捕獲
我們需要制定一套URL Scheme規則,通常我們的請求會帶有對應的協議開頭,例如常見的 https://xxx.com 或者 file://1.jpg,代表著不同的含義。我們這裡可以將協議類型的請求定製為:
xxcommand://xxxx?param1=1¶m2=2
這裡有幾個需要注意點的是:
(1) xxcommand:// 只是一種規則,可以根據業務進行制定,使其具有含義,例如我們定義 xxcommand:// 為公司所有App系通用,為通用工具協議:
xxcommand://getProxy?h=1
而定義 xxapp:// 為每個App單獨的業務協議。
xxapp://openCamera?h=2
不同的協議頭代表著不同的含義,這樣便能清楚知道每個協議的適用範圍。
(2) 這裡不要使用 location.href 發送,因為其自身機制有個問題是同時並發多次請求會被合併成為一次,導致協議被忽略,而並發協議其實是非常常見的功能。我們會使用創建 iframe 發送請求的方式。
(3) 通常考慮到安全性,需要在客戶端中設置域名白名單或者限制,避免公司內部業務協議被第三方直接調用。
客戶端可以通過 API 對 WebView 發出的請求進行攔截:
當解析到請求 URL 頭為制定的協議時,便不發起對應的資源請求,而是解析參數,並進行相關功能或者方法的調用,完成協議功能的映射。
由於協議的本質其實是發送請求,這屬於一個非同步的過程,因此我們便需要處理對應的回調機制。這裡我們採用的方式是JS的事件系統,這裡我們會用到 window.addEventListener 和 window.dispatchEvent這兩個基礎API;
window.addEventListener
window.dispatchEvent
通過事件的機制,會讓開發更符合我們前端的習慣,例如當你需要監聽客戶端的通知時,同樣只需要在通過 addEventListener 進行監聽即可。
addEventListener
Tips: 這裡有一點需要注意的是,應該避免事件的多次重複綁定,因此當唯一標識重置時,需要removeEventListener對應的事件。
removeEventListener
由於 WebView 對 URL 會有長度的限制,因此常規的通過 search參數 進行傳遞的方式便具有一個問題,既 當需要傳遞的參數過長時,可能會導致被截斷,例如傳遞base64或者傳遞大量數據時。
因此我們需要制定新的參數傳遞規則,我們使用的是函數調用的方式。這裡的原理主要是基於:
Native 可以直接調用 JS 方法並直接獲取函數的返回值。
我們只需要對每條協議標記一個唯一標識,並把參數存入參數池中,到時客戶端再通過該唯一標識從參數池中獲取對應的參數即可。
由於 Native 可以算作 H5 的宿主,因此擁有更大的許可權,上面也提到了 Native 可以通過 WebView API直接執行 Js 代碼。這樣的許可權也就讓這個方向的通訊變得十分的便捷。
// Swift webview.stringByEvaluatingJavaScriptFromString("alert(NativeCall)")
// 調用js中的JSBridge.trigger方法 // 該方法的弊端是無法獲取函數返回值; webView.loadUrl("javascript:JSBridge.trigger(NativeCall)")
Tips: 當系統低於4.4時,evaluateJavascript 是無法使用的,因此單純的使用 loadUrl 無法獲取 JS 返回值,這時我們需要使用前面提到的 prompt 的方法進行兼容,讓 H5端 通過 prompt 進行數據的發送,客戶端進行攔截並獲取數據。
// 4.4+後使用該方法便可調用並獲取函數返回值; mWebView.evaluateJavascript("javascript:JSBridge.trigger(NativeCall)", new ValueCallback<String>() { @Override public void onReceiveValue(String value) { //此處為 js 返回的結果 } });
基於上面的原理,我們已經明白 JSBridge 最基礎的原理,並且能實現 Native <=> H5 的雙向通訊機制了。
接下來,我們來理下代碼上需要的資源。實現這套方案,從上圖可以看出,其實可以分為兩個部分:
我們這裡的做法是,將這兩部分一起封裝成一個 Native SDK,由客戶端統一引入。客戶端在初始化一個 WebView 打開頁面時,如果頁面地址在白名單中,會直接在 HTML 的頭部注入對應的 bridge.js。這樣的做法有以下的好處:
這裡有一點需要注意的是,協議的調用,一定是需要確保執行在bridge.js 成功注入後。由於客戶端的注入行為屬於一個附加的非同步行為,從H5方很難去捕捉準確的完成時機,因此這裡需要通過客戶端監聽頁面完成後,基於上面的事件回調機制通知 H5端,頁面中即可通過window.addEventListener(bridgeReady, e => {})進行初始化。
window.addEventListener(bridgeReady, e => {})
將 H5 接入 App 中通常有兩種方式:
(1) 在線H5,這是最常見的一種方式。我們只需要將H5代碼部署到伺服器上,只要把對應的 URL地址 給到客戶端,用 WebView 打開該URL,即可嵌入。該方式的好處在於:
但相對的,這種方式也有對應的缺點:
通常,這種方式更適用在一些比較輕量級的頁面上,例如一些幫助頁、提示頁、使用攻略等頁面。這些頁面的特點是功能性不強,不太需要複雜的功能協議,且不需要離線使用。在一些第三方頁面接入上,也會使用這種方式,例如我們的頁面調用微信JS-SDK。
(2) 內置包H5,這是一種本地化的嵌入方式,我們需要將代碼進行打包後下發到客戶端,並由客戶端直接解壓到本地儲存中。通常我們運用在一些比較大和比較重要的模塊上。其優點是:
但同時,它的劣勢也十分明顯:
這兩種接入方式均有自己的優缺點,應該根據不同場景進行選擇。
本文主要解析了現在Hybrid App的發展現狀和其基礎原理,包含了
只有在了解了其最本質的實現原理後,才能對這套方案進行實現以及進一步的優化。接下來,我們將基於上面的理論,繼續探討如何把這套方案的真正代碼實現以及方案優化方案,請繼續第二篇實戰篇。歡迎大家一起討論!更多文章內容請到github。感謝!??
推薦閱讀: