一、什么是JSBridge?

JSBridge是一种webview侧和native侧进行通信的手段,webview可以通过jsb调用native的能力,native也可以通过jsb在webview上执行一些逻辑。

二、JSB的实现方式

在比较流行的JSBridge中,主要是通过拦截URL请求来达到native端和webview端相互通信的效果的。

这里我们以比较火的WebviewJavascriptBridge为例(源码地址:https://github.com/marcuswestin/WebViewJavascriptBridge),来解析一下它的实现方式。

2-1、在native端和webview端注册Bridge

注册的时候,需要在webview侧和native侧分别注册bridge,其实就是用一个对象把所有函数储存起来。

function registerHandler(handlerName, handler) {messageHandlers[handlerName] = handler;
}
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {_base.messageHandlers[handlerName] = [handler copy];
}

2-2、在webview里面注入初始化代码

function setupWebViewJavascriptBridge(callback) {if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }window.WVJBCallbacks = [callback];var WVJBIframe = document.createElement('iframe');WVJBIframe.style.display = 'none';WVJBIframe.src = 'https://__bridge_loaded__';document.documentElement.appendChild(WVJBIframe);setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}

这段代码主要做了以下几件事:

(1)创建一个名为WVJBCallbacks的数组,将传入的callback参数放到数组内
(2)创建一个iframe,设置不可见,设置src为 https://__bridge_loaded__
(3)设置定时器移除这个iframe

2-3、在native端监听URL请求

iOS中有两种webview,一种是UIWebview,另一种是WKWebview,这里以WKWebview为例:

- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {if (webView != _webView) { return; }__strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationResponse:decisionHandler:)]) {[strongDelegate webView:webView decidePolicyForNavigationResponse:navigationResponse decisionHandler:decisionHandler];}else {decisionHandler(WKNavigationResponsePolicyAllow);}
}

这段代码主要做了以下几件事:
(1)拦截了所有的URL请求并拿到url
(2)首先判断isWebViewJavascriptBridgeURL,判断这个url是不是webview的iframe触发的,具体可以通过host去判断。
(3)继续判断,如果是isBridgeLoadedURL,那么会执行injectJavascriptFile方法,会向webview中再次注入一些逻辑,其中最重要的逻辑就是,在window对象上挂载一些全局变量和WebViewJavascriptBridge属性,具体值如下:

window.WebViewJavascriptBridge = {registerHandler: registerHandler,callHandler: callHandler,disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,_fetchQueue: _fetchQueue,_handleMessageFromObjC: _handleMessageFromObjC
};var sendMessageQueue = [];
var messageHandlers = {};var responseCallbacks = {};
var uniqueId = 1;

(4)继续判断,如果是isQueueMessageURL,那么这就是个处理消息的回调,需要执行一些消息处理的方法(第四步会详细讲)

2-4、webview调用native能力

当native和webview都注册好了Bridge之后,双方就可以互相调用了,这里先介绍webview调用native能力的过程。

2-4-1、webview侧callHandler

当webview调用native时,会调用callHandler方法,这个方法具体逻辑如下:

bridge.callHandler('ObjC Echo', {'key':'value'}, function responseCallback(responseData) {console.log("JS received response:", responseData)
})function callHandler(handlerName, data, responseCallback) {if (arguments.length == 2 && typeof data == 'function') {responseCallback = data;data = null;}_doSend({ handlerName:handlerName, data:data }, responseCallback);
}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;
}

实际上就是先生成一个message,然后push到sendMessageQueue里,然后更改iframe的src。

2-4-2、native侧flushMessageQueue

然后,当native端检测到iframe src的变化时,会走到isQueueMessageURL的判断逻辑,然后执行WKFlushMessageQueue函数,获取到JS侧的sendMessageQueue中的所有message。

- (void)WKFlushMessageQueue {[_webView evaluateJavaScript:[_base webViewJavascriptFetchQueyCommand] completionHandler:^(NSString* result, NSError* error) {if (error != nil) {NSLog(@"WebViewJavascriptBridge: WARNING: Error when trying to fetch data from WKWebView: %@", error);}[_base flushMessageQueue:result];}];
}- (void)flushMessageQueue:(NSString *)messageQueueString{if (messageQueueString == nil || messageQueueString.length == 0) {NSLog(@"WebViewJavascriptBridge: WARNING: ObjC got nil while fetching the message queue JSON from webview. This can happen if the WebViewJavascriptBridge JS is not currently present in the webview, e.g if the webview just loaded a new page.");return;}id messages = [self _deserializeMessageJSON:messageQueueString];for (WVJBMessage* message in messages) {if (![message isKindOfClass:[WVJBMessage class]]) {NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);continue;}[self _log:@"RCVD" json:message];NSString* responseId = message[@"responseId"];if (responseId) {WVJBResponseCallback responseCallback = _responseCallbacks[responseId];responseCallback(message[@"responseData"]);[self.responseCallbacks removeObjectForKey:responseId];} else {WVJBResponseCallback responseCallback = NULL;NSString* callbackId = message[@"callbackId"];if (callbackId) {responseCallback = ^(id responseData) {if (responseData == nil) {responseData = [NSNull null];}WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };[self _queueMessage:msg];};} else {responseCallback = ^(id ignoreResponseData) {// Do nothing};}WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];if (!handler) {NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);continue;}handler(message[@"data"], responseCallback);}}
}

当一个message结构存在responseId的时候说明这个message是执行bridge后传回的。取不到responseId说明是第一次调用bridge传过来的,这个时候会生成一个返回给调用方的message,其reponseId是传过来的message的callbackId,当native执行responseCallback时,会触发_dispatchMessage方法执行webview环境的的js逻辑,将生成的包含responseId的message返回给webview。

2-4-3、webview侧handleMessageFromObjC

function _handleMessageFromObjC(messageJSON) {_dispatchMessageFromObjC(messageJSON);
}function _dispatchMessageFromObjC(messageJSON) {if (dispatchMessagesWithTimeoutSafety) {setTimeout(_doDispatchMessageFromObjC);} else {_doDispatchMessageFromObjC();}function _doDispatchMessageFromObjC() {var message = JSON.parse(messageJSON);var messageHandler;var responseCallback;if (message.responseId) {responseCallback = responseCallbacks[message.responseId];if (!responseCallback) {return;}responseCallback(message.responseData);delete responseCallbacks[message.responseId];} else {if (message.callbackId) {var callbackResponseId = message.callbackId;responseCallback = function(responseData) {_doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });};}var handler = messageHandlers[message.handlerName];if (!handler) {console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);} else {handler(message.data, responseCallback);}}}
}

如果从native获取到的message中有responseId,说明这个message是JS调Native之后回调接收的message,所以从一开始sendData中添加的responseCallbacks中根据responseId(一开始存的时候是用的callbackId,两个值是相同的)取出这个回调函数并执行,这样就完成了一次JS调用Native的流程。

2-4-4、过程总结

1、native端注册jsb
2、webview侧创建iframe,设置src为__bridge_load__
3、native端捕获请求,注入jsb初始化代码,在window上挂载相关对象和方法
4、webview侧调用callHandler方法,并在responseCallback上添加callbackId: responseCallback,并修改iframe的src,触发捕获
5、native收到message,生成一个responseCallback,并执行native侧注册好的方法
6、native执行完毕后,通过webview执行_handleMessageFromObjC方法,取出callback函数,并执行

2-5、native调用webview能力

native调用webview注册的jsb的逻辑是相似的,不过就不是通过触发iframe的src触发执行的了,因为Native可以自己主动调用JS侧的方法。其具体过程是:

1、native侧调用callHandler方法,并在responseCallback上添加callbackId: responseCallback
2、native侧主动调用_handleMessageFromObjC方法,在webview中执行对应的逻辑
3、webview侧执行结束后,生成带有responseId的message,添加到sendMessageQueue中,并修改iframe的src为__wvjb_queue_message__
4、native端拦截到url变化,调用webview的逻辑获取到message,拿到responseId,并执行对应的callback函数

JSBridge原理解析相关推荐

  1. 前端也要懂系列之 JSBridge 原理解析

    移动端盛行时代势必使得混合开发(Hybrid)成为热点. 混合开发是指使用多种开发模开发App的一种开发模式,涉及到两大类技术:原生 Native.Web H5. 原生 Native 主要指 iOS( ...

  2. clickhouse原理解析与应用实践_Hybrid App (混合应用) 技术全解析 方案原理篇

    引言 随着 Web 技术和移动设备的快速发展,Hybrid 技术已经成为一种最主流最常见的方案.一套好的 Hybrid架构方案 能让 App 既能拥有极致的体验和性能,同时也能拥有 Web技术 灵活的 ...

  3. ReactNative与iOS通信原理解析-通信篇

    文章首发个人博客: ReactNative与iOS通信原理解析-通信篇 导语:其实原本是想编写一篇  react-native (下文简称 rn) 在  iOS 中如何实现  jsbridge 的文章 ...

  4. Spark Shuffle原理解析

    Spark Shuffle原理解析 一:到底什么是Shuffle? Shuffle中文翻译为"洗牌",需要Shuffle的关键性原因是某种具有共同特征的数据需要最终汇聚到一个计算节 ...

  5. 秋色园QBlog技术原理解析:性能优化篇:用户和文章计数器方案(十七)

    2019独角兽企业重金招聘Python工程师标准>>> 上节概要: 上节 秋色园QBlog技术原理解析:性能优化篇:access的并发极限及分库分散并发方案(十六)  中, 介绍了 ...

  6. Tomcat 架构原理解析到架构设计借鉴

    ‍ 点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 Tomcat 架构原理解析到架构设计借鉴 Tomcat 发展这 ...

  7. 秋色园QBlog技术原理解析:性能优化篇:数据库文章表分表及分库减压方案(十五)...

    文章回顾: 1: 秋色园QBlog技术原理解析:开篇:整体认识(一) --介绍整体文件夹和文件的作用 2: 秋色园QBlog技术原理解析:认识整站处理流程(二) --介绍秋色园业务处理流程 3: 秋色 ...

  8. CSS实现元素居中原理解析

    原文:CSS实现元素居中原理解析 在 CSS 中要设置元素水平垂直居中是一个非常常见的需求了.但就是这样一个从理论上来看似乎实现起来极其简单的,在实践中,它往往难住了很多人. 让元素水平居中相对比较简 ...

  9. 秋色园QBlog技术原理解析:Web之页面处理-内容填充(八)

    文章回顾: 1: 秋色园QBlog技术原理解析:开篇:整体认识(一) --介绍整体文件夹和文件的作用 2: 秋色园QBlog技术原理解析:认识整站处理流程(二) --介绍秋色园业务处理流程 3: 秋色 ...

最新文章

  1. 可逼近信道容量编码技术之霍夫曼编码的实现
  2. 利用人工智能众包数据,加速药物发现
  3. 一图读懂《“十四五”软件和信息技术服务业发展规划》
  4. PHP随手记1--内置函数date
  5. 网络推广外包——网络推广外包浅析那些年起步就结束的企业网站
  6. 关于ark取得进程的镜像文件路径
  7. ListView列排序功能实现
  8. Jenkins-FQA
  9. [图解]管理九段的新排列
  10. WebQQ3.0体验
  11. 阿里云服务器租用收费标准(精准费用报价更新)
  12. [杂记]就《10.30日华为HR体验官胡玲在心声论坛爆料内部HR腐败行为》有感
  13. android 画圆形 bitmap,在android中画圆形图片的几种办法
  14. GJM : AlloyTouch实战--60行代码搞定QQ看点资料卡
  15. 在线互动课堂Web版初体验(视频连麦互动)
  16. Graph Convolutional Neural Networks for Web-Scale Recommender Systems(用于Web级推荐系统的图形卷积神经网络)
  17. 命令行——rm命令(删除)详解
  18. CentOS7安装教程
  19. 考研复试计算机组成原理篇
  20. php设计模式-Ioc(控制反转)和Di(依赖注入)

热门文章

  1. Font Icon 实践
  2. 网易蜂巢基于万节点kubernetes支撑大规模云应用实践
  3. 百度前端技术学院——DAY2
  4. python的分类算法有哪些_Python8种最常见火爆的机器学习算法
  5. 2012美国大选献金项目(最详细解释)
  6. [渝粤教育] 郑州大学 科举与唐诗 参考 资料
  7. QQ启动时:Initialization failure:0x0000000C 解决方案
  8. 集成IOS 环信SDK
  9. 微信公众号前后端分离项目网页授权登录问题
  10. 51单片机(十七)—— 定时器2寄存器介绍及功能描述