iOS WebView JavascriptBridge 源码解析

一、用法简析

这个库的还是比较精简单的,当前webView 是用UIWebView 那么我只需要引入WebViewJavascriptBridge ,相应的WKWebView则需要用到WKWebViewJavascriptBridge

这个桥接主要是介于js 和 app 间我们先来看js 要怎么做,我们需要copy 这段代码到js里至于为什么要这么做,后文会提到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 )
}
export  default   class   IOSBridge {
// key : xxx  参数 : json   回调: responseCallback
       changeState (json) {
           setupWebViewJavascriptBridge( function   (bridge) {
                  bridge.callHandler( 'xxx' , json,  function                  responseCallback (responseData) {
       })
     })
   }
}

app 端只需要

1
2
3
4
5
_bridge = [WebViewJavascriptBridge bridgeForWebView:webView];
[_bridge setWebViewDelegate:self];
[self.bridge registerHandler:@ "xxx"   handler:^(id data, WVJBResponseCallback responseCallback) {
     responseCallback(@ "xxx" );
}];

简洁的代码就已经完成js与app的桥接,那么我们来看看他整个流程是怎么执行,

首先js 调用的代码出现一个了创建了一个iframe 并且设置src = 'https://bridge_loaded'

我们很清楚的知道如果WKWebView 可以捕获到url 的变化的 也就是说设置src后WKWebView 是有回调的,那么我把断点断到WKWebView的代理看看这里发生了什么(PS:为什么这里iframe 要appendChild到document, 然后又快速的remove 呢 ,我想到一个解释 ,是因为iframe 设置src 就无用了,iframe 只能通过 removeChild 才能移除掉,然后要想remove 就必须得先append )

原理简释:

当js 第一次app 桥接 我们在代理截获到 这段

我们截获到 这段url https://bridge_loaded

这里有个loaded 我可以猜测类似ViewDidLoad 页面加载完成,那么我们进代理看看做了什么

1
2
3
4
5
6
7
8
9
10
11
if   ([_base isWebViewJavascriptBridgeURL:url]) {
         if   ([_base isBridgeLoadedURL:url]) {
             [_base injectJavascriptFile];
         else   if   ([_base isQueueMessageURL:url]) {
             [self WKFlushMessageQueue];
         else   {
             [_base logUnkownMessage:url];
         }
         decisionHandler(WKNavigationActionPolicyCancel);
         return ;
     }

直接点进去我们看到[_base isWebViewJavascriptBridgeURL:url] 这个方法去区分是否是从这个库的iframe 设置的src的url 他们是根据

1
2
3
4
#define kOldProtocolScheme @ "wvjbscheme"
#define kNewProtocolScheme @ "https"
#define kQueueHasMessage   @ "__wvjb_queue_message__"
#define kBridgeLoaded      @ "__bridge_loaded__"

判断url 是否包含以上字符串,那么接着看看当url 是https://bridge_loaded 这个的时候做了什么,也就是执行了这个 [_base injectJavascriptFile] 注入一段js,自然这段js 必然起到重要的作用,我们先简单看一下这段js,也就是这个两个文件

这段js简单来看是插入一个(function(){xxx})()的匿名函数

创建了一个WebViewJavascriptBridge 全局变量

1
2
3
4
5
6
7
window.WebViewJavascriptBridge = {
         registerHandler: registerHandler,
         callHandler: callHandler,
         disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
         _fetchQueue: _fetchQueue,
         _handleMessageFromObjC: _handleMessageFromObjC
     };

简单过完这段js 然后我继续调试,发现出现了一个url 为https://wvjb_queue_message/ 的变化

那可以猜想是不是告诉app端 js 正在调用你的方法呢,但是很奇怪的是,断点在app方法回调的部分却没有执行,这是为啥,那我们在看看 那段注入的js 做了什么

1
2
3
[self.bridge registerHandler:@ "xxx"   handler:^(id data, WVJBResponseCallback responseCallback) {
     responseCallback(@ "xxx" );
}];

我们看到注入的js里面有一个iframe 同样也设置了src 代码如下

1
2
3
4
messagingIframe = document.createElement( 'iframe' );
     messagingIframe.style.display =  'none' ;
     messagingIframe.src = CUSTOM_PROTOCOL_SCHEME +  '://'   + QUEUE_HAS_MESSAGE;
     document.documentElement.appendChild(messagingIframe);

那为什么他设置src,这样调试有啥用,是不是为了初始化什么呢,那接着调试看看,他调用了WKFlushMessageQueue这个方法,调用js WebViewJavascriptBridge._fetchQueue()的方法,那我们在来看看注入js 有没有_fetchQueue这个方法,

1
2
3
4
5
     function   _fetchQueue() {
         var   messageQueueString = JSON.stringify(sendMessageQueue);
         sendMessageQueue = [];
         return   messageQueueString;
     }

他是返回了messageQueueString ,并且清空sendMessageQueue 这个数组,可以猜测他估计这一次只是为了重新初始化sendMessageQueue 这个数组,那么这么说下一次就应该是响应js 调用 app的方法了,那么耐着性子继续来下一步,果然WKFlushMessageQueue 注入js 的回调了handelName 和参数

接着在[_base flushMessageQueue:result]; 这个方法将result字符串转换成字典 并且在messageHandlers这个全局变量获取到对应handlerName 的 hanlder 回调给app 注册的回调中

ps: app 端注册hanler 就会把这个hanler 添加messageHandlers这个可变字典中

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

说到这里大概知道他是根据代理判断url 变化来截获到js 调用app 这个事件,那到这里还有一个很大疑问就是他的url https://wvjb_queue_message/ 他是怎么把参数传过来的了,貌似我们一直在看注入的js ,忽略了刚开始复制那段js 代码,我们在过头来看看

1
2
3
4
5
6
       changeState (json) {
           setupWebViewJavascriptBridge( function   (bridge) {
                  bridge.callHandler( 'xxx' , json,  function                       responseCallback (responseData) {
       })
     })
   }

这里调用注入的js 的callHandler 方法了要绕回去,下面我会画一个流程图梳理一下整个流程,那我们来看看callHandler方法又做了什么,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
     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;
     }

原来在这个时候callHandler 方法message 这个字典塞入sendMessageQueue数组里面,并且设置src 为https://wvjb_queue_message/ 通知app 端将发送消息,当代理拦截到这段url 便调用WKFlushMessageQueue 方法调用WebViewJavascriptBridge._fetchQueue() 这个方法去sendMessageQueue这个数组获取消息内容字典,大致流程已经解析完,最后在来看看流程图,梳理下流程吧

最后总结下原来这个库原来很简单只是通过iframe 设置src变化去触发app 的webView 的代理回调,但是使用这个库却察觉不到这个痕迹,使用十分简单,代码逻辑也十分整洁,还是十分推荐的。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值