一文搞懂jsBridge的运行机制
csdh11 2024-12-02 16:39 4 浏览
我司的APP是一个典型的混合开发APP,内嵌的都是前端页面,前端页面要做到和原生的效果相似,就避免不了调用一些原生的方法,jsBridge
就是js
和原生
通信的桥梁,本文不讲概念性的东西,而是通过分析一下我司项目中的jsBridge
源码,来从前端角度大概了解一下它是怎么实现的。
js调用方式
先来看一下,js
是怎么来调用某个原生方法的,首先初始化的时候会调用window.WebViewJavascriptBridge.init
方法:
window.WebViewJavascriptBridge.init()
然后如果要调用某个原生方法可以使用下面的函数:
function native (funcName, args = {}, callbackFunc, errorCallbackFunc) {
// 校验参数是否合法
if (args && typeof args === 'object' && Object.prototype.toString.call(args).toLowerCase() === '[object object]' && !args.length) {
args = JSON.stringify(args);
} else {
throw new Error('args不符合规范');
}
// 判断是否是手机环境
if (getIsMobile()) {
// 调用window.WebViewJavascriptBridge对象的callHandler方法
window.WebViewJavascriptBridge.callHandler(
funcName,
args,
(res) => {
res = JSON.parse(res);
if (res.code === 0) {
return callbackFunc(res);
} else {
return errorCallbackFunc(res);
}
}
);
}
}
传入要调用的方法名、参数和回调即可,它先校验了一下参数,然后会调用window.WebViewJavascriptBridge.callHandler
方法。
此外也可以提供回调供原生调用:
window.WebViewJavascriptBridge.registerHandler(funcName, callbackFunc);
接下来看一下window.WebViewJavascriptBridge
对象到底是啥。
安卓
WebViewJavascriptBridge.js
文件内是一个自执行函数,首先定义了一些变量:
// 定义变量
var messagingIframe;
var sendMessageQueue = [];// 发送消息的队列
var receiveMessageQueue = [];// 接收消息的队列
var messageHandlers = {};// 消息处理器
var CUSTOM_PROTOCOL_SCHEME = 'yy';// 自定义协议
var QUEUE_HAS_MESSAGE = '__QUEUE_MESSAGE__/';
var responseCallbacks = {};// 响应的回调
var uniqueId = 1;
根据变量名简单翻译了一下,具体用处接下来会分析。接下来定义了WebViewJavascriptBridge
对象:
var WebViewJavascriptBridge = window.WebViewJavascriptBridge = {
init: init,
send: send,
registerHandler: registerHandler,
callHandler: callHandler,
_fetchQueue: _fetchQueue,
_handleMessageFromNative: _handleMessageFromNative
};
可以看到就是一个普通的对象,上面挂载了一些方法,具体方法暂时不看,继续往下:
var doc = document;
_createQueueReadyIframe(doc);
调用了_createQueueReadyIframe
方法:
function _createQueueReadyIframe (doc) {
messagingIframe = doc.createElement('iframe');
messagingIframe.style.display = 'none';
doc.documentElement.appendChild(messagingIframe);
}
这个方法很简单,就是创建了一个隐藏的iframe
插入到页面,继续往下:
// 创建一个Events类型(基础事件模块)的事件(Event)对象
var readyEvent = doc.createEvent('Events');
// 定义事件名为WebViewJavascriptBridgeReady
readyEvent.initEvent('WebViewJavascriptBridgeReady');
// 通过document来触发该事件
doc.dispatchEvent(readyEvent);
这里定义了一个自定义事件,并直接派发了,其他地方可以像通过监听原生事件一样监听该事件:
document.addEventListener(
'WebViewJavascriptBridgeReady',
function () {
console.log(window.WebViewJavascriptBridge)
},
false
);
这里的用处我理解就是当该jsBridge
文件如果是在其他代码之后引入的话需要保证之前的代码能知道window.WebViewJavascriptBridge
对象何时可用,如果规定该jsBridge
必须要最先引入的话那么就不需要这个处理了。
到这里自执行函数就结束了,接下来看一下最开始的init
方法:
function init (messageHandler) {
if (WebViewJavascriptBridge._messageHandler) {
throw new Error('WebViewJavascriptBridge.init called twice');
}
// init调用的时候没有传参,所以messageHandler=undefined
WebViewJavascriptBridge._messageHandler = messageHandler;
// 当前receiveMessageQueue也只是一个空数组
var receivedMessages = receiveMessageQueue;
receiveMessageQueue = null;
for (var i = 0; i < receivedMessages.length; i++) {
_dispatchMessageFromNative(receivedMessages[i]);
}
}
从初始化的角度来看,这个init
方法似乎啥也没做。接下来我们来看callHandler
方法,看看是如何调用安卓的方法的:
function callHandler (handlerName, data, responseCallback) {
_doSend({
handlerName: handlerName,
data: data
}, responseCallback);
}
处理了一下参数又调用了_doSend
方法:
function _doSend (message, responseCallback) {
// 如果提供了回调的话
if (responseCallback) {
// 生成一个唯一的回调id
var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
// 回调通过id存储到responseCallbacks对象上
responseCallbacks[callbackId] = responseCallback;
// 把该回调id添加到要发送给native的消息里
message.callbackId = callbackId;
}
// 消息添加到消息队列里
sendMessageQueue.push(message);
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}
这个方法首先把调用原生方法时的回调函数通过生成一个唯一的id
保存到最开始定义的responseCallbacks
对象里,然后把该id
添加到要发送的信息上,所以一个message
的结构是这样的:
{
handlerName,
data,
callbackId
}
接着把该message
添加到最开始定义的sendMessageQueue
数组里,最后设置了iframe
的src
属性:yy://__QUEUE_MESSAGE__/
,这其实就是一个自定义协议的url
,我简单搜索了一下,native
会拦截这个url
来做相应的处理,到这里我们就走不下去了,因为不知道原生做了什么事情,简单搜索了一下,发现了这个库:WebViewJavascriptBridge,我司应该是在这个库基础上修改的,结合了网上的一些文章后大概知道了,原生拦截到这个url
后会调用js
的window.WebViewJavascriptBridge._fetchQueue
方法:
function _fetchQueue () {
// 把我们要发送的消息队列转成字符串
var messageQueueString = JSON.stringify(sendMessageQueue);
// 清空消息队列
sendMessageQueue = [];
// 安卓无法直接读取返回的数据,因此还是通过iframe的src和java通信
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);
}
安卓拦截到url
后,知道js
给安卓发送消息了,所以主动调用js
的_fetchQueue
方法,取出之前添加到队列里的消息,因为无法直接读取js
方法返回的数据,所以把格式化后的消息添加到url
上,再次通过iframe
来发送,此时原生又会拦截到yy://return/_fetchQueue/
这个url
,那么取出后面的消息,解析出要其中要执行的原生方法名和参数后执行对应的原生方法,当原生方法执行完后又会主动调用js
的window.WebViewJavascriptBridge._handleMessageFromNative
方法:
function _handleMessageFromNative (messageJSON) {
// 根据之前的init方法的逻辑我们知道receiveMessageQueue是会被设置为null的,所以会走else分支
if (receiveMessageQueue) {
receiveMessageQueue.push(messageJSON);
} else {
_dispatchMessageFromNative(messageJSON);
}
}
看一下_dispatchMessageFromNative
方法做了什么:
function _dispatchMessageFromNative (messageJSON) {
setTimeout(function () {
// 原生发回的消息是字符串类型的,转成json
var message = JSON.parse(messageJSON);
var responseCallback;
// java调用完成,发回的responseId就是我们之前发送给它的callbackId
if (message.responseId) {
// 从responseCallbacks对象里取出该id关联的回调方法
responseCallback = responseCallbacks[message.responseId];
if (!responseCallback) {
return;
}
// 执行回调,js调用安卓方法后到这里顺利收到消息
responseCallback(message.responseData);
delete responseCallbacks[message.responseId];
} else {
// ...
}
});
}
messageJSON
就是原生发回的消息,里面除了执行完原生方法后返回的相关信息外,还带着之前我们传给它的callbackId
,所以我们可以通过这个id
来在responseCallbacks
里找到关联的回调并执行,本次js
调用原生方法流程结束。但是,明显函数里还有不存在id
时的分支,这里是用来干啥的呢,我们前面介绍的都是js
调用原生方法,但是显然,原生也可以直接给js
发消息,比如常见的拦截返回键功能,当原生监听到返回键事件后它会主动发送信息告诉前端页面,页面就可以执行对应的逻辑,这个else
分支就是用来处理这种情况:
function _dispatchMessageFromNative (messageJSON) {
setTimeout(function () {
if (message.responseId) {
// ...
} else {
// 和我们传给原生的消息可以带id一样,原生传给我们的消息也可以带一个id,同时原生内部也会通过这个id关联一个回调
if (message.callbackId) {
var callbackResponseId = message.callbackId;
//如果前端需要再给原生回消息的话那么就带上原生之前传来的id,这样原生就可以通过id找到对应的回调并执行
responseCallback = function (responseData) {
_doSend({
responseId: callbackResponseId,
responseData: responseData
});
};
}
// 我们并没有设置默认的_messageHandler,所以是undefined
var handler = WebViewJavascriptBridge._messageHandler;
// 原生发送的消息里面有处理方法名称
if (message.handlerName) {
// 通过方法名称去messageHandlers对象里查找是否有对应的处理方法
handler = messageHandlers[message.handlerName];
}
try {
// 执行处理方法
handler(message.data, responseCallback);
} catch (exception) {
if (typeof console !== 'undefined') {
console.log('WebViewJavascriptBridge: WARNING: javascript handler threw.', message, exception);
}
}
}
});
}
比如我们要监听原生的返回键事件,我们先通过window.WebViewJavascriptBridge
对象的方法注册一下:
window.WebViewJavascriptBridge.registerHandler('onBackPressed', () => {
// 做点什么...
})
registerHandler
方法如下:
function registerHandler (handlerName, handler) {
messageHandlers[handlerName] = handler;
}
很简单,把我们要监听的事件名和方法都存储到messageHandlers
对象上,然后如果原生监听到返回键事件后会发送如下结构的消息:
{
handlerName: 'onBackPressed'
}
这样就可以通过handlerName
找到我们注册的函数进行执行了。
到此,安卓环境的js
和原生互相调用的逻辑就结束了,总结一下就是:
1.js
调用原生
生成一个唯一的id
,把回调和id
保存起来,然后将要发送的信息(带上本次生成的唯一id)添加到一个队列里,之后通过iframe
发送一个自定义协议的请求,原生拦截到后调用js
的window.WebViewJavascriptBridge
对象的一个方法来获取队列的信息,解析出请求和参数后执行对应的原生方法,然后再把响应(带上前端传来的id)通过调用js
的window.WebViewJavascriptBridge
的指定方法传递给前端,前端再通过id
找到之前存储的回调,进行执行。
2.原生调用js
首先前端需要事先注册要监听的事件,把事件名和回调保存起来,然后原生在某个时刻会调用js
的window.WebViewJavascriptBridge
对象的指定方法,前端根据返回参数的事件名找到注册的回调进行执行,同时原生也会传过来一个id
,如果前端执行完相应逻辑后还要给原生回消息,那么要把该id
带回去,原生根据该id
来找到对应的回调进行执行。
可以看到,js
和原生两边的逻辑都是一致的。
ios
ios
和安卓基本是一致的,部分细节上有点区别,首先是协议不一样,ios
的是这样的:
var CUSTOM_PROTOCOL_SCHEME_IOS = 'https';
var QUEUE_HAS_MESSAGE_IOS = '__wvjb_queue_message__';
然后ios
初始化创建iframe
的时候会发送一个请求:
var BRIDGE_LOADED_IOS = '__bridge_loaded__';
function _createQueueReadyIframe (doc) {
messagingIframe = doc.createElement('iframe');
messagingIframe.style.display = 'none';
if (isIphone()) {
// 这里应该是ios需要先加载一下bridge
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME_IOS + '://' + BRIDGE_LOADED_IOS;
}
doc.documentElement.appendChild(messagingIframe);
}
再然后是ios
获取我们的消息队列时不需要通过iframe
,它能直接获取执行js
函数返回的数据:
function _fetchQueue () {
var messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
return messageQueueString;// 直接返回,不需要通过iframe
}
其他部分都是一样的。
总结
本文分析了一下jsBridge
的源码,可以发现其实是个很简单的东西,但是平时可能就没有去认真了解过它,总想做一些”大“的事情,以至于沦为了一个”好高骛远“的人,希望各位不要像笔者一样。
另外本文分析的只是笔者公司的jsBridge
实现,可能有不一样、更好或更新的实现,欢迎留言探讨。
- 上一篇:「爬虫技巧」浏览器开发者工具使用技巧总结
- 下一篇:存储型XSS的危害
相关推荐
- Micheal Nielsen's神经网络学习之二
-
依然是跟着MichaelNielsen的神经网络学习,基于前一篇的学习,已经大概明白了神经网络的基本结构和BP算法,也能通过神经网络训练数字识别功能,之后我试验了一下使用神经网络训练之前的文本分类,...
- CocoaPods + XCTest进行单元测试 c单元测试工具
-
在使用XCTest进行单元测试时,我们经常会遇到一些CocoaPods中的开源框架的调用,比如“Realm”或“Alamofire”在测试的时候,如果配置不当,会导致“frameworknotfo...
- Java基础知识回顾第四篇 java基础讲解
-
1、&和&&的区别作为逻辑运算符:&(不管左边是什么,右边都参与运算),&&(如果左边为false,右边则不参与运算,短路)另外&可作为位运算符...
- 项目中的流程及类似业务的设计模式总结
-
说到业务流程,可能是我做过的项目中涉及业务最多的一个方面了。除了在流程设计之外,在一些考核系统、产业审批、还有很多地方,都用到相似的设计思路,在此一并总结一下。再说到模式,并不是因为流行才用这个词,而...
- 联想三款显示器首批获得 Eyesafe Certified 2.0 认证
-
IT之家7月31日消息,据外媒报道,三款全新联想显示器是全球首批满足EyesafeCertified2.0的设备。据报道,联想获得EyesafeCertified2.0认证的显...
- maven的生命周期,插件介绍(二) 一个典型的maven构建生命周期
-
1.maven生命周期一个完整的项目构建过程通常包括清理、编译、测试、打包、集成测试、验证、部署等步骤,Maven从中抽取了一套完善的、易扩展的生命周期。Maven的生命周期是抽象的,其中的具体任务都...
- 多线程(3)-基于Object的线程等待与唤醒
-
概述在使用synchronized进行线程同步中介绍了依赖对象锁定线程,本篇文章介绍如何依赖对象协调线程。同synchronized悲观锁一样,线程本身不能等待与唤醒,也是需要对象才能完成等待与唤醒的...
- jquery mobile + 百度地图 + phonegap 写的一个"校园助手"的app
-
1jquerymobile+百度地图+phonegap写的一个"校园助手"的app,使用的是基于Flat-UI的jQueryMobile,请参考:https://github.com/...
- Apache 服务启动不了 apache系统服务启动不了
-
{我是新手,从未遇到此问题,请各位大大勿喷}事由:今天早上上班突然发现公司网站出现问题。经过排查,发现是Apache出现问题。首先检查配置文件没有出问题后,启动服务发现Apache服务能启动,但是没法...
- 健康债和技术债都不能欠 公众号: 我是攻城师(woshigcs)
-
在Solr4.4之后,Solr提供了SolrCloud分布式集群的模式,它带来的主要好处是:(1)大数据量下更高的性能(2)更好扩展性(3)更高的可靠性(4)更简单易用什么时候应该使用Sol...
- Eye Experience怎么用?HTC告诉你 eyebeam怎么用
-
IT之家(www.ithome.com):EyeExperience怎么用?HTC告诉你HTC上周除了发布HTCDesireEYE自拍机和HTCRE管状运动相机之外,还发布了一系列新的智能手机...
- Android系统应用隐藏和应用禁止卸载
-
1、应用隐藏与禁用Android设置中的应用管理器提供了一个功能,就是【应用停用】功能,这是针对某些系统应用的。当应用停用之后,应用的图标会被隐藏,但apk还是存在,不会删除,核心接口就是Packag...
- 计算机软件技术分享--赠人玫瑰,手遗余香
-
一、Netty介绍Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。也就是说,Netty...
- Gecco爬虫框架的线程和队列模型 爬虫通用框架
-
简述爬虫在抓取一个页面后一般有两个任务,一个是解析页面内容,一个是将需要继续抓取的url放入队列继续抓取。因此,当爬取的网页很多的情况下,待抓取url的管理也是爬虫框架需要解决的问题。本文主要说的是g...
- 一点感悟(一) 初识 初读感知的意思
-
时间过得很快,在IT业已从业了两年多。人这一辈子到底需要什么,在路边看着人来人往,大部分人脸上都是很匆忙。上海真是一个魔都,它有魅力,有底蕴,但是一个外地人在这里扎根置业,真的是举全家之力,还贷3...
- 一周热门
-
-
Boston Dynamics Founder to Attend the 2024 T-EDGE Conference
-
IDC机房服务器托管可提供的服务
-
详解PostgreSQL 如何获取当前日期时间
-
新版腾讯QQ更新Windows 9.9.7、Mac 6.9.25、Linux 3.2.5版本
-
一文看懂mysql时间函数now()、current_timestamp() 和sysdate()
-
流星蝴蝶剑:76邵氏精华版,强化了流星,消失了蝴蝶
-
PhotoShop通道
-
查看 CAD文件,电脑上又没装AutoCAD?这款CAD快速看图工具能帮你
-
WildBit Viewer 6.13 快速的图像查看器,具有幻灯片播放和编辑功能
-
光与灯具的专业术语 你知多少?
-
- 最近发表
-
- Micheal Nielsen's神经网络学习之二
- CocoaPods + XCTest进行单元测试 c单元测试工具
- Java基础知识回顾第四篇 java基础讲解
- 项目中的流程及类似业务的设计模式总结
- 联想三款显示器首批获得 Eyesafe Certified 2.0 认证
- maven的生命周期,插件介绍(二) 一个典型的maven构建生命周期
- 多线程(3)-基于Object的线程等待与唤醒
- jquery mobile + 百度地图 + phonegap 写的一个"校园助手"的app
- Apache 服务启动不了 apache系统服务启动不了
- 健康债和技术债都不能欠 公众号: 我是攻城师(woshigcs)
- 标签列表
-
- serv-u 破解版 (19)
- huaweiupdateextractor (27)
- thinkphp6下载 (25)
- mysql 时间索引 (31)
- mydisktest_v298 (34)
- sql 日期比较 (26)
- document.appendchild (35)
- 头像打包下载 (61)
- oppoa5专用解锁工具包 (23)
- acmecadconverter_8.52绿色版 (39)
- oracle timestamp比较大小 (28)
- f12019破解 (20)
- np++ (18)
- 魔兽模型 (18)
- java面试宝典2019pdf (17)
- beamoff下载 (17)
- unity shader入门精要pdf (22)
- word文档批量处理大师破解版 (36)
- pk10牛牛 (22)
- server2016安装密钥 (33)
- mysql 昨天的日期 (37)
- 加密与解密第四版pdf (30)
- pcm文件下载 (23)
- jemeter官网 (31)
- iteye (18)