WebViewJavascriptBridge源码分析

最近抽时间看了一遍WebViewJavascriptBridge这个开源框架,把看到的内容记录下来

源码地址:https://github.com/marcuswestin/WebViewJavascriptBridge

1、对外接口

初始化OC 初始化JS

[WebViewJavascriptBridge bridgeForWebView:(UIWebView/WebView*)webview handler:(WVJBHandler)handler]

 

[WebViewJavascriptBridge bridgeForWebView:(UIWebView/WebView*)webview webViewDelegate:(UIWebViewDelegate*)webViewDelegate handler:(WVJBHandler)handler]


document.addEventListener(‘WebViewJavascriptBridgeReady‘, function onBridgeReady(event) { ... }, false)

 

bridge.init(function messageHandler(data, response) { ... })

OC发送消息to JS JS发送消息to OC

[bridge send:(id)data]

[bridge send:(id)data responseCallback:(WVJBResponseCallback)responseCallback]


bridge.send("Hi there!")

bridge.send({ Foo:"Bar" })

bridge.send(data, function responseCallback(responseData) { ... })

OC注册事件(先) JS调用事件(后)

[bridge registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler]


WebViewJavascriptBridge.callHandler("handlerName")

OC调用事件(后) JS注册事件(先)

[bridge callHandler:(NSString*)handlerName data:(id)data]

[bridge callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)callback]


bridge.registerHandler("handlerName", function(responseData) { ... })

三类API接口用于OC与JS之间交互:初始化接口;发送消息接口,并且可以添加发送消息完成的回调函数;事件注册和调用接口,需要先在一端注册事件,另一端可以根据事件名称调用函数

除了上述提到的外部方法:还有两个方法十分重要,JS部分最重要的内部方法: _handleMessageFromObjC;OC部分重要的内部方法:flushMessageQueue

2、类结构图

WebViewJavascriptBridge目前既支持原有的UIWebView,也支持iOS8+之后新的WKWebView,使用时可以二选其一;

WebViewjavascriptBridgeBase是通用类,用于处理从Native到JS的消息注入,消息队列处理和分发,JSON数据的序列化和反序列化,LOG输出;

3、源码分析

3.1 消息发送JS-》Native

[bridge send:(id)data]

[bridge send:(id)data responseCallback:(WVJBResponseCallback)responseCallback]

这两个函数最后都是调用_doSend({ 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

}

首先生成callbackId,由不断加1的唯一需要和时间戳构成,如果有responseCallback函数,使用callbackId作为索引,存入responseCallbacks对象,等到从OC侧返回的信息中对应的callbackId与当前responseCallbacks中callbackId相同时,调用回调函数responseCallback;sendMessageQueue是个消息数组,每次的新消息放入其中,messagingIframe是iframe对象,当设置src产生一次请求,在OC端的

(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
会拦截请求内容

代码:

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

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

NSURL *url = [request
URL];

__strong
WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate =
_webViewDelegate;

if ([_baseisCorrectProcotocolScheme:url]) {

if ([_baseisCorrectHost:url]) {

NSString *messageQueueString = [self_evaluateJavascript:[_basewebViewJavascriptFetchQueyCommand]];

[_base
flushMessageQueue:messageQueueString];

} else {

[_base
logUnkownMessage:url];

}

return
NO;

} else
if (strongDelegate && [strongDelegate
respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {

return [strongDelegate
webView:webView shouldStartLoadWithRequest:requestnavigationType:navigationType];

} else {

return
YES;

}

}

重点部分:执行注入_evaluateJavascript,

OC

-(NSString *)webViewJavascriptFetchQueyCommand {

return
@"WebViewJavascriptBridge._fetchQueue();";

}

JS

function _fetchQueue() {

var messageQueueString = JSON.stringify(sendMessageQueue)

sendMessageQueue = []

return messageQueueString

}

这个函数从JS的sendMessageQueue消息队列获取内容返回,这个sendMessageQueue是在之前的_doSend函数中传入的消息内容,也就是NSString *messageQueueString = [self_evaluateJavascript:[_basewebViewJavascriptFetchQueyCommand]];这句代码获得从JS侧拿到的数据内容,然后调用[_baseflushMessageQueue:messageQueueString];对消息分发处理

- (void)flushMessageQueue:(NSString *)messageQueueString{

id messages = [self_deserializeMessageJSON:messageQueueString];

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

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

return;

}

for (WVJBMessage* messagein messages) {

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

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

continue;

}

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

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

if (responseId) {

WVJBResponseCallback responseCallback =_responseCallbacks[responseId];

responseCallback(message[@"responseData"]);

[self.responseCallbacksremoveObjectForKey: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 = self.messageHandlers[message[@"handlerName"]];

} else {

handler = self.messageHandler;

}

if (!handler) {

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

}

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

}

}

}

这个是整个框架中OC侧重要的函数,但是目前首先分析消息发送JS-》Native涉及到的部分内容,返回的消息包含callbackId,数据拼接后调用[self_queueMessage:msg];发送回JS侧的数据改为responseId为关键字key,具体如下:

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

if (self.startupMessageQueue) {

[self.startupMessageQueueaddObject:message];

} else {

[self
_dispatchMessage:message];

}

}

self.startupMessageQueue只有首次启动时有效,之后为nil,所以都是走[self_dispatchMessage:message];

- (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 = [NSStringstringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC(‘%@‘);",
messageJSON];

if ([[NSThreadcurrentThread]
isMainThread]) {

[self
_evaluateJavascript:javascriptCommand];

} else {

dispatch_sync(dispatch_get_main_queue(), ^{

[self
_evaluateJavascript:javascriptCommand];

});

}

}

此函数对message特殊字符进行转义处理,然后执行JS注入语句,WebViewJavascriptBridge._handleMessageFromObjC执行到JS侧

这个是整个框架中JS侧重要的函数,用于处理从OC侧返回的消息

function _dispatchMessageFromObjC(messageJSON) {

setTimeout(function _timeoutDispatchMessageFromObjC() {

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({ responseId:callbackResponseId, responseData:responseData })

}

}

var handler = WebViewJavascriptBridge._messageHandler

if (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)

}

}

}

})

}

执行JS侧本地回调函数

3.2 消息发送 OC--》JS

bridge.send("Hi there!")

bridge.send({ Foo:"Bar" })

bridge.send(data, function responseCallback(responseData) { ... })

调用

[_base sendData:dataresponseCallback:responseCallback
handlerName:nil];

执行
_queueMessage

3.3 OC注册事件和JS调用

OC侧注册

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

_base.messageHandlers[handlerName] = [handlercopy];

}

JS调用

function callHandler(handlerName, data, responseCallback) {

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

}

加入handlerName和data数据传给OC侧,JS侧记录responseCallback,最后也会走到- (void)flushMessageQueue:(NSString *)messageQueueString函数中,由于既没有callbackId也没有responseId,所以只处理handlerName及相关数据,最后走到 -
(void)flushMessageQueue:(NSString *)messageQueueString解析,OC侧执行之前注册的handler并传入data数据

3.4 JS注册事件和OC调用

JS注册

function registerHandler(handlerName, handler) {

messageHandlers[handlerName] = handler

}

本地记录

OC调用

- (void)callHandler:(NSString *)handlerName data:(id)data {

[self
callHandler:handlerName data:dataresponseCallback:nil];

}

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

[_base
sendData:data responseCallback:responseCallbackhandlerName:handlerName];

}

handlerName和data  在 - (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback
handlerName:(NSString*)handlerName中处理

4 、总结

1、与其它框架相比,此框架没有采用url拦截解析参数方式,而是多次JS注入参数获取,API接口暴露的操作在底层需要多次OC与JS之间交互完成

2、WKwebview部分没有使用新的delgate方法,而是沿用iframe方式,个人觉得采用新接口可能效率更高

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-19 23:23:35

WebViewJavascriptBridge源码分析的相关文章

TeamTalk源码分析之login_server

login_server是TeamTalk的登录服务器,负责分配一个负载较小的MsgServer给客户端使用,按照新版TeamTalk完整部署教程来配置的话,login_server的服务端口就是8080,客户端登录服务器地址配置如下(这里是win版本客户端): 1.login_server启动流程 login_server的启动是从login_server.cpp中的main函数开始的,login_server.cpp所在工程路径为server\src\login_server.下表是logi

Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)

1 背景 还记得前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>中关于透过源码继续进阶实例验证模块中存在的点击Button却触发了LinearLayout的事件疑惑吗?当时说了,在那一篇咱们只讨论View的触摸事件派发机制,这个疑惑留在了这一篇解释,也就是ViewGroup的事件派发机制. PS:阅读本篇前建议先查看前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>,这一篇承接上一篇. 关于View与ViewGroup的区别在前一篇的A

HashMap与TreeMap源码分析

1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Java这么久,也写过一些小项目,也使用过TreeMap无数次,但到现在才明白它的实现原理).因此本着"不要重复造轮子"的思想,就用这篇博客来记录分析TreeMap源码的过程,也顺便瞅一瞅HashMap. 2. 继承结构 (1) 继承结构 下面是HashMap与TreeMap的继承结构: pu

Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938395.html 前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化.在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就是创建并启动内核线

Spark的Master和Worker集群启动的源码分析

基于spark1.3.1的源码进行分析 spark master启动源码分析 1.在start-master.sh调用master的main方法,main方法调用 def main(argStrings: Array[String]) { SignalLogger.register(log) val conf = new SparkConf val args = new MasterArguments(argStrings, conf) val (actorSystem, _, _, _) =

Solr4.8.0源码分析(22)之 SolrCloud的Recovery策略(三)

Solr4.8.0源码分析(22)之 SolrCloud的Recovery策略(三) 本文是SolrCloud的Recovery策略系列的第三篇文章,前面两篇主要介绍了Recovery的总体流程,以及PeerSync策略.本文以及后续的文章将重点介绍Replication策略.Replication策略不但可以在SolrCloud中起到leader到replica的数据同步,也可以在用多个单独的Solr来实现主从同步.本文先介绍在SolrCloud的leader到replica的数据同步,下一篇

zg手册 之 python2.7.7源码分析(4)-- pyc字节码文件

什么是字节码 python解释器在执行python脚本文件时,对文件中的python源代码进行编译,编译的结果就是byte code(字节码) python虚拟机执行编译好的字节码,完成程序的运行 python会为导入的模块创建字节码文件 字节码文件的创建过程 当a.py依赖b.py时,如在a.py中import b python先检查是否有b.pyc文件(字节码文件),如果有,并且修改时间比b.py晚,就直接调用b.pyc 否则编译b.py生成b.pyc,然后加载新生成的字节码文件 字节码对象

LevelDB源码分析--Iterator

我们先来参考来至使用Iterator简化代码2-TwoLevelIterator的例子,略微修改希望能帮助更加容易立即,如果有不理解请各位看客阅读原文. 下面我们再来看一个例子,我们为一个书店写程序,书店里有许多书Book,每个书架(BookShelf)上有多本书. 类结构如下所示 class Book { private: string book_name_; }; class Shelf { private: vector<Book> books_; }; 如何遍历书架上所有的书呢?一种实

【Heritrix源码分析】Heritrix基本内容介绍

1.版本说明 (1)最新版本:3.3.0 (2)最新release版本:3.2.0 (3)重要历史版本:1.14.4 3.1.0及之前的版本:http://sourceforge.net/projects/archive-crawler/files/ 3.2.0及之后的版本:http://archive.org/ 由于国情需要,后者无法访问,因此本blog研究的是1.14.4版本. 2.官方材料 source:http://sourceforge.net/projects/archive-cra