咱們大前端團隊內部 ?每週一練 的知識複習計劃還在繼續,本週主題是 《Hybird APP 混合應用專題》 ,這期內容比較多,篇幅也相對較長,每一個知識點內容也比較多。javascript
以前分享的每週內容,我都整理到掘金收藏集 ?《EFT每週一練》 上啦,歡迎點贊收藏咯??。css
注:本文整理資料來源網絡,有些圖片/段落找不到原文出處,若有侵權,聯繫刪除。html
參考文章:前端
隨着如今移動互聯網的快速發展,市面上目前主流移動應用程序主要分三類:Web App、 Native App 和 Hybrid App。css3
三者大體關係以下:git
Web App,即移動端網站,通常指的是基於 Web 的應用,基於瀏覽器運行,無需下載安裝,基本上能夠說是觸屏版的網頁應用。這類應用基本上是一個網頁或一系列網頁,旨在在移動屏幕上工做。github
Web 網站通常分爲兩種:web
MPA(Multi-page Application)
SPA(Single-page Application)
通常的 Web App 是指 SPA 形式開發的網站。
優勢:
前端人員開發的代碼,可應用於各大主流瀏覽器(特殊狀況能夠代碼進行下兼容),沒有新的學習成本,並且能夠直接在瀏覽器中調試。
因爲web app資源是直接部署在服務器端的,因此只需替換服務器端文件,用戶訪問是就已經更新了(固然須要解決一些緩存問題)。
經過瀏覽器便可訪問,無需安裝,用戶使用成本更低。
缺點:
因爲是直接經過的瀏覽器訪問,因此沒法使用原生的API,操做體驗很差。
Web App每次訪問都必須依賴網絡,從服務端加載資源,當網速慢時訪問速度很不理想,特別是在移動端,對網站性能優化要求比較高。
只能使用 HTML5 的一些特殊 API ,沒法調用原生 API ,因此不少功能存在沒法實現狀況。
這既是它的優勢,也是缺點,優勢是無需安裝,肯定是用完後有時候很難再找到,或者說很難專門爲某個web app留存一個入口,致使用戶很難再次使用。
Native APP 指的是原生程序,須要用戶下載安裝使用,通常依託於操做系統,有很強的交互,是一個完整的App,可拓展性強,能發佈應用商店。
目前市面上主流的平臺有:Android 和 iOS。
優勢:
直接依託於操做系統,用戶體驗好,操做流暢,性能穩定;
用戶留存率高;
功能最爲強大,特別是在與系統交互中,幾乎全部功能都能實現;
因爲 Native APP 是直接依託於系統,因此能夠直接調用官方提供的API,功能最爲全面(好比本地資源操做,通知,動畫等)。
缺點:
Android 上基於 Java 開發,iOS 上基 OC 或 Swift 開發,相互之間獨立,必需要有各自的開發人員。
原生的一個很大特色就是獨立,因此不太容易入門,並且 Android, iOS都須要獨立學習。
原生應用更新是一個很大的問題, Android中還能直接下載整包APK進行更新,可是 iOS中,若是是發佈 AppStore ,必須經過 AppStore地址更新,而每次更新都須要審覈,因此沒法達到及時更新。
Hybrid App 指的是混合開發,也就是半原生半 Web 的開發模式,有跨平臺效果,固然了,實質最終發佈的仍然是獨立的原生APP(各類的平臺有各類的SDK)。
優勢:
Hybrid 開發模式下,由原生提供統一的 API 給 JS 調用,實際的主要邏輯由 HTML 和 JS 完成,最終放在 webview 中顯示,這樣只須要寫一套代碼便可,達到跨平臺效果,另外也能夠直接在瀏覽器中調試,很方便。
通常 Hybrid 中的跨平臺最少能夠跨三個平臺: Android App ,iOS App ,普通 webkit 瀏覽器。
須要前端人員關注一些原生提供的API,具體的實現無需關心,沒有新的學習內容。
雖然沒有 web app 更新那麼快速,可是 Hybrid 中也能夠經過原生提供 api ,進行資源主動下載,達到只更新資源文件,不更新 apk(ipa) 的效果。
由於能夠調用原生api,因此不少功能只要原生提供出就能夠實現,另外性能也比較接近原生。
這種模式是原生混合 web ,因此咱們徹底能夠將交互強,性能要求高的頁面用原生寫,而後一些其它頁面用 JS 寫,嵌入 webview 中,達到最佳體驗。
缺點:
這種模式受限於 webview 的性能,相比原生而言有很多損耗,體驗沒法和原生相比。
這種模式的主要適用:一些新聞閱讀類,信息展現類的 app ,不適用於一些交互較強或者性能要求較高的 app (好比動畫較多就不適合)。
三者使用場景對比:
三者技術特徵對比:
另外增長 ReactNative 一塊兒放入做對比。
NativeApp | WebApp | HybridApp | ReactNativeApp | |
---|---|---|---|---|
原生功能體驗 | 優秀 | 差 | 良好 | 接近優秀 |
渲染性能 | 很是快 | 慢 | 接近快 | 快 |
是否支持設備底層訪問 | 支持 | 不支持 | 支持 | 支持 |
網絡要求 | 支持離線 | 依賴網絡 | 支持離線(資源存本地狀況) | 支持離線 |
更新複雜度 | 高(幾乎老是經過應用商店更新) | 低(服務器端直接更新) | 較低(能夠進行資源包更新) | 較低(能夠進行資源包更新) |
編程語言 | Android(Java),iOS(OC/Swift) | js+html+css3 | js+html+css3 | 主要使用JS編寫,語法規則JSX |
社區資源 | 豐富(Android,iOS單獨學習) | 豐富(大量前端資源) | 有侷限(不一樣的Hybrid相互獨立) | 豐富(統一的活躍社區) |
上手難度 | 難(不一樣平臺須要單獨學習) | 簡單(寫一次,支持不一樣平臺訪問) | 簡單(寫一次,運行任何平臺) | 中等(學習一次,寫任何平臺) |
開發週期 | 長 | 短 | 較短 | 中等 |
開發成本 | 昂貴 | 便宜 | 較爲便宜 | 中等 |
跨平臺 | 不跨平臺 | 全部H5瀏覽器 | Android,iOS,h5瀏覽器 | Android,iOS |
APP發佈 | AppStore | Web服務器 | AppStore | AppStore |
這裏簡單介紹幾種狀況,具體仍是要以實際項目技術評估結果爲主。
性能要求極高,體驗要求極好,不追求開發效率。
不追求用戶體驗和性能,對離線訪問沒要求,正常來講,若是追求性能和體驗,都不會選用web app。
大部分狀況下的App都推薦採用這種模式,這種模式能夠用原生來實現要求高的界面,對於一些比較通用型,展現型的頁面徹底能夠用web來實現,達到跨平臺效果,提高效率。通常好一點的Hybrid方案,都會把資源放在本地的,能夠減小網絡流量消耗。
追求性能,體驗,同時追求開發效率,並且有必定的技術資本,捨得前期投入。
React Native這種模式學習成本較高,因此須要前期投入很多時間才能達到較好水平,可是有了必定水準後,開發起來它的優點就體現出來了,性能不遜色原生,並且開發速度也很快
參考文章: 《淺談Cordova框架》
Cordova 是一個用基於 HTML、CSS 和 JavaScript 的,用於建立跨平臺移動應用程序的快速開發平臺。它使開發者可以利用iPhone、Android、Palm、Symbian、WP七、Bada和Blackberry等智能手機的核心功能——包括地理定位、加速器、聯繫人、聲音和振動等,此外 Cordova 擁有豐富的插件,能夠調用。
也能夠用來開發原生和WebView組件之間的插件接口。
來源:
Cordova 是 PhoneGap 貢獻給 Apache 後的開源項目,是從 PhoneGap 中抽出的核心代碼,是驅動 PhoneGap 的核心引擎。能夠把它們的關係想象成相似於 Webkit 和 Google Chrome 的關係。
架構圖介紹:
用於存放咱們程序的代碼,包括業務邏輯,還有一些運行須要的資源(如:CSS,JavaScript,圖片,媒體文件等)。
應用的實現是經過 web 頁面,默認的本地文件名稱是 index.html
,應用執行在原生應用包裝的 WebView 中,這個原生應用是你分發到應用商店中的。
Cordova 用的 WebView 能夠給應用提供完整用戶訪問界面,使得應用混合了 Webview 和原生的應用組件。
插件是 Cordova 生態系統的重要組成部分。它提供了 Cordova 和原生組件相互通訊的接口,並綁定到了標準的設備API上,這使你可以經過 JavaScript 調用原生代碼。
優勢:
缺點:
Cordova 插件就是一些附加代碼用來提供原生組件的 JavaScript 接口,它容許你的 App 可使用原生設備的能力,超越了純粹的 Web App。
Cordova 在 iOS 上的實現原理:
cordova.exec(successCallback, failCallback, service, action, actionArgs); // successCallback: 成功回調方法 // failCallback: 失敗回調方法 // server: 所要請求的服務名字 // action: 所要請求的服務具體操做 // actionArgs: 請求操做所帶的參數
這五個參數並非直接傳給原生,Cordova JS 端會作如下處理:
callbackId
),並傳給原生端,原生端處理完後,會把 callbackId
連同處理結果一塊兒返回給 JS 端;callbackId
爲 key
,{success:successCallback, fail:failCallback}
爲 value
,把這個鍵值對保存在 JS 端的字典裏,successCallback
與 failCallback
這兩個參數不須要傳給原生,原生返回結果時帶上callbackId
,JS 端就能夠根據 callbackId
找到回調方法;callbackId
, service
, action
, actionArgs
;原生代碼拿到 callbackId
、service
、action
及 actionArgs
後,會作如下處理:
service
參數找到對應插件類;action
參數找到插件類中對應的處理方法,並把 actionArgs
做爲處理方法請求參數的一部分傳給處理方法;callbackId
返回給 JS 端,JS 端收到後會根據 callbackId
找到回調方法,並把處理結果傳給回調方法;callbackId
回調 cordova.js
// 根據 callbackId 及是否成功標識,找到回調方法,並把處理結果傳給回調方法 callbackFromNative: function(callbackId, success, status, args, keepCallback) { var callback = cordova.callbacks[callbackId]; if (callback) { if (success && status == cordova.callbackStatus.OK) { callback.success && callback.success.apply(null, args); } else if (!success) { callback.fail && callback.fail.apply(null, args); } // Clear callback if not expecting any more results if (!keepCallback) { delete cordova.callbacks[callbackId]; } } }
參考文章:《JSBridge的原理》
JSBridge 簡單來說,主要是 給 JavaScript 提供調用 Native 功能的接口,讓混合開發中的前端部分能夠方便地使用地址位置、攝像頭甚至支付等 Native 功能。
JSBridge 就像其名稱中的 「Bridge」 的意義同樣,是 Native 和非 Native 之間的橋樑,它的核心是 構建 Native 和非 Native 間消息通訊的通道,並且是 雙向通訊的通道。
JSBridge 另外一個叫法及你們熟知的 Hybrid app 技術。
所謂 雙向通訊的通道:
調用相關功能、通知 Native 當前 JS 的相關狀態等。
回溯調用結果、消息推送、通知 JS 當前 Native 的狀態等。
參考文章:《Hybrid APP基礎篇(四)->JSBridge的原理》
Android 和 iOS 的 JSBridge 實現方式:
url scheme
;url scheme
,並進行分析和處理;原生的 WebView/UIWebView 控件已經可以和 JS 實現數據通訊了,那爲何還要 JSBridge呢?
其實使用JSBridge有不少方面的考慮:
addJavascriptInterface
方式有安全漏掉。url scheme
交互方式是一套現有的成熟方案,能夠完美兼容各類版本,對之前老版本技術的兼容。jsbridge://className:port/methodName?jsonObj
;className // Android端實現暴露給前端的類名 port // Android返回結果給前端的端口 methodName // 前端須要調用的函數 jsonObj // 前端給Android傳遞的參數
index.html
, 編寫一個 button
綁定 click
事件;<button onclick="JSBridge.call( 'bridge', 'showToast', {'msg':'Hello JSBridge'}, function(res){ alert(JSON.stringify(res)) } )"> 測試showToast </button>
JSBridge.js
, 第2步中的 JSBridge.call
即爲調用 JSBridge.js
中的 call
方法,後面帶了四個參數;call: function (obj, method, params, callback) { console.log(obj+" "+method+" "+params+" "+callback); var port = Util.getPort(); console.log(port); this.callbacks[port] = callback; var uri=Util.getUri(obj,method,params,port); console.log(uri); window.prompt(uri, ""); },
JSBridge.js
中的 call
方法,最後調用了window.prompt
方法,這個方法就是觸發 Android 端 webChromeClient
的回調函數用的。
window.prompt
觸發了 WebChromeClient
(這個須要使用函數WebView.setWebChromeClient
( new WebChromeClietn()
)進行設定);類中的以下回調 onJsPrompt
。這時就完成了前端與 Android端 的通訊了,由於前端的信息都順利經過這個函數傳遞給Android了。
@Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { result.confirm(JSBridge.callJava(view,message)); return true; }
JSBridge.java
來管理暴露給前端使用的函數;這個類有兩個功能:
showToast
函數。解析前端的信息,獲取前端調用的函數名:
Uri uri = Uri.parse(uriString); className = uri.getHost(); param = uri.getQuery(); port = uri.getPort() + ""; String path = uri.getPath(); HashMap< String, Method> methodHashMap = exposedMethod.get(className); Method method = methodHashMap.get(methodName);
經過獲取的函數名,這裏是 showToast
,調用 Android 端的 showToast
函數。
method.invoke(null,webView,new JSONObject(param),new Callback(webView,port));
BridgeImpl.java
來具體的實現暴露給前端的全部函數。這裏的 showToast
函數以下:public static void showToast(WebView webView, JSONObject param, final JSBridge.Callback callback){ String message = param.optString("msg"); Toast.makeText(webView.getContext(),message,Toast.LENGTH_LONG).show(); if(null != callback){ try { JSONObject object = new JSONObject(); object.put("key","value"); object.put("key1","vaule1"); callback.apply(getJSONObject(0,"ok",object)); }catch (Exception e){ e.printStackTrace(); } } }
這邊代碼比較多,我使用圖片來展現,你們能夠放大來查看。
WebView
的 loadUrl()
:JS 代碼調用必定要在 onPageFinished()
回調以後才能調用,不然不會調用。
Web 端代碼:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>前端代碼</title> <script> // Android須要調用的方法 function callJS(){ alert("Android調用了JS的callJS方法"); } </script> </head> </html>
Android 端代碼:
WebView
的 evaluateJavascript()
:// 只須要將第一種方法的loadUrl()換成下面該方法便可 mWebView.evaluateJavascript( "javascript:callJS()", new ValueCallback<String>() { @Override public void onReceiveValue(String value) { //此處爲 js 返回的結果 } }); }
WebView
的 addJavascriptInterface()
進行對象映射:Android 映射:
// 繼承自Object類 public class AndroidtoJs extends Object { // 定義JS須要調用的方法 // 被JS調用的方法必須加入@JavascriptInterface註解 @JavascriptInterface public void hello(String msg) { System.out.println("JS調用了Android的hello方法"); } }
Web:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>前端代碼</title> <script> function callAndroid(){ // 因爲對象映射,因此調用test對象等於調用Android映射的對象 test.hello("js調用了android中的hello方法"); } </script> </head> <body> //點擊按鈕則調用callAndroid函數 <button type="button" id="button1" "callAndroid()"></button> </body> </html>
Android 端:
WebViewClient
的 shouldOverrideUrlLoading ()
方法回調攔截 url
:Web 端:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>前端代碼</title> <script> function callAndroid(){ /*約定的url協議爲:js://webview?arg1=111&arg2=222*/ document.location = "js://webview?arg1=111&arg2=222"; } </script> </head> <!-- 點擊按鈕則調用callAndroid()方法 --> <body> <button type="button" id="button1" onclick="callAndroid()" >點擊調用Android代碼</button> </body> </html>
Android 端:
經過 WebChromeClient 的 onJsAlert()
、onJsConfirm()
、onJsPrompt()
方法回調攔截JS對話框 alert()
、confirm()
、prompt()
消息。
Web 端:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>前端代碼</title> <script> function clickprompt(){ // 調用prompt() var result=prompt("js://demo?arg1=111&arg2=222"); alert("demo " + result); } </script> </head> <!-- 點擊按鈕則調用clickprompt() --> <body> <button type="button" id="button1" onclick="clickprompt()" >點擊調用Android代碼</button> </body> </html>
Android 端:
XMLHttpRequest
發起請求的方式:Web 端:
XMLHttpRequest bridge:
JS 端使用 XMLHttpRequest
發起了一個請求:execXhr.open('HEAD', "/!gap_exec?" + (+new Date()), true);
,請求的地址是 /!gap_exec
;並把請求的數據放在了請求的 header 裏面,見這句代碼:execXhr.setRequestHeader('cmds', iOSExec.nativeFetchMessages());
。
而在 Objective-C 端使用一個 NSURLProtocol
的子類來檢查每一個請求,若是地址是 /!gap_exec
的話,則認爲是 Cordova 通訊的請求,直接攔截,攔截後就能夠經過分析請求的數據,分發到不一樣的插件類(CDVPlugin 類的子類)的方法中:
Cordova 中優先使用這種方式,Cordova.js
中的註釋有說起爲何優先使用 XMLHttpRequest
的方式,及爲何保留第二種 iframe bridge
的通訊方式:
// XHR mode does not work on iOS 4.2, so default to IFRAME_NAV for such devices. // XHR mode’s main advantage is working around a bug in -webkit-scroll, which // doesn’t exist in 4.X devices anyways123
iframe bridge:
在 JS 端建立一個透明的 iframe
,設置這個 ifame
的 src
爲自定義的協議,而 ifame
的 src
更改時,UIWebView
會先回調其 delegate
的 webView:shouldStartLoadWithRequest:navigationType:
方法,關鍵代碼以下:
iframe
的 src
屬性:UIWebView
有一個這樣的方法 stringByEvaluatingJavaScriptFromString:
,這個方法可讓一個 UIWebView
對象執行一段 JS 代碼,這樣就能夠達到 Objective-C 跟 JS 通訊的效果,在 Cordova 的代碼中多處用到了這個方法,其中最重要的兩處以下:
對於初入混合應用開發的小夥伴,這些會有點難度,可是好好理解下那幾張流程圖,再理一理思路,相信會有幫助?
給你們加加油~~
本文首發在 pingan8787我的博客,如需轉載請保留我的介紹
Author | 王平安 |
---|---|
pingan8787@qq.com | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
每日文章推薦 | https://github.com/pingan8787/Leo_Reading/issues |
ES小冊 | js.pingan8787.com |