第二课 细探WebViewJavascriptBridge

提纲

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:这只是整个流程的一个梳理和代码穿透,可能还有一些细节有待梳理。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容