网上好多都是在介绍 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文件中点击一个按钮发起请求的代码:


[html] view plain copy

print?

  1. varcallbackButton=document.getElementById('buttons').appendChild(document.createElement('button'))

  2. callbackButton.innerHTML='Fire testObjcCallback'

  3. callbackButton.onclick=function(e) {

  4. e.preventDefault()

  5. log('JS calling handler "testObjcCallback"')

  6. //1

  7. bridge.callHandler('testObjcCallback', {'foo': 'cccccccccccc'}, function(response) {

  8. log('JS got response', response)

  9. })

  10. }



估计大家大体都能看懂,唯独有疑问的地方是:



[html] view plain copy

print?

  1. bridge.callHandler('testObjcCallback', {'foo': 'cccccccccccc'}, function(response) {

  2. log('JS got response', response)

  3. })

  4. }



这段代码先不说,上面代码就是一个按钮的普通单击事件方法。我们一起想一下,如果这个按钮需要被点击之后调用native中的funtion函数,之后需要把这个(native的)funtion函数处理结果返回给js中的方法继续处理。这个是我们需求,带着这个需求我们看一下这个方法,testObjcCallBack这个我们猜测一下应该native中的方法或者一个能够调用到方法的name/id,后面这个是个json{‘foo’:‘ccccccccccccc’},应该是个参数,那么后面这个方法一看log应该知道,是对native返回的result进行处理的方法。拿具体是不是呢?只要找到callHandler方法就知道了。


在文件WebViewJavascriptBridge.js.txt里面我们找找这个方法:


[html] view plain copy

print?

  1. function callHandler(handlerName, data, responseCallback) {

  2. _doSend({ handlerName:handlerName, data:data }, responseCallback)

  3. }



这里又多了一个方法叫_doSend连个参数 第1个是字典key-value定义,第二个是一个方法的指针(看看上面的方法你就知道了),那我们必须在同一个文件里面看看能不能找到这个_doSend方法:



[html] view plain copy

print?

  1. function _doSend(message, responseCallback) {

  2. if (responseCallback) {

  3. var callbackId='cb_'+(uniqueId++)+'_'+new Date().getTime()

  4. responseCallbacks[callbackId] = responseCallback

  5. message['callbackId'] = callbackId

  6. }

  7. sendMessageQueue.push(message)

  8. messagingIframe.src=CUSTOM_PROTOCOL_SCHEME+ '://' + QUEUE_HAS_MESSAGE

  9. }



找到了。


逐行分析一下,变量callbackId是个字符串,responseCallBacks[] 一看就知道是个字典 ,这个字典把回掉(我们猜测)的方法responseCallback给保存起来,这Key(也就是callbackId)应该是唯一的,通过计数和时间应该知道这个字符串应该是唯一的,message也是一个字典,这是给message添加了一个新的key-value。干嘛呢?我也不知道,我们来看看sendMessageQueue是什么,大家一个push就知道应该是个数组。他吧一个字典放到一个消息队列中(数组队列),让后产生一个src(url scheme)。

有两个变量我们看看:


[html] view plain copy

print?

  1. varCUSTOM_PROTOCOL_SCHEME='wvjbscheme'

  2. var QUEUE_HAS_MESSAGE='__WVJB_QUEUE_MESSAGE__'


干嘛用,肯定是给webview 的 delegate判断用的,你感觉呢?(肯定是)


下面是在文件:WebViewJavascriptBridge.m

好了到了这里大家猜猜这个要干嘛?肯定是要发url让web截取对吧?那还用问啊,肯定是啊,已经说过了js能不能调用native的funtion函数?不能。我们来看看这个messagingIframe是:


[html] view plain copy

print?

  1. function _createQueueReadyIframe(doc) {

  2. messagingIframe=doc.createElement('iframe')

  3. messagingIframe.style.display='none'

  4. messagingIframe.src=CUSTOM_PROTOCOL_SCHEME+ '://' + QUEUE_HAS_MESSAGE

  5. doc.documentElement.appendChild(messagingIframe)

  6. }



原来就是iframe,这个就不同给大家解释了。好了src一产生就会出现什么,uiwebview代理回掉截获,此时我们把目光回到UIWebview的Native下面:



[html] view plain copy

print?

  1. - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {

  2. if (webView != _webView) { return YES; }

  3. NSURL *url= [request URL];

  4. __strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate=_webViewDelegate;

  5. if ([[url scheme] isEqualToString:kCustomProtocolScheme])

  6. {

  7. if ([[url host] isEqualToString:kQueueHasMessage])

  8. {

  9. //会走这里

  10. [self _flushMessageQueue];

  11. }

  12. else

  13. {

  14. NSLog(@"WebViewJavascriptBridge: WARNING: Received unknown WebViewJavascriptBridge command %@://%@", kCustomProtocolScheme, [url path]);

  15. }

  16. return NO;

  17. }

  18. else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)])

  19. {

  20. return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];

  21. }

  22. else

  23. {

  24. return YES;

  25. }

  26. }



一看就头大,哈哈,是,我也头大。看看上面的注释说 会走这里,我们看看为什么会走那里,最外圈的if([url scheme])判断是


#define kCustomProtocolScheme @"wvjbscheme"

这个定义是什么意思,我们先不做解释,刚才我们说过js不能直接调用native的function,大家只要记住这点,接着往下走就是了。至于为什么走这里,自己看代码(上文有提到),我们看看_flushMessageQueue:


[html] view plain copy

print?

  1. - (void)_flushMessageQueue {

  2. NSString *messageQueueString= [_webView stringByEvaluatingJavaScriptFromString:@"WebViewJavascriptBridge._fetchQueue();"];

  3. //json转成数组

  4. id messages= [self _deserializeMessageJSON:messageQueueString];

  5. if (![messages isKindOfClass:[NSArray class]]) {

  6. NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [messages class], messages);

  7. return;

  8. }

  9. for (WVJBMessage* message in messages) {

  10. if (![message isKindOfClass:[WVJBMessage class]]) {

  11. NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);

  12. continue;

  13. }

  14. [self _log:@"RCVD" json:message];

  15. //用于js回掉

  16. NSString* responseId=message[@"responseId"];

  17. if (responseId) {

  18. WVJBResponseCallback responseCallback=_responseCallbacks[responseId];

  19. responseCallback(message[@"responseData"]);

  20. [_responseCallbacks removeObjectForKey:responseId];

  21. } else {

  22. WVJBResponseCallback responseCallback=NULL;

  23. NSString* callbackId=message[@"callbackId"];

  24. if (callbackId) {

  25. responseCallback= ^(id responseData) {

  26. if (responseData== nil) {

  27. responseData= [NSNull null];

  28. }

  29. WVJBMessage* msg= @{ @"responseId":callbackId, @"responseData":responseData };

  30. [self _queueMessage:msg];

  31. };

  32. } else {

  33. responseCallback= ^(id ignoreResponseData) {

  34. // Do nothing

  35. };

  36. }

  37. WVJBHandler handler;

  38. if (message[@"handlerName"]) {

  39. handler=_messageHandlers[message[@"handlerName"]];

  40. } else {

  41. handler=_messageHandler;

  42. }

  43. if (!handler) {

  44. [NSException raise:@"WVJBNoHandlerException" format:@"No handler for message from JS: %@", message];

  45. }

  46. handler(message[@"data"], responseCallback);

  47. }

  48. }

  49. }



这下牛逼了,不忍直视啊!这么多,哈哈,多不可怕,可怕是你坚持不下去了。


我们逐行来看:

NSString *messageQueueString = [_webView stringByEvaluatingJavaScriptFromString:@"WebViewJavascriptBridge._fetchQueue();"];

我们必须回去到js文件中去,这里是webview直接调用js中的方法:


[html] view plain copy

print?

  1. function _fetchQueue() {

  2. var messageQueueString=JSON.stringify(sendMessageQueue)

  3. sendMessageQueue= []

  4. return messageQueueString

  5. }



谢天谢地这个方法代码不多,这个消息很眼熟,SendMessageQueue,刚才我们说什么来?他是一个字典,那里面有哪些东西,我么来看看


handlerName:handlerName,

data:data,

callbackId:callbackId 

这个消息字典此时被取出来准备做什么,这里提示下我们已经走到webview 的delegate里面了,所以拿到这些信息肯定是调用native的method对吧?肯定是的。接着往下走,接着会把json字符串转成数组,然后进行判断,

[html] view plain copy

print?

  1. NSString*responseId=message[@"responseId"];

有没有responseid,你说又没,肯定没有啊(你不行看看上面),所以就这这里了


[html] view plain copy

print?

  1. WVJBResponseCallbackresponseCallback=NULL;

  2. NSString* callbackId=message[@"callbackId"];

  3. if (callbackId) {

  4. responseCallback= ^(id responseData) {

  5. if (responseData== nil) {

  6. responseData= [NSNull null];

  7. }

  8. WVJBMessage* msg= @{ @"responseId":callbackId, @"responseData":responseData };

  9. [self _queueMessage:msg];

  10. };

  11. } else {

  12. responseCallback= ^(id ignoreResponseData) {

  13. // Do nothing

  14. };

  15. }

  16. WVJBHandler handler;

  17. if (message[@"handlerName"]) {

  18. handler=_messageHandlers[message[@"handlerName"]];

  19. } else {

  20. handler=_messageHandler;

  21. }

  22. if (!handler) {

  23. [NSException raise:@"WVJBNoHandlerException" format:@"No handler for message from JS: %@", message];

  24. }

  25. handler(message[@"data"], responseCallback);

这部分是重点,到底他是怎么要调用本地function的,callbackId大家熟悉吧,判断是否为空,不为空给他指定一个block,这个不说了,block指定,此时不调用(手动调用才会执行),这个刚才说了用来处理native的function处理的result用于把处理后的值返回给js的,接着往下去,看到handler这个方法会从message找到handlerName,这里我们看一下多了一个_messageHandlers字典,从这个字典获取一个block(WVJBHandler是一个block),直接执行了。那我们看看_messageHandlers是怎么被添加block的:



[html] view plain copy

print?

  1. - (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {

  2. _messageHandlers[handlerName] = [handler copy];

  3. }



那又是谁调用了这个方法:


找到了(在文件 ExampleAppViewController.m的viewdidload中),这里有方法testObjecCallback


[html] view plain copy

print?

  1. [_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {

  2. NSLog(@"testObjcCallback called: %@", data);

  3. responseCallback(@"Response from testObjcCallback");

  4. }];



有点乱了。刚才我们的思路都是倒推的,如果我们整过来,首先肯定是viewdidload初始化,初始化之后会把这个block加入到_messageHandlers中,之后因为js调用动态读取这个block调用,在调用之前,我们又把定一个block付给回掉处理的responseCallback的block,这个block在handler中调用而调用,有点绕,自己可以多想想。


我们接着来看看:


[html] view plain copy

print?

  1. responseCallback= ^(id responseData) {

  2. if (responseData== nil) {

  3. responseData= [NSNull null];

  4. }

  5. WVJBMessage* msg= @{ @"responseId":callbackId, @"responseData":responseData };

  6. [self _queueMessage:msg];

  7. };



这个就是你绕的地方,他是后被定义的,所以一开不执行,只有在处理数据后回调才会被调用,这里有个方法_queueMessage:



[html] view plain copy

print?

  1. - (void)_queueMessage:(WVJBMessage*)message {

  2. if (_startupMessageQueue) {

  3. [_startupMessageQueue addObject:message];

  4. } else {

  5. [self _dispatchMessage:message];

  6. }

  7. }



这里面还有个方法:



[html] view plain copy

print?

  1. - (void)_dispatchMessage:(WVJBMessage*)message {

  2. NSString *messageJSON= [self _serializeMessage:message];

  3. [self _log:@"SEND" json:messageJSON];

  4. messageJSON= [messageJSON stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];

  5. messageJSON= [messageJSON stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];

  6. messageJSON= [messageJSON stringByReplacingOccurrencesOfString:@"\'" withString:@"\\\'"];

  7. messageJSON= [messageJSON stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];

  8. messageJSON= [messageJSON stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"];

  9. messageJSON= [messageJSON stringByReplacingOccurrencesOfString:@"\f" withString:@"\\f"];

  10. messageJSON= [messageJSON stringByReplacingOccurrencesOfString:@"\u2028" withString:@"\\u2028"];

  11. messageJSON= [messageJSON stringByReplacingOccurrencesOfString:@"\u2029" withString:@"\\u2029"];

  12. NSString* javascriptCommand= [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];

  13. if ([[NSThread currentThread] isMainThread]) {

  14. [_webView stringByEvaluatingJavaScriptFromString:javascriptCommand];

  15. } else {

  16. __strong WVJB_WEBVIEW_TYPE* strongWebView=_webView;

  17. dispatch_sync(dispatch_get_main_queue(), ^{

  18. [strongWebView stringByEvaluatingJavaScriptFromString:javascriptCommand];

  19. });

  20. }

  21. }



我们在回到WebViewJavascriptBridge.js.txt文件中看到


[html] view plain copy

print?

  1. function _handleMessageFromObjC(messageJSON) {

  2. if (receiveMessageQueue) {

  3. receiveMessageQueue.push(messageJSON)

  4. } else {//肯定走这个  为什么呢?

  5. _dispatchMessageFromObjC(messageJSON)

  6. }

  7. }



再来看看:



[html] view plain copy

print?

  1. function _dispatchMessageFromObjC(messageJSON) {

  2. setTimeout(function _timeoutDispatchMessageFromObjC() {

  3. var message=JSON.parse(messageJSON)

  4. var messageHandler

  5. var responseCallback

  6. if (message.responseId) {

  7. responseCallback=responseCallbacks[message.responseId]

  8. if (!responseCallback) { return; }

  9. responseCallback(message.responseData)

  10. delete responseCallbacks[message.responseId]

  11. } else {

  12. if (message.callbackId) {

  13. var callbackResponseId=message.callbackId

  14. responseCallback=function(responseData) {

  15. _doSend({ responseId:callbackResponseId, responseData:responseData })

  16. }

  17. }

  18. var handler=WebViewJavascriptBridge._messageHandler

  19. if (message.handlerName) {

  20. handler=messageHandlers[message.handlerName]

  21. }

  22. try {

  23. handler(message.data, responseCallback)

  24. } catch(exception) {

  25. if (typeof console != 'undefined') {

  26. console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception)

  27. }

  28. }

  29. }

  30. })

  31. }



大家还记得我们返回的对象是:

[html] view plain copy

print?

  1. @{ @"responseId":callbackId, @"responseData":responseData }


所以这里messageHandlers刚才也说过了用来存方法的,callbackId被换了个名字叫responseId意思一样,只要值没变就行,所以就会执行:


[html] view plain copy

print?

  1. bridge.callHandler('testObjcCallback', {'foo': 'cccccccccccc'}, function(response) {

  2. log('JS got response', response)

  3. })



中的方法,好了,完了。



总结一下: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过程一样的处理方式。

大体来看一下,先看一个按钮的单击事件:

[html] view plain copy

print?

  1. - (void)callHandler:(id)sender {

  2. id data= @{ @"greetingFromObjC": @"Hi there, JS!" };

  3. [_bridge callHandler:@"testJavascriptHandler" data:data responseCallback:^(id response) {

  4. NSLog(@"testJavascriptHandler responded: %@", response);

  5. }];

  6. }


看看callHandler:


[html] view plain copy

print?

  1. - (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback {

  2. [self _sendData:data responseCallback:responseCallback handlerName:handlerName];

  3. }



看看_sendData:



[html] view plain copy

print?

  1. - (void)_sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {

  2. NSMutableDictionary* message= [NSMutableDictionary dictionary];

  3. if (data) {

  4. message[@"data"] = data;

  5. }

  6. if (responseCallback) {

  7. NSString* callbackId= [NSString stringWithFormat:@"objc_cb_%ld", ++_uniqueId];

  8. _responseCallbacks[callbackId] = [responseCallback copy];

  9. message[@"callbackId"] = callbackId;

  10. }

  11. if (handlerName) {

  12. message[@"handlerName"] = handlerName;

  13. }

  14. [self _queueMessage:message];

  15. }



到_queueMessage:之后流程就和上面一样了,这里面native也有个:



[html] view plain copy

print?

  1. NSString*responseId=message[@"responseId"];

  2. if (responseId) {

  3. WVJBResponseCallback responseCallback=_responseCallbacks[responseId];

  4. responseCallback(message[@"responseData"]);

  5. [_responseCallbacks removeObjectForKey:responseId];

  6. }



这个和js中的处理思想是一样的。



总结:native将方法名、参数、回到的id放到一个对象中传给js。

js根据方法名字调用相应方法,之后将返回数据和responseId拼装,最后通过src 重定向到UIWebview 的delegate。

native得到数据后根据responseId调用事先装入_responseCallbacks的block,动态读取调用,从而完成交互。

本文转自ljianbing51CTO博客,原文链接: http://blog.51cto.com/ljianbing/1857876,如需转载请自行联系原作者

WebViewJavascriptBridge 原理分析相关推荐

  1. java signature 性能_Java常见bean mapper的性能及原理分析

    背景 在分层的代码架构中,层与层之间的对象避免不了要做很多转换.赋值等操作,这些操作重复且繁琐,于是乎催生出很多工具来优雅,高效地完成这个操作,有BeanUtils.BeanCopier.Dozer. ...

  2. Select函数实现原理分析

    转载自 http://blog.chinaunix.net/uid-20643761-id-1594860.html select需要驱动程序的支持,驱动程序实现fops内的poll函数.select ...

  3. spring ioc原理分析

    spring ioc原理分析 spring ioc 的概念 简单工厂方法 spirng ioc实现原理 spring ioc的概念 ioc: 控制反转 将对象的创建由spring管理.比如,我们以前用 ...

  4. 一次 SQL 查询优化原理分析(900W+ 数据,从 17s 到 300ms)

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:Muscleape jianshu.com/p/0768eb ...

  5. 原理分析_变色近视眼镜原理分析

    随着眼镜的发展,眼镜的外型变得越来越好看,并且眼镜的颜色也变得多姿多彩,让佩戴眼镜的你变得越来越时尚.变色近视眼镜就是由此产生的新型眼镜.变色镜可以随着阳光的强弱变换不同的色彩. 变色眼镜的原理分析 ...

  6. jieba分词_从语言模型原理分析如何jieba更细粒度的分词

    jieba分词是作中文分词常用的一种工具,之前也记录过源码及原理学习.但有的时候发现分词的结果并不是自己最想要的.比如分词"重庆邮电大学",使用精确模式+HMM分词结果是[&quo ...

  7. EJB调用原理分析 (飞茂EJB)

    EJB调用原理分析 EJB调用原理分析 作者:robbin (MSN:robbin_fan AT hotmail DOT com) 版权声明:本文严禁转载,如有转载请求,请和作者联系 一个远程对象至少 ...

  8. 深入掌握Java技术 EJB调用原理分析

      深入掌握Java技术 EJB调用原理分析     一个远程对象至少要包括4个class文件:远程对象:远程对象的接口:实现远程接口的对象的stub:对象的skeleton这4个class文件. 在 ...

  9. 神经网络(NN)+反向传播算法(Backpropagation/BP)+交叉熵+softmax原理分析

    神经网络如何利用反向传播算法进行参数更新,加入交叉熵和softmax又会如何变化? 其中的数学原理分析:请点击这里. 转载于:https://www.cnblogs.com/code-wangjun/ ...

最新文章

  1. SAP 画皮门 从白狐到超人的华丽转身
  2. VS中编译64位程序以及遇到的问题(E0000235)
  3. python 命令行参数-Python 命令行参数解析
  4. mysql 云主机名_mysql部署到云主机的笔记
  5. java如何分页_java中分页的实现
  6. hbase 查询设置超时_hbase master挂掉-zookeeper连接超时原因
  7. php文件下载不完整,求帮看下这段PHP下载MP4文件的有关问题,文件下载不完整
  8. seL4操作系统基础02:从Hello World开始
  9. PHPExcel存放多个sheet报错:Invalid character found in sheet title
  10. 趣谈网络协议——HTTPS协议
  11. Windows运维之道——PXE网克系统
  12. 2018最佳GAN论文回顾
  13. 【项目】小餐馆(点餐系统)项目框架
  14. 图纸识别自动生成BOM清单的方法
  15. layui下拉框联动查询效果
  16. JAVA12_10总结
  17. 聚类分析 | MATLAB实现k-Means(k均值聚类)分析
  18. android游戏开发原理及关键技术
  19. ATmega16开发板教程(4)——定时器
  20. 使用DoraOS瘦客户机系统,改造华为版的升腾C92,打造超值的瘦客户机系统

热门文章

  1. 线性表(二)——链表
  2. python语句分为复合语句_复合语句if条件的Python求值
  3. 安卓使用Span富文本给某段Text文本加上波浪线
  4. Python opencv 获取图像形状大小
  5. 手把手指导centos7中安装python3
  6. linux查看mongo表大小,MongoDB_mongodb 查看数据库和表大小,1,查看数据库db.stats() - phpStudy...
  7. 实验七多线程编程_JAVA
  8. 如何解决没有文件扩展“.js”的脚本引擎
  9. 1、用Anaconda配置Windows环境下的tensorflow(CPU版本)
  10. 【深度学习】LeNet-5、AlexNet、VGG16、GoogLeNet、ResNet