小驴快运,克拉玛依二手房出售,线路检修
众所周知android 4.2以下的webview存在addjavascriptinterface漏洞的问题,不太了解的同学可参考
@javascriptinterface
因此,公司项目中很早便使用 jsbridge
实现 “js与native的通信” 了。
android端webview启动时,会加载一段webviewjavascriptbridge.js
的js脚本代码。
webview.loadurl(javascript:webviewjavascriptbridge.xxxxx)
调用在webviewjavascriptbridge.js
中提前定义好的xxxxx
方法,将数据传递到js端;reload iframe
将数据传递到native的shouldoverrideurlloading(webview view, string url)
方法的url参数中,android端通过截获url获取js传递过来的参数。以此来实现native与js的通信。
以下是**“ native向js端传递数据,并接受js回调数据 ”**的时序图
/** * native调用js * <p> * call javascript registered handler * 调用javascript处理程序注册 * * @param handlername js中注册的handlername * @param data native传递给js的数据 * @param callback js处理完成后,回调到native */ public void callhandler(string handlername, string data, callbackfunction callback) { dosend(handlername, data, callback); }
注释很全,看注释吧,不作讲解
/** * native 调用 js * <p> * 保存message到消息队列 * * @param handlername js中注册的handlername * @param data native传递给js的数据 * @param responsecallback js处理完成后,回调到native */ private void dosend(string handlername, string data, callbackfunction responsecallback) { logutils.e(tag, "dosend——>data: " + data); logutils.e(tag, "dosend——>handlername: " + handlername); // 创建一个消息体 message m = new message(); // 添加数据 if (!textutils.isempty(data)) { m.setdata(data); } // if (responsecallback != null) { // 创建回调id string callbackstr = string.format(bridgeutil.callback_id_format, ++uniqueid + (bridgeutil.underline_str + systemclock.currentthreadtimemillis())); // 1、js回调native数据时候使用;key: id value: callback (通过js返回的callbackid 可以找到相应的callback方法) responsecallbacks.put(callbackstr, responsecallback); // 1、js回调native数据时候使用;key: id value: callback (通过js返回的callbackid 可以找到相应的callback方法) m.setcallbackid(callbackstr); } // js中注册的方法名称 if (!textutils.isempty(handlername)) { m.sethandlername(handlername); } logutils.e(tag, "dosend——>message: " + m.tojson()); // 添加消息 或者 分发消息到js queuemessage(m); }
message
来封装data数据map<string, callbackfunction> responsecallbacks
,这样js相应方法处理结束后,将js的处理结果返回来的时候,native可通过该callbackid找到对应的callbackfunction,从而完成数据回调。/** * bridgewebview.java * list<message> != null 添加到消息集合否则分发消息 * * @param m message */ private void queuemessage(message m) { logutils.e(tag, "queuemessage——>message: " + m.tojson()); if (startupmessage != null) { startupmessage.add(m); } else { // 分发消息 dispatchmessage(m); } } /** * bridgewebview.java * 分发message 必须在主线程才分发成功 * * @param m message */ void dispatchmessage(message m) { logutils.e(tag, "dispatchmessage——>message: " + m.tojson()); // 转化为json字符串 string messagejson = m.tojson(); //escape special characters for json string 为json字符串转义特殊字符 messagejson = messagejson.replaceall("(\\\\)([^utrn])", "\\\\\\\\$1$2"); messagejson = messagejson.replaceall("(?<=[^\\\\])(\")", "\\\\\""); string javascriptcommand = string.format(bridgeutil.js_handle_message_from_java, messagejson); logutils.e(tag, "dispatchmessage——>javascriptcommand: " + javascriptcommand); // 必须要找主线程才会将数据传递出去 --- 划重点 if (thread.currentthread() == looper.getmainlooper().getthread()) { // 调用js中_handlemessagefromnative方法 this.loadurl(javascriptcommand); } }
javascript:webviewjavascriptbridge._handlemessagefromnative('%s');
将message数据传递到js方法的_handlemessagefromnative当中// native通过loadurl(js_handle_message_from_java),调用js中_handlemessagefromnative方法,实现native向js传递数据 final static string js_handle_message_from_java = "javascript:webviewjavascriptbridge._handlemessagefromnative('%s');";
webviewjavascriptbridge.js
// 1、收到native的消息 // 提供给native调用,receivemessagequeue 在会在页面加载完后赋值为null,所以 function _handlemessagefromnative(messagejson) { // console.log(messagejson); // 添加到消息队列 if (receivemessagequeue) { receivemessagequeue.push(messagejson); } // 分发native消息 _dispatchmessagefromnative(messagejson); }
receivemessagequeue
数组中。//2、分发native消息 function _dispatchmessagefromnative(messagejson) { settimeout(function() { // 解析消息 var message = json.parse(messagejson); // var responsecallback; //java call finished, now need to call js callback function if (message.responseid) { ... } else { // 消息中有callbackid 说明需要将处理完成后,需要回调native端 //直接发送 if (message.callbackid) { // 回调消息的 回调id var callbackresponseid = message.callbackid; // responsecallback = function(responsedata) { // 发送js端的responsedata _dosend({ responseid: callbackresponseid, responsedata: responsedata }); }; } var handler = webviewjavascriptbridge._messagehandler; if (message.handlername) { handler = messagehandlers[message.handlername]; } //查找指定handler try { handler(message.data, responsecallback); } catch (exception) { if (typeof console != 'undefined') { console.log("webviewjavascriptbridge: warning: javascript handler threw.", message, exception); } } } }); }
demo.html
bridge.registerhandler("functioninjs", function(data, responsecallback) { document.getelementbyid("show").innerhtml = ("data from java: = " + data); if (responsecallback) { var responsedata = "javascript says right back aka!"; responsecallback(responsedata); } });
javascript says right back aka!
返回,回调到webviewjavascriptbridge.js _dispatchmessagefromnative注册的responsecallback,从而调用到webviewjavascriptbridge.js 的_dosend方法之中。一下为webviewjavascriptbridge.js 的_dosend
webviewjavascriptbridge.js
// 发送js端的responsedata _dosend({ responseid: callbackresponseid, responsedata: responsedata });
// 3、js将数据发送到native端 // sendmessage add message, 触发native的 shouldoverrideurlloading方法,使native主动向js取数据 // // 把消息队列数据放到shouldoverrideurlloading 的url中不就可以了吗? // 为什么还要native主动取一次,然后再放到shouldoverrideurlloading的url中返回? function _dosend(message, responsecallback) { // 发送的数据存在 if (responsecallback) { // var callbackid = 'cb_' + (uniqueid++) + '_' + new date().gettime(); responsecallbacks[callbackid] = responsecallback; message.callbackid = callbackid; } // 添加到消息队列中 sendmessagequeue.push(message); // 让native加载一个新的页面 messagingiframe.src = custom_protocol_scheme + '://' + queue_has_message; }
sendmessagequeue
消息队列中bridgewebviewclient.java
@override public boolean shouldoverrideurlloading(webview view, string url) { logutils.d(tag, "shouldoverrideurlloading——>url: " + url); try { url = urldecoder.decode(url, "utf-8"); } catch (unsupportedencodingexception e) { e.printstacktrace(); } if (url.startswith(bridgeutil.yy_return_data)) { // 如果是返回数据 webview.handlerreturndata(url); return true; } else if (url.startswith(bridgeutil.yy_override_schema)) { // webview.flushmessagequeue(); return true; } else { return super.shouldoverrideurlloading(view, url); } }
/** * 1、调用js的 _fetchqueue方法,获取js中处理后的消息队列。 * js 中_fetchqueue 方法 中将message数据返回到native的 {@link #bridgewebviewclient.shouldoverrideurlloading}中 * <p> * 2、等待{@link #handlerreturndata} 回调 callback方法 */ void flushmessagequeue() { logutils.d(tag, "flushmessagequeue"); if (thread.currentthread() == looper.getmainlooper().getthread()) { // 调用js的 _fetchqueue方法 bridgewebview.this.loadurl(bridgeutil.js_fetch_queue_from_java, new callbackfunction() { @override public void oncallback(string data) { // ... 此处暂时省略 } }); } }
// 调用js的 _fetchqueue方法。_fetchqueue方法中将message数据返回到native的shouldoverrideurlloading中 final static string js_fetch_queue_from_java = "javascript:webviewjavascriptbridge._fetchqueue();";
webviewjavascriptbridge.js
// 将数据返回给native // 提供给native调用,该函数作用:获取sendmessagequeue返回给native,由于android不能直接获取返回的内容,所以使用url shouldoverrideurlloading 的方式返回内容 function _fetchqueue() { // json数据 var messagequeuestring = json.stringify(sendmessagequeue); // message数据清空 sendmessagequeue = []; // 数据返回到shouldoverrideurlloading //android can't read directly the return data, so we can reload iframe src to communicate with java messagingiframe.src = custom_protocol_scheme + '://return/_fetchqueue/' + encodeuricomponent(messagequeuestring); }
/** * 1、获取到callbackfunction data执行调用并且从数据集移除 * <p> * 2、回调native{@link #flushmessagequeue()} callback方法 * * @param url */ void handlerreturndata(string url) { logutils.d(tag, "handlerreturndata——>url: " + url); // 获取js的方法名称 // _fetchqueue string functionname = bridgeutil.getfunctionfromreturnurl(url); // 获取_fetchqueue 对应的回调方法 callbackfunction f = responsecallbacks.get(functionname); // 获取body message消息体 string data = bridgeutil.getdatafromreturnurl(url); // 回调 native flushmessagequeue callback方法 if (f != null) { logutils.d(tag, "oncallback data" + data); f.oncallback(data); responsecallbacks.remove(functionname); return; } }
@override public void oncallback(string data) { logutils.d(tag, "flushmessagequeue——>data: " + data); // deserializemessage 反序列化消息 list<message> list = null; try { list = message.toarraylist(data); } catch (exception e) { e.printstacktrace(); return; } if (list == null || list.size() == 0) { logutils.e(tag, "flushmessagequeue——>list.size() == 0"); return; } for (int i = 0; i < list.size(); i++) { message m = list.get(i); string responseid = m.getresponseid(); /** * 完成native向js发送信息后的回调 */ // 是否是response callbackfunction if (!textutils.isempty(responseid)) { callbackfunction function = responsecallbacks.get(responseid); string responsedata = m.getresponsedata(); function.oncallback(responsedata); responsecallbacks.remove(responseid); } else { // ... 此处暂时省略 } } }
到这里,jsbridge中native调用js代码的通信,则完成了。
webviewjavascriptbridge.js的_dosend(message, responsecallback)方法中,把message消息队列 放到shouldoverrideurlloading 的url
中直接返回给native不就可以了吗?
为什么还要用_dosend 中 reload iframe " yy://queue_message/ " 触发native的 shouldoverrideurlloading方法,让native主动向js请求一次message队列,然后再放到shouldoverrideurlloading的url中返回给native呢?
个人观点: 觉得,这样将message集中在一起,通过发送一个消息给native,让native主动将所有数据请求回来。避免了js与native的频繁交互。
不太想说了,就到这吧
如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复
Android apk 项目一键打包并上传到蒲公英的实现方法
Android 自定义LineLayout实现满屏任意拖动功能的示例代码
android 限制某个操作每天只能操作指定的次数(示例代码详解)
Android 集成 google 登录并获取性别等隐私信息的实现代码
网友评论