JsBridge源码详解(一) JS与Native通讯过程(附详细流程图)
通讯前准备
通讯的实现需要注入一段js代码,js代码的注入在页面加载完毕也就是WebViewClent的onPageFinished方法中
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(mWebView, url);
//加载本地通讯桥接的js文件
JsBridgeUtil.webViewLoadLocalJs(view, BridgeX5WebView.toLoadJs);
}
其中JsBridgeUtil的webViewLoadLocalJs方法负责注入js片段,其代码如下
public static void webViewLoadLocalJs(WebView view, String path) {
String jsContent = assetFile2Str(view.getContext(), path);
view.loadUrl("javascript:" + jsContent);
}
负责通讯的js片段以文件形式放在asserts目录下,assetFile2Str将文件以流的方式读取出来转换成字符串格式,然后通过loadUrl就加载了通讯的js片段,js的具体内容下面再一一分析。
至此,通讯的准备工作就完成了,下面来看看具体的通讯实现。
通讯过程
js调用native有个前提条件,就是上面讲到的js片段必须注入完毕,所以这里有部分逻辑是对该部分准备工作的监听,负责通讯的js通过发送事件的方式告诉调用方js:
var readyEvent = doc.createEvent('Events');
readyEvent.initEvent('WebViewJavascriptBridgeReady');
readyEvent.bridge = WebViewJavascriptBridge;
doc.dispatchEvent(readyEvent);
调用方js可以通过如下方式来监听该事件
function connectWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) {
// 准备完毕,可以通讯了
} else {
document.addEventListener(
'WebViewJavascriptBridgeReady'
, function() {
// 准备完毕,可以通讯了
},
false
);
}
}
监听到桥js加载完毕(步骤1),接下来就可以通讯了,调用方js通过调用桥js的callHandler函数来发起通讯(步骤2),来看一个获取H5所运行的设备的信息例子:
function getDeviceInfo(){
window.WebViewJavascriptBridge.callHandler(
'NativeHandler'
, {'funcName' : 'getDeviceInfo'}
, function(responseData) {
document.getElementById("show").innerHTML = "getDeviceInfo involved,responseData from native is " + responseData
}
);
}
各个参数的作用后面会讲到,先来看一下callHandler的实现
function callHandler(handlerName, data, responseCallback) {
_doSend({
handlerName: handlerName,
data: data
}, responseCallback);
}
callHandler调用了内部doSend方法
function _doSend(message, responseCallback) {
if (responseCallback) {
var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
responseCallbacks[callbackId] = responseCallback;
message.callbackId = callbackId;
}
sendMessageQueue.push(message);
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}
dosend方法先是判断是否需要有结果回调,如果有则生成唯一的回调id并将该id与回调方法一起保存进map以便调用结束后通知回调方法。回调id的生成用一个自增长的整数和当前时间戳组成。唯一的id会被放进传递给native的message对象中,使得native执行完毕之后可以告诉js应该将结果返回给哪个id对应的回调方法。
需要注意的是,dosend的方法并没有直接将调用者发送来的数据告诉native,而是将message对象保存进待发送消息队列sendMessageQueue,然后改变iframe标签的src来通知native有消息要取,这是怎么做到的呢?
首先这里要简单介绍下iframe:
iframe 标签规定一个内联框架
一个内联框架被用来在当前 HTML 文档中嵌入另一个文档
iframe能在当前页面加载其他的页面,也就是说制定了其src之后会触发WebViewClient的shouldOverrideUrlLoading方法。
看一下iframe的初始化
var doc = document;
_createQueueReadyIframe(doc);
function _createQueueReadyIframe(doc) {
messagingIframe = doc.createElement('iframe');
messagingIframe.style.display = 'none';
doc.documentElement.appendChild(messagingIframe);
}
在documentElement上增加了一个不可见的iframe标签,使用该标签来触发通讯。那么究竟是如何通讯的呢?
看一下native的WebViewClient的shouldOverrideUrlLoading方法(步骤3)
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith(JsBridgeUtil.YY_RETURN_DATA)) {
try {
url = URLDecoder.decode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
LogUtil.e("decode url fail:" + url, e);
}
mWebView.handlerReturnData(url);
return true;
} else if (url.startsWith(JsBridgeUtil.YY_OVERRIDE_SCHEMA)) {
try {
url = URLDecoder.decode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
LogUtil.e("decode url fail:" + url, e);
}
mWebView.flushMessageQueue();
return true;
} else {
return super.shouldOverrideUrlLoading(view, url);
}
}
其中JsBridgeUtil.YY_RETURN_DATA 为 ”yy://return/“这部分用来处理从js端获取到的返回数据。
JsBridgeUtil.YY_OVERRIDE_SCHEMA为”yy://“ 刚刚桥js的doSend方法改变的iframe的src为CUSTOM_PROTOCOL_SCHEME + ‘
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Redis的高级特性与应用场景(二)
- Redis的高级特性与应用场景(一)
- 022.基于IT论坛案例学习Elasticsearch(一):Filter相关知识
- 耐人寻味的逻辑错误
- 实践搭建Sentry异常中心结合Laravel使用
- 自动化部署 - Laravel Deploy实战
- 正则表达式 | 锚点
- adb shell读取设置手机ocd值以及ocd介绍
- sqli-labs练习(第三、四关)
- 使用 Python 操作 word文档
- sqli-labs练习(第五、六关)
- 分享一个Qt写的SMTP邮件客户端(库)
- mysql学习笔记
- MapReduce的常见输入格式之NlineInputFormat
- MapReduce的常见输入格式之KeyValueTextInputFormat