隨著HTML5的不斷普及及優(yōu)化,以及移動端對動態(tài)化的需求越來越大,開發(fā)者經(jīng)常需要在app中嵌入一些網(wǎng)頁,然后會在web和native之間進(jìn)行交互,如傳遞數(shù)據(jù),調(diào)用函數(shù),而連接web與native需要一個橋梁。DSBridge android 和 ios 版上兩篇文章已經(jīng)介紹完畢,既然DSBridge 號稱是地球上最好用的跨平臺開源 js bridge,那么今天就將它和現(xiàn)在的老大 WebViewJavascriptBridge 全方位對比一下,看看究竟是初生牛犢不怕虎還是嘩眾取寵,又或是實(shí)至名歸......
下面是DSBridge和WebViewJavascriptBridge的github地址:
DSBridge-IOS:https://github.com/wendux/DSBridge-IOS
DSBridge-Android:https://github.com/wendux/DSBridge-Android
WebViewJavascriptBridge(ios) https://github.com/marcuswestin/WebViewJavascriptBridge
約定:一下術(shù)語“端”指的是native(ios/android), 而 “前端” 特指 web端(javascript);
好橋的標(biāo)準(zhǔn)
- 跨平臺;這是首要的,必須同時(shí)支持ios/android,因?yàn)榫W(wǎng)頁會嵌入到端上,而網(wǎng)頁代碼只有一份,同樣的javascript代碼必須能同時(shí)保證能和ios/android正常通信。
- 三端易用;三端指ios 、android和前端。這很重要,因?yàn)樗鼤苯佑绊懝ぷ髁亢痛a量。
- 雙向調(diào)用;js可以調(diào)用native, native可以調(diào)用js;
- 支持同步/異步調(diào)用;同步用于一般任務(wù),異步主要用于耗時(shí)任務(wù),調(diào)用方式不同會影響前段代碼流程。
- 性能、兼容性等。
跨平臺
通過github上的信息可以看到DSBridge官方是同時(shí)支持ios/android的,但是注意到,DSBridge ios版是支持wkwebview的,而wkwebview也是可以用在osx中,也就是說DSBridge ios版也是可以用于mac開發(fā)的。也就是說DSBridge同時(shí)支持:ios/android/osx。再來看看WebViewJavascriptBridge,官方說明是支持ios/osx的,但WebViewJavascriptBridge并不支持android, 當(dāng)然,由于WebViewJavascriptBridge的人氣實(shí)在太高,也有一些人在android上實(shí)現(xiàn)了兼容的版本如這個點(diǎn)我,但是總的來說,并非一家之作,這可能會給日后維護(hù)帶來問題。而DSBridge是同一個作者,如果將來更新時(shí)可以保證雙端同步。第一回合,DSBridge勝!
易用性
Javascript Bridge比較特殊,因?yàn)樗瑫r(shí)涉及三端,讓三個端使用起來都比較容易是很有挑戰(zhàn)的一個任務(wù),這不僅是能力層面,也和接口和使用方式的設(shè)計(jì)息息相關(guān),當(dāng)然能在三端之間做出最好的平衡,也對作者能力要求比較高(必須同時(shí)了解ios/android/web)。 接下來我們分別從三端的接口和使用方式做一個詳細(xì)對比。
web端
Javascript調(diào)用native
假設(shè)native有個回顯信息的函數(shù),簽名如下:
String echo(JSONObject args); //先用java描述
該函數(shù)功能是js調(diào)用時(shí)傳遞一個字符串msg給端,然后端上再返回 "you put string "+msg; 參數(shù)以json傳遞。
先來看看WebViewJavascriptBridge的調(diào)用方式:
第一步:復(fù)制 setupWebViewJavascriptBridge 函數(shù)聲明到你的js中:
function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
window.WVJBCallbacks = [callback];
var WVJBIframe = document.createElement('iframe');
WVJBIframe.style.display = 'none';
WVJBIframe.src = 'https://__bridge_loaded__';
document.documentElement.appendChild(WVJBIframe);
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
第二步:調(diào)用 setupWebViewJavascriptBridge,在回調(diào)中獲得bridge, 然后通過bridge調(diào)用native方法:
setupWebViewJavascriptBridge(function(bridge) {
bridge.callHandler('echo', {'msg':'hello world'}, function responseCallback(responseData) {
console.log(responseData)
})
})
我們再來看看DSBridge的調(diào)用方式:
var bridge = getJsBridge();
var str=bridge.call("testSyn", {msg: "hello world"});
console.log(str)
甚至只需要一行代碼:
console.log(getJsBridge().call("testSyn", {msg: "hello world"}))
對比一下,WebViewJavascriptBridge 的setupWebViewJavascriptBridge函數(shù)必須在使用者代碼中聲明,而此函數(shù)主要的作用就是安裝bridge,安裝成功后,只能在回調(diào)中獲取bridge對象;這么做有兩大缺點(diǎn):
- bridge安裝細(xì)節(jié)不應(yīng)暴漏給用戶;setupWebViewJavascriptBridge不應(yīng)該由用戶聲明,這個函數(shù)從來都不變,安裝的過程應(yīng)該由sdk去做,這樣強(qiáng)制要求用戶自己聲明調(diào)用不僅不符合職責(zé)分離的軟件設(shè)計(jì)原則,而且還會造成代碼冗余,試想每個網(wǎng)頁中都要加這么一塊代碼,不僅麻煩,而且也浪費(fèi)帶寬。(可能你不在乎用戶的流量,但是如果你的用戶很多而服務(wù)器、帶寬資源有限,這對服務(wù)器來說也是一個問題,筆者阿里云的ecs帶寬只有1兆,而網(wǎng)站就跑了六個,同時(shí)在線人數(shù)超過200人沒人將會感覺到卡,如果你是土豪,那無視這個吧)。
- 不能直接獲取返回值;這點(diǎn)很重要,只能通過回調(diào)方式處理返將直接影響前端代碼組織邏輯,將會在后面同步/異步對比部分詳細(xì)討論。
然而DSBridge 竟是如此優(yōu)雅!
Natvie 調(diào)用 javascript函數(shù)
使用WebViewJavascriptBridge時(shí),提供給native調(diào)用js函數(shù)必須通過bridge在前端進(jìn)行注冊,如:
setupWebViewJavascriptBridge(function(bridge) {
bridge.registerHandler('jsfun', function(data, responseCallback) {
console.log("I am js function, arg is:", data)
responseCallback(data)
})
})
而DSBridge中完全不需要,只需像正常函數(shù)一樣將其聲明為全局函數(shù)即可:
function jsfun(data){
console.log("I am js function, arg is:", data)
}
如果你的代碼不再全局環(huán)境下(在一個閉包中),您只需將函數(shù)作為window對象的一個屬性即可:
window.jsfun=function(data){
console.log("I am js function, arg is:", data)
}
第二回合 DSBridge完勝!
IOS端
實(shí)現(xiàn)API的方式
WebViewJavascriptBridge需要手動注冊所有可供js調(diào)用的api,如下:
[self.bridge registerHandler:@"echo" handler:^(id data, WVJBResponseCallback responseCallback) {
//設(shè)置js函數(shù)的返回值,該值將在js調(diào)用時(shí)傳遞的回調(diào)中接收
responseCallback(...);
}];
[self.bridge registerHandler:@"api2" handler:^(id data, WVJBResponseCallback responseCallback) {
responseCallback(...);
}];
...
需要注意的是WebViewJavascriptBridge對返回值的處理只能通過block.
我們看看DSBridge,很簡單只需要將所有api放到一個類中,然后統(tǒng)一注冊即可,還有返回值可以直接返回
//JsApiTest.m
@implementation JsApiTest
- (NSString *) echo:(NSDictionary *) args
{
return @"...";
}
- (NSString *) api2:(NSDictionary *) args
{
return @"...";
}
@end
//統(tǒng)一注冊
jsApi=[[JsApiTest alloc] init];
webview.JavascriptInterfaceObject=jsApi;
Android端
android端和ios端類似,我們復(fù)制一下https://github.com/gzsll/WebViewJavascriptBridge 下的例子(這是android的一個兼容實(shí)現(xiàn)):
webView.registerHandler("api1", new WVJBWebView.WVJBHandler() {
@Override
public void request(Object data, WVJBWebView.WVJBResponseCallback callback) {
callback.callback("return value");
}
});
webView.registerHandler("api2", new WVJBWebView.WVJBHandler() {
@Override
public void request(Object data, WVJBWebView.WVJBResponseCallback callback) {
callback.callback("return value");
}
});
...
每個api都要單獨(dú)注冊,我們來看看DSBridge官方給出的例子:
public class JsApi{
//同步api
@JavascriptInterface
String api1(JSONObject jsonObject) throws JSONException {
return jsonObject.getString("msg") + "[syn call]";
}
//異步api
@JavascriptInterface
void api2(JSONObject jsonObject, CompletionHandler handler) throws JSONException {
handler.complete(jsonObject.getString("msg")+" [asyn call]");
}
}
//注冊
webView.setJavascriptInterface(new JsApi());
DSBridge不用每個api都去手動注冊,只需要將所有api放到一個類中,將需要供js調(diào)用的api添加@JavascriptInterface標(biāo)注即可(出于安全的考慮,關(guān)于這個話題,如果有興趣可自行g(shù)oogle)。
對比一下:
WebViewJavascriptBridge為每個api都要單獨(dú)注冊,并且返回值只能通過block或委托;而DSBridge這種統(tǒng)一注冊的方式不僅簡潔方便,而且所有api放在同一個類中也有助于代碼管理,清晰,優(yōu)雅;而DSBridge同步api可以直接返回值!
綜上所述,無論在前端還是在ios/android,在易用性上 DSBridge完勝WebViewJavascriptBridge。
Native調(diào)用js
Native調(diào)用js方面,兩者差異不大,具體請參考github說明,持平。
支持同步/異步調(diào)用支持
這是DSBridge大顯神威的主要地方,在比較之前,我們先來討論一下同步/異步對程序流程的影響:如果你是一名前端開發(fā)者,想必對異步、回調(diào)這些概念已經(jīng)有所體會,如果你接觸過node, 那應(yīng)該早已深入骨髓。javascript語言從設(shè)計(jì)為單線程開始就決定了它將與異步相伴終老的局面。縱觀晚上node被黑的所有理由中,“過多依賴異步導(dǎo)致代碼流程難以控制”首當(dāng)其沖,隨然 node社區(qū)一些非常知名的第三方包,有很多都和異步轉(zhuǎn)同步、流程控制相關(guān),如bluebird、async,fibers等. 甚至最新的ECMA標(biāo)準(zhǔn)中引入的generator/yield 、 Promise、async/await等都和同步/異步有關(guān)。javascript最成功的地方是異步然而最受詬病的也是異步,當(dāng)然,我不打算過多討論js語言,我們回來,看看過多的異步會帶來什么問題,試想如下的需求:
假設(shè)有一個內(nèi)嵌在端上的web功能模塊,有多個頁面,而多個頁面之間需要通過端共享一些數(shù)據(jù) ,為了說明問題,我們不使用h5本地存儲。假設(shè)前端的邏輯需要在不同的時(shí)段從端上獲取不同的數(shù)據(jù),用WebViewJavascriptBridge的話,代碼整體的流程大概會是下面這個樣子:
bridge.callHandler('getData', {'key':'name'}, function responseCallback(responseData) {
//執(zhí)行一些操作
bridge.callHandler('getData', {'key':'age'}, function responseCallback(responseData) {
//執(zhí)行一些操作
...
bridge.callHandler('getData', {'key':'sex'}, function responseCallback(responseData) {
...
})
})
})
我們來仔細(xì)看看這種方式有什么問題,首先,獲取數(shù)據(jù)并非耗時(shí)操作,端上的API設(shè)計(jì)成同步,但是由于WebViewJavascriptBridge前端只支持異步調(diào)用方式,所以最終的代碼將必然是回調(diào)套回調(diào),如果交互變多,這種情況會變的更糟,這就是異步編程最大的缺點(diǎn),流程難以理解,但人類的思維模式是同步的,第一步做什么,然后第二步做什么,用代碼來描述就是第一句執(zhí)行什么,然后第二句執(zhí)行什么,然而這種基于回調(diào)的方式卻正好相反,簡單一點(diǎn)的應(yīng)用,不會有太大問題,但是稍微復(fù)雜一點(diǎn)的應(yīng)用,事情將會變的很糟。
然而,DSBridge讓一切變的簡單!
我們看看用DSBridge實(shí)現(xiàn)上述邏輯的大概樣子:
var name=bridge.call('getData', {'key':'name'});
//執(zhí)行一些操作
var age=bridge.call('getData', {'key':'age'});
//執(zhí)行一些操作
var sex=bridge.call('getData', {'key':'sex'});
簡單清晰!
當(dāng)然,DSBridge也是支持異步調(diào)用的,這通常用于耗時(shí)的api調(diào)用,調(diào)用方式請查看github文檔。
到目前為止,據(jù)作者所知,跨平臺的js bridge中,DSBridge是唯一一個支持同步調(diào)用的!這一點(diǎn)吊打包括WebViewJavascriptBridge在內(nèi)現(xiàn)有的其它幾乎所有的js bridge。
性能、兼容性
從底層的實(shí)現(xiàn)來看,WebViewJavascriptBridge是通過iframe和自定義協(xié)議在web和native之間傳遞數(shù)據(jù),屬于一種間接的做法,因?yàn)樵赼ndroid下,js是可以直接和java對象關(guān)聯(lián)通信的,而在ios下UIWebview可以直接使用JavascriptContext實(shí)現(xiàn)和安卓類似的功能:js 可以直接和 oc對象關(guān)聯(lián),而DSBridge在android和ios、uiwebview中是直接通過js和原生對象關(guān)聯(lián)方式直接通信。具體性能雖沒有做過專門測試,當(dāng)從實(shí)現(xiàn)方式上來看,DSBridge少了一個iframe中間層,應(yīng)該會高一些。
關(guān)于兼容性,兩者差不多,ios都支持7.0以上,DSBridge理論上支持所有android版本,但是考慮到近年來移動端瀏覽器對h5支持的快速提高,默認(rèn)的最低版本設(shè)置為api16, 你可以根據(jù)自己的需求修改這個配置。
總結(jié)
通過各方面對比,DSBridge幾乎完爆WebViewJavascriptBridge,當(dāng)然,由于WebViewJavascriptBridge誕生的年代比較早,到現(xiàn)在積累的用戶非常多(事實(shí)上是世界上目前使用最多的),但是時(shí)間在流逝,有些東西必將成為歷史,DSBridge的出現(xiàn)也注定著WebViewJavascriptBridge將成為歷史,如果DSBridge沒有實(shí)現(xiàn)這個目標(biāo),那只有一個可能,那就是DSBridge的作者對其推廣不夠。
如果你覺的DSBridge不錯,想對這個新丁支持一下,想讓更多的人能夠知道它,請?jiān)趃ithub上star一下哦,頂起來。
再次貼出DSBridge項(xiàng)目地址:
DSBridge-IOS:https://github.com/wendux/DSBridge-IOS
DSBridge-Android:https://github.com/wendux/DSBridge-Android
預(yù)告:下一篇我們將繼續(xù)將DSBridge 和 老二( github上2.3k star的 https://github.com/lzyzsd/JsBridge)做一個對比,歡迎持續(xù)關(guān)注。