前言
我們大前端團隊內部 ?每週一練 的知識複習計劃還在繼續,本週主題是 《Hybrid APP 混合應用專題》 ,這期內容比較多,篇幅也相對較長,每個知識點內容也比較多。
之前分享的每週內容,我都整理到掘金收藏集 ?《EFT每週一練》 上啦,歡迎點贊收藏咯??。
注:本文整理資料來源網路,有些圖片/段落找不到原文出處,如有侵權,聯絡刪除。
一、什麼是 Hybrid App,與 Native App 及 Web App 有什麼區別
參考文章:
1.1 主流應用型別
隨著現在移動網際網路的快速發展,市面上目前主流移動應用程式主要分三類:Web App、 Native App 和 Hybrid App。
三者大致關係如下:
1.2 Web App
Web App,即移動端網站,一般指的是基於 Web 的應用,基於瀏覽器執行,無需下載安裝,基本上可以說是觸屏版的網頁應用。這類應用基本上是一個網頁或一系列網頁,旨在在移動螢幕上工作。
Web 網站一般分為兩種:
-
MPA(Multi-page Application)
-
SPA(Single-page Application)
一般的 Web App 是指 SPA 形式開發的網站。
優點:
- 開發和維護成本低,可以跨平臺,除錯方便;
前端人員開發的程式碼,可應用於各大主流瀏覽器(特殊情況可以程式碼進行下相容),沒有新的學習成本,而且可以直接在瀏覽器中除錯。
- 更新最為快速;
由於web app資源是直接部署在伺服器端的,所以只需替換伺服器端檔案,使用者訪問是就已經更新了(當然需要解決一些快取問題)。
- 無需安裝App,不會佔用手機記憶體;
通過瀏覽器即可訪問,無需安裝,使用者使用成本更低。
缺點:
- 效能低,使用者體驗差;
由於是直接通過的瀏覽器訪問,所以無法使用原生的API,操作體驗不好。
- 依賴於網路,頁面訪問速度慢,耗費流量;
Web App每次訪問都必須依賴網路,從服務端載入資源,當網速慢時訪問速度很不理想,特別是在移動端,對網站效能優化要求比較高。
- 功能受限,大量功能無法實現;
只能使用 HTML5 的一些特殊 API ,無法呼叫原生 API ,所以很多功能存在無法實現情況。
- 臨時性入口,使用者留存率低;
這既是它的優點,也是缺點,優點是無需安裝,確定是用完後有時候很難再找到,或者說很難專門為某個web app留存一個入口,導致使用者很難再次使用。
1.3 Native App
Native APP 指的是原生程式,需要使用者下載安裝使用,一般依託於作業系統,有很強的互動,是一個完整的App,可擴充性強,能釋出應用商店。
目前市面上主流的平臺有:Android 和 iOS。
優點:
-
直接依託於作業系統,使用者體驗好,操作流暢,效能穩定;
-
使用者留存率高;
-
功能最為強大,特別是在與系統互動中,幾乎所有功能都能實現;
由於 Native APP 是直接依託於系統,所以可以直接呼叫官方提供的API,功能最為全面(比如本地資源操作,通知,動畫等)。
缺點:
- 開發和維護成本高,無法跨平臺,需要各平臺各自獨立開發;
Android 上基於 Java 開發,iOS 上基 OC 或 Swift 開發,相互之間獨立,必須要有各自的開發人員。
- 門檻較高,原生人員有一定的入門門檻,人才較少;
原生的一個很大特點就是獨立,所以不太容易入門,而且 Android, iOS都需要獨立學習。
- 分發成本高,更新緩慢,特別是釋出應用商店後,需要等到稽核週期;
原生應用更新是一個很大的問題, Android中還能直接下載整包APK進行更新,但是 iOS中,如果是釋出 AppStore ,必須通過 AppStore地址更新,而每次更新都需要稽核,所以無法達到及時更新。
1.4 Hybrid App
Hybrid App 指的是混合開發,也就是半原生半 Web 的開發模式,有跨平臺效果,當然了,實質最終釋出的仍然是獨立的原生APP(各種的平臺有各種的SDK)。
優點:
- 學習和開發成本較低,可以跨平臺,除錯方便;
Hybrid 開發模式下,由原生提供統一的 API 給 JS 呼叫,實際的主要邏輯由 HTML 和 JS 完成,最終放在 webview 中顯示,這樣只需要寫一套程式碼即可,達到跨平臺效果,另外也可以直接在瀏覽器中除錯,很方便。
一般 Hybrid 中的跨平臺最少可以跨三個平臺: Android App ,iOS App ,普通 webkit 瀏覽器。
需要前端人員關注一些原生提供的API,具體的實現無需關心,沒有新的學習內容。
- 維護成本低,功能可複用,並且更容易更新;
雖然沒有 web app 更新那麼快速,但是 Hybrid 中也可以通過原生提供 api ,進行資源主動下載,達到只更新資原始檔,不更新 apk(ipa) 的效果。
- 功能更加完善,效能和體驗要比起 web app 好太多;
因為可以呼叫原生api,所以很多功能只要原生提供出就可以實現,另外效能也比較接近原生。
- 部分效能要求的頁面可用原生實現;
這種模式是原生混合 web ,所以我們完全可以將互動強,效能要求高的頁面用原生寫,然後一些其它頁面用 JS 寫,嵌入 webview 中,達到最佳體驗。
缺點:
- 相比原生,效能仍然有較大損耗;
這種模式受限於 webview 的效能,相比原生而言有不少損耗,體驗無法和原生相比。
- 不適用於互動性較強的app;
這種模式的主要適用:一些新聞閱讀類,資訊展示類的 app ,不適用於一些互動較強或者效能要求較高的 app (比如動畫較多就不適合)。
1.5 三者區別
三者使用場景對比:
三者技術特徵對比:
另外增加 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 |
1.6 三者如何選擇
這裡簡單介紹幾種情況,具體還是要以實際專案技術評估結果為主。
- 選擇純 Native App 模式的情況:
效能要求極高,體驗要求極好,不追求開發效率。
- 選擇 Web App 模式的情況:
不追求使用者體驗和效能,對離線訪問沒要求,正常來說,如果追求效能和體驗,都不會選用web app。
- 選擇 Hybrid App 模式的情況
大部分情況下的App都推薦採用這種模式,這種模式可以用原生來實現要求高的介面,對於一些比較通用型,展示型的頁面完全可以用web來實現,達到跨平臺效果,提升效率。一般好一點的Hybrid方案,都會把資源放在本地的,可以減少網路流量消耗。
- 選擇React Native App模式的情況
追求效能,體驗,同時追求開發效率,而且有一定的技術資本,捨得前期投入。
React Native這種模式學習成本較高,所以需要前期投入不少時間才能達到較好水平,但是有了一定水準後,開發起來它的優勢就體現出來了,效能不遜色原生,而且開發速度也很快
二、什麼是 Cordova,它的優缺點是什麼
參考文章: 《淺談Cordova框架》
2.1 Cordova 簡介
Cordova 是一個用基於 HTML、CSS 和 JavaScript 的,用於建立跨平臺移動應用程式的快速開發平臺。它使開發者能夠利用iPhone、Android、Palm、Symbian、WP7、Bada和Blackberry等智慧手機的核心功能——包括地理定位、加速器、聯絡人、聲音和振動等,此外 Cordova 擁有豐富的外掛,可以呼叫。
也可以用來開發原生和WebView元件之間的外掛介面。
來源:
Cordova 是 PhoneGap 貢獻給 Apache 後的開源專案,是從 PhoneGap 中抽出的核心程式碼,是驅動 PhoneGap 的核心引擎。可以把它們的關係想象成類似於 Webkit 和 Google Chrome 的關係。
2.2 Cordova 架構圖
架構圖介紹:
- Web App
用於存放我們程式的程式碼,包括業務邏輯,還有一些執行需要的資源(如:CSS,JavaScript,圖片,媒體檔案等)。
應用的實現是通過 web 頁面,預設的本地檔名稱是 index.html
,應用執行在原生應用包裝的 WebView 中,這個原生應用是你分發到應用商店中的。
- WebView
Cordova 用的 WebView 可以給應用提供完整使用者訪問介面,使得應用混合了 Webview 和原生的應用元件。
- Cordova Plugins
外掛是 Cordova 生態系統的重要組成部分。它提供了 Cordova 和原生元件相互通訊的介面,並繫結到了標準的裝置API上,這使你能夠通過 JavaScript 呼叫原生程式碼。
2.3 優缺點
優點:
- 跨平臺,開發簡單,學習成本低;
- 框架多,外掛多,可自定義外掛;
- 發展最早,社群資源豐富;
缺點:
- WebView效能低下時,使用者體驗差,反應慢;
- 中文文件資源少;
- 除錯不方便,既不像原生那麼好除錯,也不像純web那種除錯;
三、Cordova 外掛的原理是什麼
Cordova 外掛就是一些附加程式碼用來提供原生元件的 JavaScript 介面,它允許你的 App 可以使用原生裝置的能力,超越了純粹的 Web App。
Cordova 在 iOS 上的實現原理:
3.1 工作流程
- Cordova 發起對原生的請求:
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
找到回撥方法; - 每次 JS 請求,最後發到原生的資料包括:
callbackId
,service
,action
,actionArgs
;
- 為每個請求生成一個唯一標識(
-
原生程式碼拿到
callbackId
、service
、action
及actionArgs
後,會做以下處理:- 根據
service
引數找到對應外掛類; - 根據
action
引數找到外掛類中對應的處理方法,並把actionArgs
作為處理方法請求引數的一部分傳給處理方法; - 處理完成後,把處理結果及
callbackId
返回給 JS 端,JS 端收到後會根據callbackId
找到回撥方法,並把處理結果傳給回撥方法;
- 根據
- JS 端根據
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];
}
}
}
複製程式碼
四、什麼是 JS Bridge,它的作用是什麼
參考文章:《JSBridge的原理》
4.1 JS Bridge 介紹
JSBridge 簡單來講,主要是 給 JavaScript 提供呼叫 Native 功能的介面,讓混合開發中的前端部分可以方便地使用地址位置、攝像頭甚至支付等 Native 功能。
JSBridge 就像其名稱中的 “Bridge” 的意義一樣,是 Native 和非 Native 之間的橋樑,它的核心是 構建 Native 和非 Native 間訊息通訊的通道,而且是 雙向通訊的通道。
JSBridge 另一個叫法及大家熟知的 Hybrid app 技術。
所謂 雙向通訊的通道:
- JS 向 Native 傳送訊息 :
呼叫相關功能、通知 Native 當前 JS 的相關狀態等。
- Native 向 JS 傳送訊息 :
回溯呼叫結果、訊息推送、通知 JS 當前 Native 的狀態等。
4.2. JS Bridge 實現原理
參考文章:《Hybrid APP基礎篇(四)->JSBridge的原理》
Android 和 iOS 的 JSBridge 實現方式:
4.2.1 基本流程
- H5 頁面通過某種方式觸發一個
url scheme
; - Native 捕獲到
url scheme
,並進行分析和處理; - Native 呼叫 H5 的 JSBridge 物件傳遞迴調;
原生的 WebView/UIWebView 控制元件已經能夠和 JS 實現資料通訊了,那為什麼還要 JSBridge呢?
其實使用JSBridge有很多方面的考慮:
- Android4.2以下,
addJavascriptInterface
方式有安全漏掉。 - iOS7以下,JS 無法呼叫 Native。
url scheme
互動方式是一套現有的成熟方案,可以完美相容各種版本,對以前老版本技術的相容。
4.2.1 實現流程(Android 為例)
- 擬定協議,參考 http 制定的協議為:
jsbridge://className:port/methodName?jsonObj
;
className // Android端實現暴露給前端的類名
port // Android返回結果給前端的埠
methodName // 前端需要呼叫的函式
jsonObj // 前端給Android傳遞的引數
複製程式碼
- 新建 HTML 檔案命名為
index.html
, 編寫一個button
繫結click
事件;
<button onclick="JSBridge.call(
'bridge',
'showToast',
{'msg':'Hello JSBridge'},
function(res){
alert(JSON.stringify(res))
}
)">
測試showToast
</button>
複製程式碼
- 新建 JS 檔案命名為
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;
}
複製程式碼
- Android 中會定義一個類
JSBridge.java
來管理暴露給前端使用的函式;
這個類有兩個功能:
- 暴露給前端的函式的動態註冊功能。
- 解析前端資訊,呼叫了 Android 端對應的函式,這個示例中是:
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();
}
}
}
複製程式碼
五、請列舉 Android 與 iOS 平臺下 JS Bridge 的實現方式
這邊程式碼比較多,我使用圖片來展示,大家可以放大來檢視。
5.1 Android 實現方式
5.1.1 Android 呼叫 JS 的 2 種方式
- 通過
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 返回的結果
}
});
}
複製程式碼
5.1.2 JS 呼叫 Android 的 3 種方式
- 通過
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 的方法回撥攔截JS對話方塊方法:
通過 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 端:
5.2 iOS 實現方式
5.2.1 JS 呼叫 iOS 的 2 種方式
- 使用
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
屬性:
5.2.2 iOS 呼叫 JS 的方式
UIWebView
有一個這樣的方法 stringByEvaluatingJavaScriptFromString:
,這個方法可以讓一個 UIWebView
物件執行一段 JS 程式碼,這樣就可以達到 Objective-C 跟 JS 通訊的效果,在 Cordova 的程式碼中多處用到了這個方法,其中最重要的兩處如下:
- 獲取 JS 的請求資料:
- 把 JS 請求的結果返回給 JS 端:
結語
對於初入混合應用開發的小夥伴,這些會有點難度,但是好好理解下那幾張流程圖,再理一理思路,相信會有幫助?
給大家加加油~~
關於我
本文首發在 pingan8787個人部落格,如需轉載請保留個人介紹
Author | 王平安 |
---|---|
pingan8787@qq.com | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
每日文章推薦 | github.com/pingan8787/… |
ES小冊 | js.pingan8787.com |