一步一步带你了解Hybrid开发框架之DsBridge

本文详细介绍了Hybrid开发中Android与H5的交互方式,包括无回调的loadUrl和有回调的evaluateJavascript。接着,文章探讨了js与android交互的原理,强调了@JavascriptInterface注解的重要性。然后,文章重点分析了DsBridge框架的使用方法和工作流程,包括js调用原生、原生调用js的细节。最后,作者指出DsBridge主要是对原生和js交互进行了封装,简化了Hybrid开发中的接口调用。
摘要由CSDN通过智能技术生成

Hybrid开发即 原生与前端的混合开发,常指原生+H5的混合开发。在此之前,我们来梳理下,原生与H5交互的最原始做法(这里基于android)。

android与js交互

android与js交互的核心思想是通过向页面注入javascript代码间接调用H5脚本中定义的方法。WebView为我们提供了两种注入方式,一种有回调,一种无回调。

  • SDK<19,android提供的无回调方法:loadUrl(“javascript:xxx”)
  • SDK>=19,android新增了有回调的方法:evaluateJavascript(String,ValueCallback)

比如H5页面中定义了这样一个方法:

<script type="">
    function show() {
        alert("show()---");
        return "js接收到了消息---";
    }
</script>

那么用上面的方法可以这样调用:

String scrpit = "show()";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        webView.evaluateJavascript(script, new ValueCallback<String>() {
            @Override
            public void onReceiveValue(String value) {
                
            }
        });
} else {
        webView.loadUrl("javascript:" + script);
        }

##js与android交互
js与android交互的核心思想是 android向页面注入一个对象后,js就可以拿到android注入进来的对象,通过这个对象调用其方法。
核心代码:

WebSettings webSettings = wv.getSettings();
webSettings.setJavaScriptEnabled(true);
wv.addJavascriptInterface(new JsApp(),"app");

class JsApp{
    public JsApp(){}
    @JavascriptInterface
    public void call(Object obj){
        
    }
}

这里,我们向页面注入了JsApp对象,命名为app,这样H5就可以通过app来调用JsApp中的方法了,H5中调用方式:

app.call(obj);

我们发现了JsApp中call()加了@JavascriptInterface注解,这是android4.2之后引入的。因为webview允许JavaScript 控制宿主应用程序,这是个很强大的特性,但同时,在4.2的版本前存在重大安全隐患,因为JavaScript 可以使用反射访问注入webview的java对象的public fields,在一个包含不信任内容的WebView中使用这个方法,会允许攻击者去篡改宿主应用程序,使用宿主应用程序的权限执行java代码。因此4.2以后,任何为JS暴露的接口,都需要加@@JavascriptInterface注解,这样可以避免Java对象的fields 被JS访问。

##Hybrid开发框架-DsBridge
基于前面我们讲到的android与js的交互,一些方便我们进行hybrid开发的框架应运而生,比如下面我们要分析的DsBridge框架。

###使用方式
1.添加 JitPack repository 到gradle脚本中

allprojects {
  repositories {
   ...
   maven { url 'https://jitpack.io' }
  }
}

2.添加依赖

dependencies {
	//compile 'com.github.wendux:DSBridge-Android:3.0-SNAPSHOT'
	//support the x5 browser core of tencent
	//compile 'com.github.wendux:DSBridge-Android:x5-3.0-SNAPSHOT'
}

3.新建一个java类,实现API

public class JsApi{
    //同步API
    @JavascriptInterface
    public String testSyn(Object msg)  {
        return msg + "[syn call]";
    }

    //异步API
    @JavascriptInterface
    public void testAsyn(Object msg, CompletionHandler<String> handler) {
        handler.complete(msg+" [ asyn call]");
    }
}

可以看到,DSBridge正式通过类的方式集中、统一地管理API。由于安全原因,所有Java API 必须有"@JavascriptInterface" 标注。

4.添加API类实例到 DWebView

DWebView dwebView= (DWebView) findViewById(R.id.dwebview);
dwebView.addJavascriptObject(new JsApi(), null);

5.在Javascript中调用原生 (Java/Object-c/swift) API ,并注册一个 javascript API供原生调用

js调原生:
//同步调用
dsBridge.call("testSyn","testSyn");

//异步调用
dsBridge.call("testAsyn","testAsyn", function (v) {
  alert(v);
})

//注册 javascript API 
 dsBridge.register('addValue',function(l,r){
     return l+r;
 })

6.在Java中调用 Javascript API

dwebView.callHandler("addValue",new Object[]{3,4},new OnReturnValue<Integer>(){
     @Override
     public void onValue(Integer retValue) {
        Log.d("jsbridge","call succeed,return value is "+retValue);
     }
});

以上使用方法来自github,接下来,我们就针对js调原生、原生调js两个方面来疏通其流程。

###js调原生
打开github上提供的DSBridge-Android工程,在/src/main/assets下存放着前端需要用到的资源,其中我们主要关注js-call-native.html、dsbridge.js。前者是js调原生的H5页面,后者是Node.js写的js模块,主要是提供给前者调用的功能封装。

js-call-native.html:

<!DOCTYPE html>
<html>
<head lang="zh-cmn-Hans">
    <meta charset="UTF-8">
    <title>DSBridge Test</title>
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
    <meta name="viewport" content="width=device-width,initial-scale=0.5,user-scalable=no"/>
    <!--require dsbridge init js-->
    <script src="./dsbridge.js"> </script>
</head>
<body>
<div class="btn" onclick="callSyn()">Synchronous call</div>
<div class="btn" onclick="callAsyn()">Asynchronous call</div>
...
<script>

    function callSyn() {
        alert(dsBridge.call("testSyn", "testSyn"))
    }

    function callAsyn() {
        dsBridge.call("testAsyn","testAsyn", function (v) {
            alert(v)
        })
    }

    function callAsyn_() {
        for (var i = 0; i < 2000; i++) {
            dsBridge.call("testAsyn", "js+" + i, function (v) {
                if (v == "js+1999 [ asyn call]") {
                    alert("All tasks completed!")
                }
            })
        }
    }
    //省略了其他的代码,我们只看这两个方法
...
</script>
</body>
</html>

JsApi.java:

public class JsApi{
    @JavascriptInterface
    public String testSyn(Object msg)  {
        return msg + "[syn call]";
    }

    @JavascriptInterface
    public void testAsyn(Object msg, CompletionHandler<String> handler){
        handler.complete(msg+" [ asyn call]");
    }
    //省略了其他代码,我们只看着两个方法
    ...
}

我们看这里:

    function callSyn() {
        alert(dsBridge.call("testSyn", "testSyn"))
    }
    callSyn();

通过调用callSyn()方法,js就可以调用到android中定义在JsApi.java中testSyn()方法。
callSyn()方法中,走了dsBridge.call(),它是哪里来的呢?这时我们就要关注前面提到的dsBridge.js了,通过<script src="./dsbridge.js"> </script>将dsbridge.js注入到当前页面,这样就可以调用dsbridge模块了。接下来,我们来看dsbridge.js:

var bridge = {
    default:this,// for typescript
    call: function (method, args, cb) {
        var ret = '';
        if (typeof args == 'function') {
            cb = args;
            args = {};
        }
        var arg={data:args===undefined?null:args}//定义arg对象
        if (typeof cb == 'function') { //如果cb参数是一个方法
            var cbName = 'dscb' + window.dscb++; //cbName = dscb1、dscb2、dscb3...
            window[cbName] = cb; //window.dscb1 = cb,因此可以调用 dscb1(v)、dscb2(v)等函数,对应 function callAsyn() {
//                                                                                            dsBridge.call("testAsyn","testAsyn", function (v) {
//                                                                                                alert(v)
//                                                                                            })
//                                                                                        }   中的 function(v){}
            arg['_dscbstub'] = cbName; //arg对象中添加一个属性 _dscbstub = cbName
        }
        arg = JSON.stringify(arg) //arg转为json

        //if in webview that dsBridge provided, call!
        if(window._dsbridge){//是否注入过 _dsbridge对象(android注入的对象)
           ret=  _dsbridge.call(method, arg) //调用android对象的call()
        }else if(window._dswk||navigator.userAgent.indexOf("_dsbridge")!=-1){//如果注入过_dswk对象,或者 userAgent的最后是_dsbridge
           ret = prompt("_dsbridge=" + method, arg); //走android  prompt
        }

       return  JSON.parse(ret||'{}').data
    },
    register: function (name, fun, asyn) {
        var q = asyn ? window._dsaf : window._dsf
        if (!window._dsInit) {
            window._dsInit = true;
            //notify native that js apis register successfully on next event loop
            setTimeout(function () {
                bridge.call("_dsb.dsinit");
            }, 0)
        }
        if (typeof fun == "object") {
            q._obs[name] = fun;
        } else {
            q[name] = fun
        }
    },
    registerAsyn: function (name, fun) {
        this.register(name, fun, true);
    },
    hasNativeMethod: function (name, type) {
        return this.call("_dsb.hasNativeMethod", {name: name, type:type||"all"});
    },
    disableJavascriptDialogBlock: function (disable) {
        this.call("_dsb.disableJavascriptDialogBlock", {
            disable: disable !== false
        })
    }
};

!function () {
    if (window._dsf) return;
    var _close=window.close;
    var ob = {
        //保存JS同步方法
        _dsf: {
            _obs: {}
        },
        //保存JS异步方法
        _dsaf: {
            _obs: {}
        },
        dscb: 0,
        dsBridge: bridge,
        close: function () {
            if(bridge.hasNativeMethod('_dsb.closePage')){
             bridge.call("_dsb.closePage")
            }else{
             _close.cal
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fastsy

打赏一份隆江猪脚饭吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值