DSWebview基本解析

923 阅读5分钟

callhandler方法就是将传入的参数json格式化一下,保存了用户设置的回调handler,然后调用dispatchJavaScriptCall
window._handleMessageFromNative()这个方法与dsbridge.js里面的定义的函数对应起来,这个函数执行完毕之后还会通过js调用android方法的方式将处理结果返回给android,就不是通过elevate方法的回调进行处理了。
做了一个版本判断,如果大于4.4则使用evaluate方法执行js代码,如果小于4.4的话直接使用javascript:协议进行调用就可以了。注意这边的回调设置的是null,哪js的处理结果是怎么返回给android的呢,前面有提到就是通过js调用android方法的方式进行的,那就来一探js中对应的方法到底是怎么一回事。

_handleMessageFromNative: function (info) {
            var arg = JSON.parse(info.data);
            var ret = {
                id: info.callbackId,
                complete: true
            }
            var f = this._dsf[info.method];
            var af = this._dsaf[info.method]
            var callSyn = function (f, ob) {
                ret.data = f.apply(ob, arg)
                bridge.call("_dsb.returnValue", ret)
            }
            var callAsyn = function (f, ob) {
                arg.push(function (data, complete) {
                    ret.data = data;
                    ret.complete = complete!==false;
                    bridge.call("_dsb.returnValue", ret)
                })
                f.apply(ob, arg)
            }
            if (f) {
                callSyn(f, this._dsf);
            } else if (af) {
                callAsyn(af, this._dsaf);
            } else {
                //with namespace
                var name = info.method.split('.');
                if (name.length<2) return;
                var method=name.pop();
                var namespace=name.join('.')
                var obs = this._dsf._obs;
                var ob = obs[namespace] || {};
                var m = ob[method];
                if (m && typeof m == "function") {
                    callSyn(m, ob);
                    return;
                }
                obs = this._dsaf._obs;
                ob = obs[namespace] || {};
                m = ob[method];
                if (m && typeof m == "function") {
                    callAsyn(m, ob);
                    return;
                }
            }
        }

首先通过json解析传入的参数,然后判断是异步处理还是同步处理,异步还是同步主要根据传入的方法名,syn.xxx表示就是同步方法,asyn表示就是异步方法,最后关键的就是调用返回方法了

就是解析传回来的obj数据,然后根据callid获取之前保存在hashmap中的handler,也就是在调用callhandler方法时指定的回调,也就是方法的第三个参数。

js调用android中的方法

首先对于dWebview来说,他只认识一个javainterface,因为再dWebView的init方法中,只通过webview的addJavascriptInterface添加了一个一个interface,所以在web页面中调用android这边的方法,始终是通过这个_dsbridge.xxx的形式进行调用,

只是还有开放一些方法往这个唯一注册的一个javascriptinterface中添加一些接口。
看下这个往这个唯一注册的接口添加javascript对应方法的具体实现

就是通过hashmap存储了每一个调用这个方法的接口,map键使用的是命名空间,键传的时候如果没有指定后者是null,则会做处理变成没有命名空间就是不需要xxx.xxx的方式调用而是直接使用xxx就行了,值使用的就是需要添加到这个注册好的接口中的额外的方法。 然后由于只有一个注册的javascript接口,然后又有多个命名空间需要处理所以就在dsbridge.js文件中有封装好一个方法,就是call方法,也就是说以后js想要调用android这边的方法只需要通过dsBridge.call方法进行调用就好了

call: function (method, args, cb) {
        var ret = '';
        if (typeof args == 'function') {
            cb = args;
            args = {};
        }
        var arg={data:args===undefined?null:args}
        if (typeof cb == 'function') {
            var cbName = 'dscb' + window.dscb++;
            window[cbName] = cb;
            arg['_dscbstub'] = cbName;
        }
        arg = JSON.stringify(arg)

        //if in webview that dsBridge provided, call!
        if(window._dsbridge){
           ret=  _dsbridge.call(method, arg)
        }else if(window._dswk||navigator.userAgent.indexOf("_dsbridge")!=-1){
           ret = prompt("_dsbridge=" + method, arg);
        }

       return  JSON.parse(ret||'{}').data
    }

这个方法有三个参数,第一个是js要调用android这边的方法名称,第二个是调用这个方法需要的参数,第三个是回调函数,注意,这个回调函数一般只需要在异步调用中进行赋值,用来接收异步结果,然后 会做个判断,如果args也是函数类型的话,就是默认处理空参数有回调的方式。然后回调的执行方式是这样的,首先将回调注册到web页面的windows属性中,然后将注册的名称存放到args的json数组中,然后去调用唯一注册的js接口中的call方法

public String call(String methodName, String argStr) {
        String error = "Js bridge  called, but can't find a corresponded " +
                "JavascriptInterface object , please check your code!";
        String[] nameStr = parseNamespace(methodName.trim());
        methodName = nameStr[1];
        Object jsb = mJavaScriptNamespaceInterfaceMap.get(nameStr[0]);
        JSONObject ret = new JSONObject();
        try {
            ret.put("code", -1);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        if (jsb == null) {
            PrintDebugInfo(error);
            return ret.toString();
        }
        Object arg = null;
        Method method = null;
        String callback = null;

        try {
            JSONObject args = new JSONObject(argStr);
            if (args.has("_dscbstub")) {
                callback = args.getString("_dscbstub");
            }
            if (args.has("data")) {
                arg = args.get("data");
            }
        } catch (JSONException e) {
            error = String.format("The argument of \"%s\" must be a JSON object string!", methodName);
            PrintDebugInfo(error);
            e.printStackTrace();
            return ret.toString();
        }


        Class<?> cls = jsb.getClass();
        boolean asyn = false;
        try {
            method = cls.getMethod(methodName,
                    new Class[]{Object.class, CompletionHandler.class});
            asyn = true;
        } catch (Exception e) {
            try {
                method = cls.getMethod(methodName, new Class[]{Object.class});
            } catch (Exception ex) {

            }
        }

        if (method == null) {
            error = "Not find method \"" + methodName + "\" implementation! please check if the  signature or namespace of the method is right ";
            PrintDebugInfo(error);
            return ret.toString();
        }


        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
            JavascriptInterface annotation = method.getAnnotation(JavascriptInterface.class);
            if (annotation == null) {
                error = "Method " + methodName + " is not invoked, since  " +
                        "it is not declared with JavascriptInterface annotation! ";
                PrintDebugInfo(error);
                return ret.toString();
            }
        }

        Object retData;
        method.setAccessible(true);
        try {
            if (asyn) {
                final String cb = callback;
                method.invoke(jsb, arg, new CompletionHandler() {

                    @Override
                    public void complete(Object retValue) {
                        complete(retValue, true);
                    }

                    @Override
                    public void complete() {
                        complete(null, true);
                    }

                    @Override
                    public void setProgressData(Object value) {
                        complete(value, false);
                    }

                    private void complete(Object retValue, boolean complete) {
                        try {
                            JSONObject ret = new JSONObject();
                            ret.put("code", 0);
                            ret.put("data", retValue);
                            //retValue = URLEncoder.encode(ret.toString(), "UTF-8").replaceAll("\\+", "%20");
                            if (cb != null) {
                                //String script = String.format("%s(JSON.parse(decodeURIComponent(\"%s\")).data);", cb, retValue);
                                String script = String.format("%s(%s.data);", cb, ret.toString());
                                if (complete) {
                                    script += "delete window." + cb;
                                }
                                //Log.d(LOG_TAG, "complete " + script);
                                mDWebView.evaluateJavascript(script);
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
            } else {
                retData = method.invoke(jsb, arg);
                ret.put("code", 0);
                ret.put("data", retData);
                return ret.toString();
            }
        } catch (Exception e) {
            e.printStackTrace();
            error = String.format("Call failed:The parameter of \"%s\" in Java is invalid.", methodName);
            PrintDebugInfo(error);
            return ret.toString();
        }
        return ret.toString();
    }

这个方法比较长但是逻辑不会复杂,首先就是解析方法参数,命名空间.方法名的数据解析,首先获取命名空间,在通过addjavascriptinterface中维护的hashmap中获取这个命名空间对应的接口类,初始化返回的json值变量,然后解析args参数,主要判断里面是否有回调函数,就是通过args中带过来的数据进行获取。然后根据反射获取这个方法,同时可以判断出是异步方法还是同步方法,然后检查方法是否有使用@JavascriptInterface注解进行修饰,接下来就是方法处理了,首先判断是否是异步方法,如果是异步方法的话,首先通过反射先执行对应的方法,然后因为是java的异步方法,第二个参数是回调函数,在complete回调中,通过webview.evaluateJavascript方法调用在js中写的回调函数,同时组装返回变量ret的值,如果是同步的方法的话,直接反射执行方法,组装返回变量的值,最后就是讲返回变量的值返回了,回到dsbridge.js中的call函数,其实就是对返回的变量值进行json格式化处理。