WebViewJavascriptBridge解析和改造

WebViewJavascriptBridge框架可以同时支持UIWebView及WKWebView,完成native与web的交互。其主要核心思路是native在webView的代理中拦截url,根据url来做不同处理!
WebViewJavascriptBridge框架结构主要结构为:
WebViewJavascriptBridge、WKWebViewJavascriptBridge这两个类分别为UIWebView与WKWebView与web交互桥梁,native与web的交互工作主要通过这两个类处理
WebViewJavasciptBridge_JS该类是web的JS环境,客户端需要在网页初始化的时候注入到web端,只有一个返回值为NSString的方法

一、WebViewJavascriptBridgeBase

该类有两个重要的属性:

1、NSMutableDictionary *responseCallbacks–存储内容有

callbackId为key,objc_cb_(uniqueId)为值,其中_uniqueId会递增
objc_cb
(_uniqueId)为key,回调方法为值
handlerName为key,参入的该参数为值

2、messageHandlers—存储客户端注册方法的方法及实现

二、web调用native的实现

1、native本地注册方法

[self.jsBridge registerHandler:@"JSCallOC" handler: ^(id data, WVJBResponseCallback responseCallback) {
   responseCallback(@"hahaha");
   NSLog(@"data:%@----responseCallback:%@", data, responseCallback);
}];

该注册方法内部实现主要是将注册方法名和方法实现存储到messageHandlers中

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

2、web端通过点击调用

WebViewJavascriptBridge.callHandler('JSCallOC', {'lmf':'cool'}, function(response) {
   alert(response);
})

上面方法中的WebViewJavascriptBridge为注入的js环境提供的,callHandler方法也是用环境中的代码实现的,这个js环境是在加载网页时,注入的。

3、Native消息处理

native通过webView:decidePolicyForNavigationAction:decisionHandler:拦截消息https://wvjb_queue_message,进而执行WKFlushMessageQueue

- (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];
    }];
}

webViewJavascriptFetchQueryCommand的实现如下

- (NSString*)webViewJavascriptFetchQueyCommand {

   return@"WebViewJavascriptBridge._fetchQueue();";
}

即WKFlushMessageQueue方法会执行web端的_fetchQueue
function _fetchQueue() {

var messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
return messageQueueString;
}
result数据内容为:

[
    {
      “handlerName”:”JSCallOC”,
      ”data”:{“lmf”:”cool”},
      ”callbackId”:”cb_1_15709533997161"
    }
]

再通过执行[_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);
        }
    }
}

存在callbackId,生成responseCallback

//responseCallback
^(id responseData) {
 if (responseData == nil) {
 responseData = [NSNull null];
 }                   
 WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
 [self _queueMessage:msg];
};

messageHandlers通过message[@“handlerName”]获取native注册的方法handler,并执行handler(message[@“data”], responseCallback);

4、执行注册的方法

执行注册的方法,并调用responseCallback传入参数hahaha,得到msg-@{ @“responseId”😡“cb_1_1587911907256”, @“responseData”😡“hahaha” }再执行_queueMessage

- (void)_queueMessage:(WVJBMessage*)message {
    if (self.startupMessageQueue) {
        [self.startupMessageQueue addObject:message];
    } else {
        [self _dispatchMessage:message];
    }
}
//执行js回调
- (void)_dispatchMessage:(WVJBMessage*)message {
    NSString *messageJSON = [self _serializeMessage:message pretty:NO];
    [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]) {
        [self _evaluateJavascript:javascriptCommand];

    } else {
        dispatch_sync(dispatch_get_main_queue(), ^{
            [self _evaluateJavascript:javascriptCommand];
        });
    }
}

该方法会执行web端的WebViewJavascriptBridge._handleMessageFromObjC(message.string);

5、执行js端的_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);
            }
        }
    }
}

判断存在responseId,通过responseId从responseCallbacks获取回调,并传入responseData执行回调,再从responseCallbacks中删除responseId的数据。

三、技术改造

在Native消息处理的位置我们能发现,js调用的方法我们都是能接收的,问题是在本地的映射表中做了校验,只有我们注册过的方法才能接收到信息。
根据这里的信息,我们就可以打断这种注册校验的关系了,外面也不用再注册,可以通过改造统一把接收到的数据传递到外面。

一、自己封装WebViewJavascriptBridge模块

1、仓库构建

因为我们需要直接改造WebViewJavascriptBridgeBase内的信息,所以直接使用github上的仓库就不适用了。
在这里插入图片描述
这里我们创建HUWebViewJavascriptBridge仓库,把WebViewJavascriptBridge里面的信息拉出来,集成到我们的仓库中就创建了一个我们自己的仓库,接下来就可以改造了。

2、技术改造

1、添加对外API

在这里插入图片描述

2、信息回调
1、创建内部代理回调

在这里插入图片描述

2、在消息处理位置添加代理调用

在这里插入图片描述

3、对外传递

在这里插入图片描述
这里我们就可以通过对外的API,做统一处理了,这里能看到,WebViewJavascriptBridge本身的能力我们并没有修改,保留了原有的注册和映射的方式。这里我们的组件就封装完成了。

二、调用

这里我们如果统一的API回调之后,就不再需要注册了。

1、改造前

 bridge.registerHandler("hello") { data, responseCallback in
      // do
  }

2、改造后

// 统一回调
        /// - Parameter type: 注册key
        /// - Parameter data: 传递参数
        /// - Parameter responseCallback: 回调函数
        bridge.jsCallBack = { type, data, responseCallback in
            guard let type = type, let responseCallback = responseCallback else { return }
            switch type {
            case "hello":
                toast("hello\(String(describing: data))")
                responseCallback("")
            default:
                toast("未实现")
                responseCallback("")
            }
        }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值