隨着 Web 技術和移動設備的飛速發展,各類 APP 層出不窮,極速的業務擴展提升了團隊對開發效率的要求,這個時候使用 IOS/Andriod 開發一個 APP 彷佛成本有點太高了,而 H5 的低成本、高效率、跨平臺等特性立刻被利用起來造成了一種新的開發模式:Hybrid APP
。javascript
Hybrid 技術已經成爲一種最主流最多見的方案。一套好的 Hybrid 架構解決方案能讓 App 既能擁有極致的體驗和性能,同時也能擁有 Web 技術 靈活的開發模式、跨平臺能力以及熱更新機制。本文主要是結合我最近開發的一個 Hybrid 項目(https://github.com/Jack-cool/hybrid_jd
),帶你們全面瞭解一下 Hybrid。前端
深刻了解 Hybrid 前,讓咱們先來看一下目前市面上比較成熟的混合解決方案。java
這種是市面上大多數 app 採起的方案,也是混合開發最基礎的方案。在 webview 的基礎上,與原生客戶端創建js bridge
橋接,以達到 js 調用Native API
和 Native 執行js
方法的目的。react
目前國內絕大部分的大廠都有一套本身的基於 webview ui 的 hybrid 解決方案,例如微信的JS-SDK
,支付寶的JSAPI
等,經過JSBridge
完成 h5 與 Native 的雙向通信,從而賦予 H5 必定程度的原生能力。android
能夠簡單理解爲「跨平臺」,如今比較通用的有React Native
,Weex
,Flutter
等。在賦予 H5 原生 API 能力的基礎上,進一步經過 JSBridge 將 JS 解析成的虛擬節點數(Virtual DOM)傳遞到 Native 並使用原生渲染。咱們這裏來看下上面提到的這三種:ios
「Learn once, write anywhere」,React Native
採用了 React 的設計模式,但 UI 渲染、動畫效果、網絡請求等均由原生端實現(因爲 JS 是單線程,不大可能處理太多耗時的操做)。開發者編寫的 JS 代碼,經過 React Native 的中間層轉化爲原生控件和操做,極大的提升了用戶體驗。git
React Native
全部的標籤都不是真實控件,JS 代碼中所寫控件的做用,相似 Map 中的 key 值。JS 端經過這個 key 組合的 Dom ,最後 Native 端會解析這個 Dom ,獲得對應的 Native 控件渲染,如 Android 中 標籤對應 ViewGroup 控件。github
總結下來,就是:React Native 是利用 JS 來調用 Native 端的組件,從而實現相應的功能。web
「Write once, run everywhere」,基於 Vue 設計模式,支持 web、android、ios 三端,原生端一樣經過中間層轉化,將控件和操做轉化爲原生邏輯來提高用戶體驗。編程
在 weex 中,主要包括三大部分:JS Bridge
、Render
、Dom
,JS Bridge 主要用來和 JS 端實現進行雙向通訊,好比把 JS 端的 dom 結構傳遞給 Dom 線程。Dom 主要是用於負責 dom 的解析、映射、添加等等的操做,最後通知 UI 線程更新。而 Render 負責在 UI 線程中對 dom 實現渲染。
和 react native 同樣,weex 全部的標籤也都不是真實控件,JS 代碼中所生成的 dom,最終都是由 Native 端解析,再獲得對應的 Native 控件渲染,如 Android 中 標籤對應 WXTextView 控件。
Flutter
是谷歌 2018 年發佈的跨平臺移動 UI 框架。與 react native 和 weex 的經過 Javascript 開發不一樣,Flutter 的編程語言是Dart
,因此執行時並不須要 Javascript 引擎,但實際效果最終也經過原生渲染。
看完這三種方案的簡介,下面讓咱們簡單來作個對比吧:
React Native | Weex | Flutter | |
---|---|---|---|
平臺實現 | JavaScript | JavaScript | 原生編碼 |
引擎 | JS V8 | JSCore | Flutter engine |
核心語言 | React | Vue | Dart |
框架程度 | 較重 | 較輕 | 重 |
特色 | 適合開發總體 App | 適合單頁面 | 適合開發總體 App |
支持 | Android、IOS | Android、IOS、Web | Android、IOS(可能還不止) |
Apk 大小(Release) | 7.6M | 10.6M | 8.1M |
小程序開發本質上仍是前端 HTML + CSS + JS 那一套邏輯,它基於 WebView 和微信(固然支付寶、百度、字節等如今都有本身的小程序,這裏只是拿微信小程序作個說明)本身定義的一套 JS/WXML/WXSS/JSON 來開發和渲染頁面。微信官方文檔裏提到,小程序運行在三端:iOS、Android 和用於調試的開發者工具,三端的腳本執行環境以及用於渲染非原生組件的環境是各不相同的。
經過更加定製化的 JSBridge,並使用雙 WebView 雙線程的模式隔離了 JS 邏輯與 UI 渲染,造成了特殊的開發模式,增強了 H5 與 Native 混合程度,提升了頁面性能及開發體驗。
Progressive Web App
, 簡稱 PWA,是提高 Web App 體驗的一種新方法,能給用戶帶來原生應用的體驗。
PWA 能作到原生應用的體驗不是靠某一項特定的技術,而是通過應用一系列新技術進行改進,在安全、性能和體驗三個方面都有了很大的提高,PWA 本質上仍是 Web App,併兼具了 Native App 的一些特性和優勢,主要包括下面三點:
Android 和主流的瀏覽器都早已支持了 PWA 標準,在 iOS 11.3 和 macOS 10.13.4 上,蘋果的 Safari 上也支持了 PWA。相信在不久的未來勢必會迎來 PWA 的大爆發...
看完目前主流的混合解決方案,咱們迴歸本篇主題,講解一下成熟解決方案背後的 Hybrid底層基礎
,要知道決定上層建築的永遠都是底層基礎,新的技術層出不窮,只有原理是不變的~~
Hybrid
,字面意思「混合」。能夠簡單理解爲是前端和客戶端的混合開發。
讓咱們先來看一下目前主流的移動應用開發方式:
Native App 是一種基於智能手機本地操做系統如 iOS、Android、WP 並使用原生程式編寫運行的第三方應用程序,也叫本地 app。通常使用的開發語言爲 Java、C++、Objective-C。。分別來看一下 Native 開發的優缺點:
優勢
缺點
Web App,顧名思義是指基於 Web 的應用,基本採用 Html5 語言寫出,不須要下載安裝。相似於如今所說的輕應用。基於瀏覽器運行的應用,基本上能夠說是觸屏版的網頁應用。分別來看一下 Web 開發的優缺點:
優勢
缺點
究其緣由就是性能要求的問題。Web app 之因此可以佔領開發市場,主要是由於它的開發速度快,使用簡單,應用範圍廣,可是在性能方面由於沒法調用所有硬件底層功能,就如今講,仍是比不過原生 App 的性能。
混合開發,也就是半原生半 Web 的開發模式,由原生提供統一的 API 給 JS 調用,實際的主要邏輯有 Html 和 JS 來完成,最終是放在 webview 中顯示的,因此只須要寫一套代碼便可達到跨平臺效果。
Hybrid App 兼具了 Native APP 用戶體驗佳、系統功能強大和 Web APP 跨平臺、更新速度快的優點。本質實際上是在原生的 App 中,使用 WebView 做爲容器直接承載 Web 頁面。所以,最核心的點就是 Native 端 與 H5 端 之間的雙向通信層,也就是咱們常說的 JSBridge
。
下面讓咱們來看下 JS 與 Native(客戶端)通訊的方式吧。
JS上下文注入
原理其實就是 Native 獲取 JavaScript 環境上下文,並直接在上面掛載對象或者方法,使 JS 能夠直接調用。
Android 與 IOS 分別擁有對應的掛載方式。分別對應是:蘋果UIWebview JavaScriptCore注入
、安卓addJavascriptInterface注入
、蘋果WKWebView scriptMessageHandler注入
。
上面這三種方式均可以被稱爲是JS上下文注入
,他們都有一個共同的特色就是,不經過任何攔截的辦法,而是直接將一個 native 對象(or 函數)注入到 JS 裏面,能夠由 Web 的 JS 代碼直接調用,直接操做。
彈窗攔截
這種方式主要是經過修改瀏覽器 Window 對象的某些方法,而後攔截固定規則的參數,以後分發給客戶端對應的處理方法,從而實現通訊。
經常使用的四個方法:
簡單拿 prompt 來舉例說明,Web 頁面經過調用 prompt()方法,安卓客戶端經過監聽onJsPrompt
事件,攔截傳入的參數,若是參數符合必定協議規範,那麼就解析參數,扔給後續的 Java 去處理。這種協議規範,最好是跟 iOS 的協議規範同樣,這樣跨端調起協議是一致的,但具體實現不同而已。好比:jack://utils/${action}?a=a
這樣的協議,而其餘格式的 prompt 參數,是不會監聽的,即除了 jack://utils/${action}?a=a
這樣的規範協議,prompt 仍是原來的 prompt。
但這幾種方法在實際的使用中有利有弊,但因爲prompt
是幾個裏面惟一能夠自定義返回值,能夠作同步交互的,因此在目前的使用中,prompt
是使用的最多的。
URL Schema
schema 是 URI 的一種格式,上文提到的jack://utils/${action}?a=a
就是一個 scheme 協議,這裏說的 scheme(或者 schema)泛指安卓和 iOS 的 schema 協議,由於它比較通用。
安卓和 iOS 均可以經過攔截跳轉頁 URL 請求,而後解析這個 scheme 協議,符合約定規則的就給到對應的 Native 方法去處理。
安卓和 iOS 分別用於攔截 URL 請求的方法是:
shouldOverrideUrlLoading
方法delegate
函數這裏簡單看一個以前項目中對於 schema 封裝:
// 調用 window.fsInvoke.share({title: 'xxx', content: 'xxx'}, result => { if (result.errno === 0) { alert('分享成功') } else { // 分享失敗 alert(result.message) } ) ---------------------------下方爲對fsInvoke的封裝 (function(window, undefined) { // 分享 invokeShare = (data, callback) => { _invoke('share', data, callback) } // 登陸 invokeLogin = (data, callback) => { _invoke('login', data, callback) } // 打開掃一掃 invokeScan = (data, callback) => { _invoke('scan', data, callback) } _invoke = (action, data, callback) => { // 拼接schema協議 let schema = `jack://utils/${action}?a=a`; Object.keys(data).forEach(key => { schema += `&${key}=${data[key]}` }) // 處理callback let callbackName = ''; if(typeof callback === 'string) { callbackName = callback } else { callbackName = action + Date.now(); window[callbackName] = callback; } schema += `&callback=${callbackName}` // 觸發 let iframe = document.createElement('iframe'); iframe.style.display = 'none'; iframe.src = schema; let body = document.body; body.appendChild(iframe); setTimeout(function() { body.removeChild(iframe); iframe = null; }) } // 暴露給全局 window.fsInvoke = { share: invokeShare, login: invokeLogin, scan: invokeScan } })(window)
說完了 JS 主動通知客戶端(Native)的方式,下面讓咱們來看下客戶端(Native)主動通知調用 JS。
loadUrl
在安卓 4.4 之前是沒有 evaluatingJavaScript
API 的,只能經過 loadUrl
來調用 JS 方法,只能讓某個 JS 方法執行,可是沒法獲取該方法的返回值。這時咱們須要使用前面提到的 prompt 方法進行兼容,讓 H5 端 經過 prompt 進行數據的發送,客戶端進行攔截並獲取數據。
// mWebView = new WebView(this); //即當前webview對象 mWebView.loadUrl("javascript: 方法名('參數,須要轉爲字符串')"); //ui線程中運行 runOnUiThread(new Runnable() { @Override public void run() { mWebView.loadUrl("javascript: 方法名('參數,須要轉爲字符串')"); Toast.makeText(Activity名.this, "調用方法...", Toast.LENGTH_SHORT).show(); } });
evaluatingJavaScript
在安卓 4.4 以後,evaluatingJavaScript 是一個很是廣泛的調用方式。經過 evaluateJavascript 異步調用 JS 方法,而且能在 onReceiveValue 中拿到返回值。
//異步執行JS代碼,並獲取返回值 mWebView.evaluateJavascript("javascript: 方法名('參數,須要轉爲字符串')", new ValueCallback() { @Override public void onReceiveValue(String value) { //這裏的value即爲對應JS方法的返回值 } });
stringByEvaluatingJavaScriptFromString
在 iOS 中 Native 經過stringByEvaluatingJavaScriptFromString
調用 Html 綁定在 window 上的函數。
// Swift webview.stringByEvaluatingJavaScriptFromString("方法名('參數')") // oc [webView stringByEvaluatingJavaScriptFromString:@"方法名(參數);"];
看完本篇文章,相信你對 Hybrid 有了一個初步的瞭解。雖然本篇比較基礎,可是隻有了解了最本質的底層原理後,才能對現有的解決方案有一個很好的理解,你也能夠去打造適合你和團隊的Hybrid方案
。固然了,後面會有對於 Hybrid 更深刻的探討,敬請期待哦!!
你能夠關注個人同名公衆號【前端森林】,這裏我會按期發一些大前端相關的前沿文章和平常開發過程當中的實戰總結。固然,我也是開源社區的積極貢獻者,github地址https://github.com/Jack-cool,歡迎star!!!