WebViewJavascriptBridge 原理分析
- WebViewJavascriptBridge 原理分析
-
网上好多都是在介绍 WebViewJavascriptBridge如何使用,这篇文章就来说说 WebViewJavascriptBridge 设计原理。
主要从两个过程来讲一下:js调用UIViewController中的代码(Native),Native调用js
1.概述

首先有两个问题:
a.Native(中的UIWebView)是否可以直接调用js method(方法)? 可以。
b.js 是否可以直接调用Native的mthod?不行。
明确上述两个问题,那么上图就不难明白了,webpage中的js method和webview本地的method之间关系。那WebViewJavascriptBridge出现是否解决这个问题(这个问题就是让js可以直接调用native的method)呢?答案是否定的?没有本质还是用uiwebview的代理方法进行字段拦截(判断url的scheme),实现js间接调用native的method。
我们来看WebViewJavascriptBridge提供的demo:

主要的核心是下面两个,接下来我们就来讨论一下其设计原理。
2.js调用Native method
在概述中说过,js是不能直接调用native的method所以,需要借助- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType,这个方法大家不陌生,每次在重新定向URL的时候,这个方法就会被触发,通常情况,我们会在这里做一些拦截完成js和本地的间接交互什么的。那么WebViewJavascriptBridge也不另外,也是这么做。
我们先来看看在ExampleApp.html文件中点击一个按钮发起请求的代码:
12345678910var callbackButton = document.getElementById('buttons').appendChild(document.createElement('button'))callbackButton.innerHTML ='Fire testObjcCallback'callbackButton.onclick = function(e) {e.preventDefault()log('JS calling handler "testObjcCallback"')//1bridge.callHandler('testObjcCallback', {'foo':'cccccccccccc'}, function(response) {log('JS got response', response)})}估计大家大体都能看懂,唯独有疑问的地方是:
1234bridge.callHandler('testObjcCallback', {'foo':'cccccccccccc'}, function(response) {log('JS got response', response)})}这段代码先不说,上面代码就是一个按钮的普通单击事件方法。我们一起想一下,如果这个按钮需要被点击之后调用native中的funtion函数,之后需要把这个(native的)funtion函数处理结果返回给js中的方法继续处理。这个是我们需求,带着这个需求我们看一下这个方法,testObjcCallBack这个我们猜测一下应该native中的方法或者一个能够调用到方法的name/id,后面这个是个json{‘foo’:‘ccccccccccccc’},应该是个参数,那么后面这个方法一看log应该知道,是对native返回的result进行处理的方法。拿具体是不是呢?只要找到callHandler方法就知道了。
在文件WebViewJavascriptBridge.js.txt里面我们找找这个方法:
123function callHandler(handlerName, data, responseCallback) {_doSend({ handlerName:handlerName, data:data }, responseCallback)}这里又多了一个方法叫_doSend连个参数 第1个是字典key-value定义,第二个是一个方法的指针(看看上面的方法你就知道了),那我们必须在同一个文件里面看看能不能找到这个_doSend方法:
123456789function _doSend(message, responseCallback) {if(responseCallback) {var callbackId ='cb_'+(uniqueId++)+'_'+newDate().getTime()responseCallbacks[callbackId] = responseCallbackmessage['callbackId'] = callbackId}sendMessageQueue.push(message)messagingIframe.src = CUSTOM_PROTOCOL_SCHEME +'://'+ QUEUE_HAS_MESSAGE}找到了。
逐行分析一下,变量callbackId是个字符串,responseCallBacks[] 一看就知道是个字典 ,这个字典把回掉(我们猜测)的方法responseCallback给保存起来,这Key(也就是callbackId)应该是唯一的,通过计数和时间应该知道这个字符串应该是唯一的,message也是一个字典,这是给message添加了一个新的key-value。干嘛呢?我也不知道,我们来看看sendMessageQueue是什么,大家一个push就知道应该是个数组。他吧一个字典放到一个消息队列中(数组队列),让后产生一个src(url scheme)。
有两个变量我们看看:
12var CUSTOM_PROTOCOL_SCHEME ='wvjbscheme'var QUEUE_HAS_MESSAGE ='__WVJB_QUEUE_MESSAGE__'干嘛用,肯定是给webview 的 delegate判断用的,你感觉呢?(肯定是)
下面是在文件:WebViewJavascriptBridge.m
好了到了这里大家猜猜这个要干嘛?肯定是要发url让web截取对吧?那还用问啊,肯定是啊,已经说过了js能不能调用native的funtion函数?不能。我们来看看这个messagingIframe是:
123456function _createQueueReadyIframe(doc) {messagingIframe = doc.createElement('iframe')messagingIframe.style.display ='none'messagingIframe.src = CUSTOM_PROTOCOL_SCHEME +'://'+ QUEUE_HAS_MESSAGEdoc.documentElement.appendChild(messagingIframe)}原来就是iframe,这个就不同给大家解释了。好了src一产生就会出现什么,uiwebview代理回掉截获,此时我们把目光回到UIWebview的Native下面:
123456789101112131415161718192021222324252627- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {if(webView != _webView) {returnYES; }NSURL *url = [request URL];__strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;if([[url scheme] isEqualToString:kCustomProtocolScheme]){if([[url host] isEqualToString:kQueueHasMessage]){//会走这里[self _flushMessageQueue];}else{NSLog(@"WebViewJavascriptBridge: WARNING: Received unknown WebViewJavascriptBridge command %@://%@", kCustomProtocolScheme, [url path]);}returnNO;}elseif(strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]){return[strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];}else{returnYES;}}一看就头大,哈哈,是,我也头大。看看上面的注释说 会走这里,我们看看为什么会走那里,最外圈的if([url scheme])判断是
#define kCustomProtocolScheme @"wvjbscheme"
这个定义是什么意思,我们先不做解释,刚才我们说过js不能直接调用native的function,大家只要记住这点,接着往下走就是了。至于为什么走这里,自己看代码(上文有提到),我们看看_flushMessageQueue:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253- (void)_flushMessageQueue {NSString *messageQueueString = [_webView stringByEvaluatingJavaScriptFromString:@"WebViewJavascriptBridge._fetchQueue();"];//json转成数组id messages = [self _deserializeMessageJSON:messageQueueString];if(![messages isKindOfClass:[NSArrayclass]]) {NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [messagesclass], messages);return;}for(WVJBMessage* message in messages) {if(![message isKindOfClass:[WVJBMessageclass]]) {NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [messageclass], message);continue;}[self _log:@"RCVD"json:message];//用于js回掉NSString* responseId = message[@"responseId"];if(responseId) {WVJBResponseCallback responseCallback = _responseCallbacks[responseId];responseCallback(message[@"responseData"]);[_responseCallbacks removeObjectForKey:responseId];}else{WVJBResponseCallback responseCallback = NULL;NSString* callbackId = message[@"callbackId"];if(callbackId) {responseCallback = ^(id responseData) {if(responseData == nil) {responseData = [NSNullnull];}WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };[self _queueMessage:msg];};}else{responseCallback = ^(id ignoreResponseData) {// Do nothing};}WVJBHandler handler;if(message[@"handlerName"]) {handler = _messageHandlers[message[@"handlerName"]];}else{handler = _messageHandler;}if(!handler) {[NSException raise:@"WVJBNoHandlerException"format:@"No handler for message from JS: %@", message];}handler(message[@"data"], responseCallback);}}}这下牛逼了,不忍直视啊!这么多,哈哈,多不可怕,可怕是你坚持不下去了。
我们逐行来看:
NSString *messageQueueString = [_webView stringByEvaluatingJavaScriptFromString:@"WebViewJavascriptBridge._fetchQueue();"];
我们必须回去到js文件中去,这里是webview直接调用js中的方法:
12345function _fetchQueue() {var messageQueueString = JSON.stringify(sendMessageQueue)sendMessageQueue = []returnmessageQueueString}谢天谢地这个方法代码不多,这个消息很眼熟,SendMessageQueue,刚才我们说什么来?他是一个字典,那里面有哪些东西,我么来看看
handlerName:handlerName,
data:data,
callbackId:callbackId
这个消息字典此时被取出来准备做什么,这里提示下我们已经走到webview 的delegate里面了,所以拿到这些信息肯定是调用native的method对吧?肯定是的。接着往下走,接着会把json字符串转成数组,然后进行判断,
1NSString* responseId = message[@"responseId"];有没有responseid,你说又没,肯定没有啊(你不行看看上面),所以就这这里了
1234567891011121314151617181920212223242526272829WVJBResponseCallback responseCallback = NULL;NSString* callbackId = message[@"callbackId"];if(callbackId) {responseCallback = ^(id responseData) {if(responseData == nil) {responseData = [NSNullnull];}WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };[self _queueMessage:msg];};}else{responseCallback = ^(id ignoreResponseData) {// Do nothing};}WVJBHandler handler;if(message[@"handlerName"]) {handler = _messageHandlers[message[@"handlerName"]];}else{handler = _messageHandler;}if(!handler) {[NSException raise:@"WVJBNoHandlerException"format:@"No handler for message from JS: %@", message];}handler(message[@"data"], responseCallback);这部分是重点,到底他是怎么要调用本地function的,callbackId大家熟悉吧,判断是否为空,不为空给他指定一个block,这个不说了,block指定,此时不调用(手动调用才会执行),这个刚才说了用来处理native的function处理的result用于把处理后的值返回给js的,接着往下去,看到handler这个方法会从message找到handlerName,这里我们看一下多了一个_messageHandlers字典,从这个字典获取一个block(WVJBHandler是一个block),直接执行了。那我们看看_messageHandlers是怎么被添加block的:
123- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {_messageHandlers[handlerName] = [handler copy];}那又是谁调用了这个方法:
找到了(在文件 ExampleAppViewController.m的viewdidload中),这里有方法testObjecCallback
1234[_bridge registerHandler:@"testObjcCallback"handler:^(id data, WVJBResponseCallback responseCallback) {NSLog(@"testObjcCallback called: %@", data);responseCallback(@"Response from testObjcCallback");}];有点乱了。刚才我们的思路都是倒推的,如果我们整过来,首先肯定是viewdidload初始化,初始化之后会把这个block加入到_messageHandlers中,之后因为js调用动态读取这个block调用,在调用之前,我们又把定一个block付给回掉处理的responseCallback的block,这个block在handler中调用而调用,有点绕,自己可以多想想。
我们接着来看看:
12345678responseCallback = ^(id responseData) {if(responseData == nil) {responseData = [NSNullnull];}WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };[self _queueMessage:msg];};这个就是你绕的地方,他是后被定义的,所以一开不执行,只有在处理数据后回调才会被调用,这里有个方法_queueMessage:
1234567- (void)_queueMessage:(WVJBMessage*)message {if(_startupMessageQueue) {[_startupMessageQueue addObject:message];}else{[self _dispatchMessage:message];}}这里面还有个方法:
12345678910111213141516171819202122- (void)_dispatchMessage:(WVJBMessage*)message {NSString *messageJSON = [self _serializeMessage:message];[self _log:@"SEND"json:messageJSON];messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\\"withString:@"\\\\"];messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\""withString:@"\\\""];messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\'"withString:@"\\\'"];messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\n"withString:@"\\n"];messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\r"withString:@"\\r"];messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\f"withString:@"\\f"];messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2028"withString:@"\\u2028"];messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2029"withString:@"\\u2029"];NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];if([[NSThread currentThread] isMainThread]) {[_webView stringByEvaluatingJavaScriptFromString:javascriptCommand];}else{__strong WVJB_WEBVIEW_TYPE* strongWebView = _webView;dispatch_sync(dispatch_get_main_queue(), ^{[strongWebView stringByEvaluatingJavaScriptFromString:javascriptCommand];});}}我们在回到WebViewJavascriptBridge.js.txt文件中看到
1234567function _handleMessageFromObjC(messageJSON) {if(receiveMessageQueue) {receiveMessageQueue.push(messageJSON)}else{//肯定走这个 为什么呢?_dispatchMessageFromObjC(messageJSON)}}再来看看:
123456789101112131415161718192021222324252627282930313233343536function _dispatchMessageFromObjC(messageJSON) {setTimeout(function _timeoutDispatchMessageFromObjC() {var message = JSON.parse(messageJSON)var messageHandlervar responseCallbackif(message.responseId) {responseCallback = responseCallbacks[message.responseId]if(!responseCallback) {return; }responseCallback(message.responseData)delete responseCallbacks[message.responseId]}else{if(message.callbackId) {var callbackResponseId = message.callbackIdresponseCallback = function(responseData) {_doSend({ responseId:callbackResponseId, responseData:responseData })}}var handler = WebViewJavascriptBridge._messageHandlerif(message.handlerName) {handler = messageHandlers[message.handlerName]}try{handler(message.data, responseCallback)}catch(exception) {if(typeof console !='undefined') {console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception)}}}})}大家还记得我们返回的对象是:
1@{ @"responseId":callbackId, @"responseData":responseData }所以这里messageHandlers刚才也说过了用来存方法的,callbackId被换了个名字叫responseId意思一样,只要值没变就行,所以就会执行:
123bridge.callHandler('testObjcCallback', {'foo':'cccccccccccc'}, function(response) {log('JS got response', response)})中的方法,好了,完了。
总结一下:js这边 先把方法名字、参数、处理方法保存成一个字典在转成json字符串,在通过UIWebview调用js中某个方法把这个json字符串传到Native中去(不是通过url传的,这样太low了),同时把这个处理的方法以key-value形式放到一个js的字典中。
UIWebView在收到这个json之后,进行数据处理、还有js的回掉的处理方法(就是那个callbackId)处理完成后也会拼成一个key-value字典通过调用js传回去(可以直接调用js)。
js在接到这个json后,根据responseId读取responseCallbacks中处理方法进行处理Native code返回的数据。
3.Native调用js method
过程不是直接调用js,也是通过js调用Native过程一样的处理方式。
大体来看一下,先看一个按钮的单击事件:
123456- (void)callHandler:(id)sender {id data = @{ @"greetingFromObjC": @"Hi there, JS!"};[_bridge callHandler:@"testJavascriptHandler"data:data responseCallback:^(id response) {NSLog(@"testJavascriptHandler responded: %@", response);}];}看看callHandler:
123- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback {[self _sendData:data responseCallback:responseCallback handlerName:handlerName];}看看_sendData:
123456789101112131415161718- (void)_sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {NSMutableDictionary* message = [NSMutableDictionary dictionary];if(data) {message[@"data"] = data;}if(responseCallback) {NSString* callbackId = [NSString stringWithFormat:@"objc_cb_%ld", ++_uniqueId];_responseCallbacks[callbackId] = [responseCallback copy];message[@"callbackId"] = callbackId;}if(handlerName) {message[@"handlerName"] = handlerName;}[self _queueMessage:message];}到_queueMessage:之后流程就和上面一样了,这里面native也有个:
123456NSString* responseId = message[@"responseId"];if(responseId) {WVJBResponseCallback responseCallback = _responseCallbacks[responseId];responseCallback(message[@"responseData"]);[_responseCallbacks removeObjectForKey:responseId];}这个和js中的处理思想是一样的。
总结:native将方法名、参数、回到的id放到一个对象中传给js。
js根据方法名字调用相应方法,之后将返回数据和responseId拼装,最后通过src 重定向到UIWebview 的delegate。
native得到数据后根据responseId调用事先装入_responseCallbacks的block,动态读取调用,从而完成交互。
WebViewJavascriptBridge 原理分析的更多相关文章
- IOS WebViewJavascriptBridge 使用以及原理分析
本文转自:https://www.jianshu.com/p/b8d4285395c6 概述 从两个方面来讲: js不能直接调用oc的方法 oc可以通过如下函数调用js代码 - (void)evalu ...
- Handler系列之原理分析
上一节我们讲解了Handler的基本使用方法,也是平时大家用到的最多的使用方式.那么本节让我们来学习一下Handler的工作原理吧!!! 我们知道Android中我们只能在ui线程(主线程)更新ui信 ...
- Java NIO使用及原理分析(1-4)(转)
转载的原文章也找不到!从以下博客中找到http://blog.csdn.net/wuxianglong/article/details/6604817 转载自:李会军•宁静致远 最近由于工作关系要做一 ...
- 原子类java.util.concurrent.atomic.*原理分析
原子类java.util.concurrent.atomic.*原理分析 在并发编程下,原子操作类的应用可以说是无处不在的.为解决线程安全的读写提供了很大的便利. 原子类保证原子的两个关键的点就是:可 ...
- Android中Input型输入设备驱动原理分析(一)
转自:http://blog.csdn.net/eilianlau/article/details/6969361 话说Android中Event输入设备驱动原理分析还不如说Linux输入子系统呢,反 ...
- 转载:AbstractQueuedSynchronizer的介绍和原理分析
简介 提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架.该同步器(以下简称同步器)利用了一个int来表示状态,期望它能够成为实现大部分同步需求的基础.使用的方法是继承,子类通过 ...
- Camel运行原理分析
Camel运行原理分析 以一个简单的例子说明一下camel的运行原理,例子本身很简单,目的就是将一个目录下的文件搬运到另一个文件夹,处理器只是将文件(限于文本文件)的内容打印到控制台,首先代码如下: ...
- NOR Flash擦写和原理分析
NOR Flash擦写和原理分析 1. NOR FLASH 的简单介绍 NOR FLASH 是很常见的一种存储芯片,数据掉电不会丢失.NOR FLASH支持Execute On Chip,即程序可以直 ...
- 使用AsyncTask异步更新UI界面及原理分析
概述: AsyncTask是在Android SDK 1.5之后推出的一个方便编写后台线程与UI线程交互的辅助类.AsyncTask的内部实现是一个线程池,所有提交的异步任务都会在这个线程池中的工作线 ...
随机推荐
- C#——字符操作
题目要求:用户随机输入字母及数字组成的字符串,当用户连续输入字符串‘hello’时,程序结束用户输入,并分别显示用户输入的字母及数字的数目. 代码: using System; using Syste ...
- Easyui 加载树(easyui-tree)[dotnet]
前台 html: <ul class="easyui-tree" id="ul_Tree" data-options="fit:true,ani ...
- OpenGL7-3快速绘制(索引方式)
代码下载#include "CELLWinApp.hpp"#include <gl/GLU.h>#include <assert.h>#include &l ...
- (转)Facebook内部分享:26个高效工作的小技巧
春节假期马上就要结束了,该收收心进入新一年的工作节奏了~分享 26 个高效工作的小技巧,希望对大家有所帮助~(我发现自己只有最后一条执行得很好,并且堪称完美!) 1.时间常有,时间优先. 2.时间总会 ...
- Java WebService简单实例
一.准备工作(以下为本实例使用工具) 1.MyEclipse10.7.1 2.JDK 1.6.0_22 二.创建服务端 1.创建[Web Service Project],命名为[TheService ...
- 基于api安全性的解决处理方案
api解决用户安全主要采用两种方案: 一.使用token识别用户信息,作为识别用户的凭证 1.为什么使用token? 常规性的pc站点,使用session存储用户登录状态. 即用户登录之后,服务端会生 ...
- Linux进程间通信IPC学习笔记之消息队列(SVR4)
Linux进程间通信IPC学习笔记之消息队列(SVR4)
- C#网络编程简单实现通信小例子-1
1.主界面 2.源程序 Send public partial class formUdpSend : Form { //声明一个UdpClient对象 UdpClient udpClient; pu ...
- 4、WPF应用程序的启动
启动第一步: 启动第二步 启动第三步:
- NotifyIcon制作任务栏托盘菜单
常用软件飞信.QQ在任务栏中的图标ICO,以及鼠标移动到图标是右键菜单选项 1.首先制作任务栏图标 this.ShowInTaskbar = true; 2.窗体最小化时或者关闭时隐藏到任务栏,有时候 ...