提纲
1、准备工作在了解 这个第三方库,以及读懂它的源码之前,我们要先了解 wkwebview的一些代理方法,对他们有一个掌握。
2、初步了解 js的src,因为改变了iframe的src之后,wkwebview会加载 如下这个方法。
3、读懂WKWebViewJavascriptBridge.m这个方法干了什么事。这里就以wkwebview为例来分析。
4、串起整个流程的关键就是 responseid和callbackid 分别都是什么时候设置存起来的,什么时候释放的,会不会释放.
常规流程—js调用原生
步骤及详解
1、可以看到这个三方库的demo里 H5的js中的点击事件调用的核心代码是bridge.callHandler,我们就从这里开始。
2、源码穿透,找到WebViewJavascriptBridge_JS.m文件,可以看到callHandler背后是_doSend({ handlerName:handlerName, data:data }, responseCallback) 这个方法。那么这个callHandler方法是怎么穿透到js文件里的这个方法呢,我们暂且搁置,先往下走。
3、我们来展开dosend方法如下图
大致解读一下,判断是否需要回调,这将决定了我们OC的代码里是否需要实现回调。如果有回调,那么利用uniqueId++ 和时间戳生成唯一的callbackid。这个很关键,见提纲4之后用responseCallBacks这个字典把回调这个block和callbackId关联存起来,同时在message这个字典里也存上一份。接下来是sendMessageQueue.push,这个地方应该是一个队列,这个地方的操作我们暂时搁置等回过头来再看。之后是给iframe的src赋值,也就是改变了iframe的src。
4、根据提纲改变了src就会触发wkwebview的decidePolicy方法,也就是url重定向方法。那么我们深入到WKWebViewJavascriptBridge.m这个方法里面看看都做了什么操作。如下图
我们来逐行分析一下,前面三行不用看了,接下里判断是否要拦截处理jsbridge,判断条件可以自己看一下,这里简而言之就是是https的请求,且host在js里面被注册成__bridge_loaded__了。否则的话就会走正常的加载流程。接下来是两种模式,第一种是我们的host在js里注册了__bridge_loaded__了,第二种是队列消息,加上了这个三方库自己的标识,这个我就不细说了。
5、[_baseinjectJavascriptFile] 这个方法。我这边经过多次尝试和分析源码发现,这个地方第一次是不可能走第二个路径的,我们在正常使用的时候都是要先走第一个__bridge_loaded__,因为在js里面注册的时候也要加上这个src标识WVJBIframe.src ='https://__bridge_loaded__’,而第二个那个__wvjb_queue_message__标识目前根本还没出现。我们在第二点的时候提到了怎么知道callHandler后面的代码是_doSend的,接下来可以回答了,就是因为我们H5页面在第一次执行的时候就会走到这个方法,因为我们的src设置的符合这个方法的条件判断。为什么要先走这个方法呢,这里面有一个重要的语句就是
执行完了这个之后后面的代码就不用看了,执行不执行都无所谓了。这里面我们可以看一下这个WebViewJavascriptBridge_js(),这个里面就执行了这个js语句也就是定义了callHandler的方法,所以第二步才能往下走下去。 这个里面还重新设置了iframe的src变成了__wvjb_queue_message__,我们前面讲过src改变的的时候,会重新加载重定向方法。然后代码就走向了我们第二条路径了,也就是WKFlushMessageQueue.接下来我们看看这个方法。
6、WKFlushMessageQueue 这个方法。先上图如下
这个方法,我们先看看[_base webviewJavascriptFetchQueryCommand] 这个方法我们看到是返回了一个fetchqueue这个js的语句。也就是_webview 去执行了这个语句。还记得我们在第三点的时候有一个pushqueue的操作么,就是把_dosend的执行的js的handlerName以及参数作为一个字典存到了queue这个数组里了。现在fetchqueue我们通过代码穿透知道了这个js语法就是拿到这个queue,这里注意一点就是这个地方已经把这个js方法数据包的字典变成了json字符串了。那么接下来我们看一下flushMessageQueue这个方法。这里稍微总结一下,到这一步就是我们从js里面想调起原生的方法,首先通过callHandler做了一个doSend操作然后通过上面的步骤一步一步走到了这里。这个flushMessageQueue方法是在WebViewJavascriptBridgeBase.m这个类里面。
7、重点关注这个方法- (void)flushMessageQueue:(NSString*)messageQueueString 还是先上图
这个方法有点长,我们一步一步来看,首先最上面一个条件语句不用看了。接下来是对我们的messageQueueString做了一个反序列化形成了一个数组。然后做了一个快排,以我们目前介绍的按理来看这里面应该就一个数据。同样的做了一个容错判断,接下里是要判断responseId,大家可以搜一下我们整个流程里到目前为止还没有对responseId赋值,只是在第三点的时候我们对这个message里的callbackid进行了赋值。那么接下来的语句自然走到了else里面了,来判断callbackId了,这个地方正常是有的,所以走到了我们对responseCallBack这个block做了一个初始化。这里面初始化了一个WVJBMessage并对responseId和responseData封装成了一个字典。并调用了[self_queueMessage:msg];这个方法我们等会儿再看。接着往下走,通过handlerName拿到了handler。handlerName我们通过第二点可以看到,但是这个handler是怎么拿到的呢,self.messageHandlers这个字典里怎么会存有handler呢,这里我们回过头来再看OC原生里的注册方法如下图所示。这个方法我们可以点进去看一下,执行了一个这个操作 _base.messageHandlers[handlerName] = [handlercopy];而这个_base 就是我们上一个截图里的self,所以说如果js调用callHandler方法之后,如果oc不注册这个方法,那么流程走到这里就凉了,必须是客户端先注册好,这个时候的messageHandler里才会有对应的handler。接下来就很明了了,调用handler(message[@"data"], responseCallback)方法。也就是说你在原生注册的方法开始走回调了,这个回调是把js的数据给到原生,原生通过调用responseCallback再进行回调传参(这个会在下一步里讲到)。也就是原生里的这个方法
我们再来看一下最开始的js最开始的调用如下图:
至此我们就把最开始js的调用到最后的原生去通过bridge注册这个方法,并拿到数据,最后通过responseCallback回调数据的流程和代码穿透做完了。其他的原生还会执行一些设置代理啊等方法,这个就不多说了,大家可以自己去看源码。
8、在上一步里说到了[self_queueMessage:msg]方法还没处理.我们通过代码穿透可以看到这个地方是直接走到了[self_dispatchMessage:message];这个里面。这其中有一个if(self.startupMessageQueue) 判断在我们执行第五点那个方法的时候就给置为空了。所以会走dispatch这个方法。我们层层点下去发现这个代码走到了WebViewJavascriptBridge_JS这个类里面的_dispatchMessageFromObjC 这个方法。这个方法也是个重点,我们po一下图。
我们重点看这段代码。这里判断了一下message的responseId,通过上面我们可以看到这个responseId是存在的,那么就从responseCallbacks里面拿到这个callBack,这个是我们在第三点的时候存起来的,这个就是接受OC的回调。到了这个时候js就能收到OC的回调了。我们再来回顾一下这个操作,在上一步里我们js调起原生的handler里面的方法的时候顺便也接受了responsecallBack的回调也就是OC给js的回调,这块就是双向绑定。个人觉得这块的代码把block活用到了极致了。本来这个地方就结束了,也实现了js调原生,原生回调js的双向绑定了。但是这里有一个重要的代码delete responseCallbacks[message.responseId]; 这个操作就溜了,这是删除了responseCallbacks里面的这个回调了。就是说下一次原生再给这个回调到js,js收不到了。
这里有本人的亲身经历,这个地方是个坑,因为有的时候我们需要原生多次回调给js。在我们第二次给到js回调的时候,由于responsecallbacks里面的回调被删除了。所以这个地方直接return了。这个地方是我们可以自定制的。这也为我之后的优化提供了思路。到此我们对WebViewJavascriptBridge的探究就到这里了。
tips:这只是整个流程的一个梳理和代码穿透,可能还有一些细节有待梳理。