巅峰对决 DSBridge vs WebViewJavascriptBridge

5,101 阅读10分钟
原文链接: www.jianshu.com

随着HTML5的不断普及及优化,以及移动端对动态化的需求越来越大,开发者经常需要在app中嵌入一些网页,然后会在web和native之间进行交互,如传递数据,调用函数,而连接web与native需要一个桥梁。本文主要对比github新秀DSBridge与经典的WebViewJavascriptBridge各方面对比。

下面是DSBridge和WebViewJavascriptBridge的github地址:

DSBridge-IOS:github.com/wendux/DSBr…
DSBridge-Android:github.com/wendux/DSBr…

WebViewJavascriptBridge(ios) github.com/marcuswesti…

约定:一下术语“端”指的是native(ios/android), 而 “前端” 特指 web端(javascript);

好桥的标准

  1. 跨平台;这是首要的,必须同时支持ios/android,因为网页会嵌入到端上,而网页代码只有一份,同样的javascript代码必须能同时保证能和ios/android正常通信。
  2. 三端易用;三端指ios 、android和前端。这很重要,因为它会直接影响工作量和代码量。
  3. 双向调用;js可以调用native, native可以调用js;
  4. 支持同步/异步调用;同步用于一般任务,异步主要用于耗时任务,调用方式不同会影响前段代码流程。
  5. 性能、兼容性等。

跨平台

通过github上的信息可以看到DSBridge官方是同时支持ios/android的,但是注意到,DSBridge ios版是支持wkwebview的,而wkwebview也是可以用在osx中,也就是说DSBridge ios版也是可以用于mac开发的。也就是说DSBridge同时支持:ios/android/osx。再来看看WebViewJavascriptBridge,官方说明是支持ios/osx的,但WebViewJavascriptBridge并不支持android, 当然,由于WebViewJavascriptBridge的人气实在太高,也有一些人在android上实现了兼容的版本如这个点我,但是总的来说,并非一家之作,这可能会给日后维护带来问题。而DSBridge是同一个作者,如果将来更新时可以保证双端同步。第一回合,DSBridge胜!

易用性

Javascript Bridge比较特殊,因为它要同时涉及三端,让三个端使用起来都比较容易是很有挑战的一个任务,这不仅是能力层面,也和接口和使用方式的设计息息相关,当然能在三端之间做出最好的平衡,也对作者能力要求比较高(必须同时了解ios/android/web)。 接下来我们分别从三端的接口和使用方式做一个详细对比。

web端

Javascript调用native

假设native有个回显信息的函数,签名如下:

String echo(JSONObject args); //先用java描述

该函数功能是js调用时传递一个字符串msg给端,然后端上再返回 "you put string "+msg; 参数以json传递。

先来看看WebViewJavascriptBridge的调用方式

第一步:复制 setupWebViewJavascriptBridge 函数声明到你的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)
}

第二步:调用 setupWebViewJavascriptBridge,在回调中获得bridge, 然后通过bridge调用native方法:

setupWebViewJavascriptBridge(function(bridge) {
    bridge.callHandler('echo', {'msg':'hello world'}, function responseCallback(responseData) {
        console.log(responseData)
    })
})

我们再来看看DSBridge的调用方式

var bridge = getJsBridge();
var str=bridge.call("testSyn", {msg: "hello world"});
console.log(str)

甚至只需要一行代码:

console.log(getJsBridge().call("testSyn", {msg: "hello world"}))

对比一下,WebViewJavascriptBridge 的setupWebViewJavascriptBridge函数必须在使用者代码中声明,而此函数主要的作用就是安装bridge,安装成功后,只能在回调中获取bridge对象;这么做有两大缺点:

  1. bridge安装细节不应暴漏给用户;setupWebViewJavascriptBridge不应该由用户声明,这个函数从来都不变,安装的过程应该由sdk去做,这样强制要求用户自己声明调用不仅不符合职责分离的软件设计原则,而且还会造成代码冗余,试想每个网页中都要加这么一块代码,不仅麻烦,而且也浪费带宽。(可能你不在乎用户的流量,但是如果你的用户很多而服务器、带宽资源有限,这对服务器来说也是一个问题,笔者阿里云的ecs带宽只有1兆,而网站就跑了六个,同时在线人数超过200人没人将会感觉到卡,如果你是土豪,那无视这个吧)。
  2. 不能直接获取返回值;这点很重要,只能通过回调方式处理返将直接影响前端代码组织逻辑,将会在后面同步/异步对比部分详细讨论。

然而DSBridge 竟是如此优雅!

Natvie 调用 javascript函数

使用WebViewJavascriptBridge时,提供给native调用js函数必须通过bridge在前端进行注册,如:

setupWebViewJavascriptBridge(function(bridge) {
    bridge.registerHandler('jsfun', function(data, responseCallback) {
        console.log("I am js function, arg is:", data)
        responseCallback(data)
    })
})

而DSBridge中完全不需要,只需像正常函数一样将其声明为全局函数即可:

function jsfun(data){
  console.log("I am js function, arg is:", data)
}

如果你的代码不再全局环境下(在一个闭包中),您只需将函数作为window对象的一个属性即可:

window.jsfun=function(data){
  console.log("I am js function, arg is:", data)
}

第二回合 DSBridge完胜!

IOS端

实现API的方式

WebViewJavascriptBridge需要手动注册所有可供js调用的api,如下:

[self.bridge registerHandler:@"echo" handler:^(id data, WVJBResponseCallback responseCallback) {
   //设置js函数的返回值,该值将在js调用时传递的回调中接收 
   responseCallback(...);
}];
[self.bridge registerHandler:@"api2" handler:^(id data, WVJBResponseCallback responseCallback) {
    responseCallback(...);
}];
...

需要注意的是WebViewJavascriptBridge对返回值的处理只能通过block.

我们看看DSBridge,很简单只需要将所有api放到一个类中,然后统一注册即可,还有返回值可以直接返回

//JsApiTest.m
@implementation JsApiTest
- (NSString *) echo:(NSDictionary *) args
{
    return @"...";
}
- (NSString *) api2:(NSDictionary *) args 
{
   return @"..."; 
}
@end
//统一注册
jsApi=[[JsApiTest alloc] init];
webview.JavascriptInterfaceObject=jsApi;

Android端

android端和ios端类似,我们复制一下github.com/gzsll/WebVi… 下的例子(这是android的一个兼容实现):

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都要单独注册,我们来看看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调用的api添加@JavascriptInterface标注即可(出于安全的考虑,关于这个话题,如果有兴趣可自行google)。

对比一下:

WebViewJavascriptBridge为每个api都要单独注册,并且返回值只能通过block或委托;而DSBridge这种统一注册的方式不仅简洁方便,而且所有api放在同一个类中也有助于代码管理,清晰,优雅;而DSBridge同步api可以直接返回值!

综上所述,无论在前端还是在ios/android,在易用性上 DSBridge完胜WebViewJavascriptBridge。

Native调用js

Native调用js方面,两者差异不大,具体请参考github说明,持平。

支持同步/异步调用支持

这是DSBridge大显神威的主要地方,在比较之前,我们先来讨论一下同步/异步对程序流程的影响:如果你是一名前端开发者,想必对异步、回调这些概念已经有所体会,如果你接触过node, 那应该早已深入骨髓。javascript语言从设计为单线程开始就决定了它将与异步相伴终老的局面。纵观晚上node被黑的所有理由中,“过多依赖异步导致代码流程难以控制”首当其冲,随然 node社区一些非常知名的第三方包,有很多都和异步转同步、流程控制相关,如bluebird、async,fibers等. 甚至最新的ECMA标准中引入的generator/yield 、 Promise、async/await等都和同步/异步有关。javascript最成功的地方是异步然而最受诟病的也是异步,当然,我不打算过多讨论js语言,我们回来,看看过多的异步会带来什么问题,试想如下的需求:

假设有一个内嵌在端上的web功能模块,有多个页面,而多个页面之间需要通过端共享一些数据 ,为了说明问题,我们不使用h5本地存储。假设前端的逻辑需要在不同的时段从端上获取不同的数据,用WebViewJavascriptBridge的话,代码整体的流程大概会是下面这个样子:

bridge.callHandler('getData', {'key':'name'}, function responseCallback(responseData) {
    //执行一些操作
    bridge.callHandler('getData', {'key':'age'}, function responseCallback(responseData) {
        //执行一些操作
        ...
        bridge.callHandler('getData', {'key':'sex'}, function responseCallback(responseData) {
          ...
        })
    })
})

我们来仔细看看这种方式有什么问题,首先,获取数据并非耗时操作,端上的API设计成同步,但是由于WebViewJavascriptBridge前端只支持异步调用方式,所以最终的代码将必然是回调套回调,如果交互变多,这种情况会变的更糟,这就是异步编程最大的缺点,流程难以理解,但人类的思维模式是同步的,第一步做什么,然后第二步做什么,用代码来描述就是第一句执行什么,然后第二句执行什么,然而这种基于回调的方式却正好相反,简单一点的应用,不会有太大问题,但是稍微复杂一点的应用,事情将会变的很糟。

然而,DSBridge让一切变的简单!

我们看看用DSBridge实现上述逻辑的大概样子:

var name=bridge.call('getData', {'key':'name'});
//执行一些操作
var age=bridge.call('getData', {'key':'age'});
//执行一些操作
var sex=bridge.call('getData', {'key':'sex'});

简单清晰!

当然,DSBridge也是支持异步调用的,这通常用于耗时的api调用,调用方式请查看github文档。

到目前为止,据作者所知,跨平台的js bridge中,DSBridge是唯一一个支持异步调用的!这一点吊打包括WebViewJavascriptBridge在内现有的其它几乎所有的js bridge

性能、兼容性

从底层的实现来看,WebViewJavascriptBridge是通过iframe和自定义协议在web和native之间传递数据,属于一种间接的做法,因为在android下,js是可以直接和java对象关联通信的,而在ios下UIWebview可以直接使用JavascriptContext实现和安卓类似的功能:js 可以直接和 oc对象关联,而DSBridge在android和ios、uiwebview中是直接通过js和原生对象关联方式直接通信。具体性能虽没有做过专门测试,当从实现方式上来看,DSBridge少了一个iframe中间层,应该会高一些。

关于兼容性,两者差不多,ios都支持7.0以上,DSBridge理论上支持所有android版本,但是考虑到近年来移动端浏览器对h5支持的快速提高,默认的最低版本设置为api16, 你可以根据自己的需求修改这个配置。

总结

通过各方面对比,DSBridge几乎完爆WebViewJavascriptBridge,当然,由于WebViewJavascriptBridge诞生的年代比较早,到现在积累的用户非常多(事实上是世界上目前使用最多的),但是时间在流逝,有些东西必将成为历史,DSBridge的出现也注定着WebViewJavascriptBridge将成为历史,如果DSBridge没有实现这个目标,那只有一个可能,那就是DSBridge的作者对其推广不够。

如果你觉的DSBridge不错,想对这个新丁支持一下,想让更多的人能够知道它,请在github上star一下哦,顶起来。

再次贴出DSBridge项目地址:

DSBridge-IOS:github.com/wendux/DSBr…
DSBridge-Android:github.com/wendux/DSBr…