Android Webview与JS交互之DSbridge源码分析

自从有了Webview与JS交互,而我又使用过并且有了一定的理解,真心的体会到让开发者能在web页相关开发为所欲为。demo地址

image.png

比如,在一个活动页面,需要用户登陆后的userId,才能领取活动页面的奖品,怎么获取呢?这个时候,你可以通过js跳转到登录页,登陆成功带着userId回到活动页,就可以进行下一步动作了,美滋滋~
上面只是个简单的例子,本章的主角是DSbridge,不过,在这之前,我们先回顾下,在没封装的时候,Webview与JS交互的实现。(我之前一直在用的方式)

/**js调用android端的方法:**/
//主要看showLog这个方法
public void initView() { 
 webView.getSettings().setJavaScriptEnabled(true);                                     webView.addJavascriptInterface(new TestEntity(),"test");
}
public classTestEntity { 
 @JavascriptInterface
public void showLog(String data) {
      Log.i("android",data);
  }
}

js调用showLog()方法:

function handleAndroidMethod() {
    test.showLog('hhh');
}

android 调用js 的方法:

//js里面方法
function jsMethod(data) {
....方法实现
}

android这边这样调用:

webview.loadUrl('jsMethod('测试')')
  很好,传统的是如上这样实现,我们回到DSbridge,DSbridge其实就是在这样的交互中封装了一下。那么在分析封装实现之前,我们来看看,DSbridge完成交互中的流程。(还是以分享的例子)

android需要处理的:

public void initView() {
webView= (DWebView) findViewById(R.id.webView);
webView.getSettings().setJavaScriptEnabled(true);
webView.setJavascriptInterface(newJsApiEntity(this));
}
public class JsApiEntity {
private Activity mActivity;
public JsApiEntity(Activity mActivity) {
this.mActivity= mActivity;
}
//for synchronous invocation 同步
@JavascriptInterface
String testSyn(JSONObject jsonObject)throwsJSONException {
return jsonObject.getString("msg") +"[syn call]";
}
//for asynchronous invocation  异步
//分享相关
@JavascriptInterface
void share(JSONObject jsonObject,CompletionHandler handler)throwsJSONException {
      EventBus.getDefault().post(jsonObject,"SHOW_SHARE_DIALOG");
      handler.complete("对应后台里面的flag");//回调数据给H5
           }
}

js那边调用:

<script>
    var title = "我是标题";
    var content = "我是内容";
    var url = "落地页的链接";
    function share() {
        dsBridge.call("share",  {title: title,content:content,
            imageUrl: "图片链接哟",url:url}, function(flag){alert(flag);})
    }
</script>

很对,上面就是使用DSbridge之后的使用详情了。其实还是挺有意思的,只要调用dsBridge.call()就可以了,参数是之前定义的方法名称(比如 share)。
DSbridge做的事情,就是在js调用的原生代码时候做了封装,每次调用都是用dsBridge.call()来处理,然后具体的就根据参数来区分了。
我们来看看他的主要源码部分 DWebView.java:

void init() {
......
 super.addJavascriptInterface(new Object() {
            int i = 0;

            @JavascriptInterface
            @Keep
            public String call(String methodName, String args) {
                String error = "Js bridge method called, but there is not a JavascriptInterface object, please set JavascriptInterface object first!";
                if(DWebView.this.jsb == null) {
                    Log.e("SynWebView", error);
                    return "";
                } else {
                    Class cls = DWebView.this.jsb.getClass();

                    try {
                        boolean asyn = false;
                        JSONObject arg = new JSONObject(args);
                        final String callback = "";

                        Method e;
                        try {
                            callback = arg.getString("_dscbstub");
                            arg.remove("_dscbstub");
                            e = cls.getDeclaredMethod(methodName, new Class[]{JSONObject.class, CompletionHandler.class});
                            asyn = true;
                        } catch (Exception var12) {
                            e = cls.getDeclaredMethod(methodName, new Class[]{JSONObject.class});
                        }

                        if(e == null) {
                            error = "ERROR! \n Not find method \"" + methodName + "\" implementation! ";
                            Log.e("SynWebView", error);
                            DWebView.this.evaluateJavascript(String.format("alert(decodeURIComponent(\"%s\"})", new Object[]{error}));
                            return "";
                        }

                        JavascriptInterface annotation = (JavascriptInterface)e.getAnnotation(JavascriptInterface.class);
                        if(annotation != null) {
                            e.setAccessible(true);
                            Object ret;
                            if(asyn) {
                                ret = e.invoke(DWebView.this.jsb, new Object[]{arg, new CompletionHandler() {
                                    public void complete(String retValue) {
                                        this.complete(retValue, true);
                                    }

                                    public void complete() {
                                        this.complete("", true);
                                    }

                                    public void setProgressData(String value) {
                                        this.complete(value, false);
                                    }

                                    private void complete(String retValue, boolean complete) {
                                        try {
                                            if(retValue == null) {
                                                retValue = "";
                                            }

                                            retValue = URLEncoder.encode(retValue, "UTF-8").replaceAll("\\+", "%20");
                                            String e = String.format("%s(decodeURIComponent(\"%s\"));", new Object[]{callback, retValue});
                                            if(complete) {
                                                e = e + "delete window." + callback;
                                            }

                                            DWebView.this.evaluateJavascript(e);
                                        } catch (UnsupportedEncodingException var4) {
                                            var4.printStackTrace();
                                        }

                                    }
                                }});
                            } else {
                                ret = e.invoke(DWebView.this.jsb, new Object[]{arg});
                            }

                            if(ret == null) {
                                ret = "";
                            }

                            return ret.toString();
                        }

                        error = "Method " + methodName + " is not invoked, since  it is not declared with JavascriptInterface annotation! ";
                        DWebView.this.evaluateJavascript(String.format("alert(\'ERROR \\n%s\')", new Object[]{error}));
                        Log.e("SynWebView", error);
                    } catch (Exception var13) {
                        DWebView.this.evaluateJavascript(String.format("alert(\'ERROR! \\n调用失败:函数名或参数错误 [%s]\')", new Object[]{var13.getMessage()}));
                        var13.printStackTrace();
                    }

                    return "";
                }
            }

            @JavascriptInterface
            @Keep
            public void returnValue(int id, String value) {
                OnReturnValue handler = (OnReturnValue)DWebView.this.handlerMap.get(Integer.valueOf(id));
                if(handler != null) {
                    handler.onValue(value);
                    DWebView.this.handlerMap.remove(Integer.valueOf(id));
                }

            }

            @JavascriptInterface
            @Keep
            public void init() {
                DWebView.this.injectJs();
            }
        }, "_dsbridge");

......
 public void setJavascriptInterface(Object object) {
        this.jsb = object;
    }
}

很好,我们先看init方法(@Keep,其实就是为了不被混淆,不用在意),从
Class cls = DWebView.this.jsb.getClass()里面一开始就很明显的说出了意图,使用反射来处理,通过e = cls.getDeclaredMethod获取到this.jsb里面的方法,方法区分同步和异步,还有这个call方法是不是很眼熟?public String call(String methodName, String args) 就是js调用原生代码里面用到的,methodName就是需要调用的方法。
我们回到这个jsb ,其实就是通过setJavascriptInterface方法设置进来的,在上面例子里面其实就是我们定义的JsApiEntity对象,现在,通过反射,把需要调用的Method (e)获取到了。接下来肯定是通过注解来调用方法了,我们接着看:

 JavascriptInterface annotation = (JavascriptInterface)e.getAnnotation(JavascriptInterface.class);
   if(annotation != null) {'....''}

使用了@JavascriptInterface注解的,才会进入里面,里面其实就是通过e.invoke来执行js调用的方法了。

本文demo地址:https://github.com/niyige/DSBridgeWebDemo
有什么问题欢迎交流:893007592@qq.com

相关资料:
https://github.com/wendux/DSBridge-Android

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,332评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,930评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,204评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,348评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,356评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,447评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,862评论 3 394
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,516评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,710评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,518评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,582评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,295评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,848评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,881评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,121评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,737评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,280评论 2 341

推荐阅读更多精彩内容