webview javascript bridge

在hybrid iOS app中,为了达到有些页面能够方便的通过服务器配置内容更改客户端界面,而不需要等待苹果的一个审核周期,一种简单的办法就是把这样的界面用UIWebView通过读取服务端的H5来展示。如果除去简单的展示功能,还希望能做更复杂的事情,调用一些native的模块完成一些操作,甚至进而将操作结果展示在H5页面上,就需要借助webview javascript bridge的帮助了。
最近借用了这个bridge完成了一个H5页面上的图片全屏浏览,左右滑动,长按保存的功能。

两个基础API

WebviewJavascriptBridge是github上的开源库,它可以做到js与native互相发送消息并且返回结果。它主要是基于iOS UIWebView的两个方法来完成js和native之间的通信的,这两个API是整个通信机制的基石:

    //UIWebView
    func stringByEvaluatingJavaScriptFromString(script:String)->String

    //UIWebViewDelegate
    func webView(_ webView:UIWebView, shouldStartLoadWithRequest request:NSURLRequest, navigationType navigationType:UIWebViewNavigationType) -> Bool

通过第一个API,native可以向js发消息,这很简单而js端要想向native发消息,就必须借助一点黑魔法。这里js和native端约定好一个fake protocol,在js中创建一个空的iframe,这个frame通过设置src来读取这个fake address,而这个读取请求则会被UIWebViewDelegate的代理方法截获,并且当成发送给native端的消息去解析。

 var CUSTOM_PROTOCOL_SCHEME = 'wvjbscheme'

 function _createQueueReadyIframe(doc) {
  messagingIframe = doc.createElement('iframe')
  messagingIframe.style.display = 'none'
  messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
  doc.documentElement.appendChild(messagingIframe)
 }

此后每次js想要通知native端接收消息,都会设置这个messagingIframe的src。

js向native发消息的过程

native端初始化

为了响应js端发来的消息,native端在初始化加载H5的UIWebView的时候需要做一些准备工作:

  1. 初始化一个WebviewJavascriptBridge实例,这里需要注册一个默认的响应函数,还可以用registerHandler方法以key-block的形式注册若干handler,其本质类似于向js端暴露若干函数,key相当于函数的声明,block相当于函数的实现。这里我们注册了一个browseImages的方法,block中就是调用图片浏览的模块用paging的UIScrollView去展示图片。
  2. UIWebView可以去加载H5啦
  3. 在UIWebView加载完毕的delegate method回调中,注入js,包括WebviewJavascriptBridge的js和我们自己定义的js。以我们的需求为例,在这里需要给所有的image节点加上listener,当图片上发生点击事件的时候,去通过bridge向native端发送browseImages的消息。

实际流程

虽然上面说明了如果在js端如何向native端发送消息,但是仍有两个问题需要解决:

  1. 参数,尤其是复杂的参数如何传递
  2. 回调,如果能够异步处理另一端的处理结果

第一个问题的答案是js端将调用函数,参数,回调block都保存到了sendMessageQueue中,native端收到了消息后,会主动来取参数。
第二个问题的答案是为回调block生成callbackid,保存callbackid和回调block到字典中,在回调消息中去和responseid做匹配。
那么,2还带来了一个延伸问题,虽然有以上两个API保证了互相传递的通道,但是如何区分是另一方主动发来的调用还是回调的通知呢?
这就是通过判断是否存在responseid这个字段来判定的。

详细流程如下,左侧代表js端,右侧代表native端:

《webview javascript bridge》 流程1

《webview javascript bridge》 流程2

《webview javascript bridge》 流程3

native端向js端发消息

总体来说流程是完全类似的,这次用文字来描述:

  1. js端首先注册若干handler方法,保存在messageHandlers字典中
  2. native端发起对js的调用:将调用的js handler名称,参数放到message字典中
  3. 如果需要回调,则为回调方法生成唯一的callbackId,将callbackId和回调block保存在native端的responseCallbacks字典中
  4. 通过UIWebView.stringByEvaluatingJavascript…调用js端处理message函数
  5. js端检查发现message中不包含responseId,确认不是js调用native后的回调,则在自己的messageHandlers中找到对应的handler
  6. 如果message中包含callbackId,则生成回调native的block,callbackId在回调message中的key是responseId
  7. handler运行完成,如果需要回调native端,则将生成的数据加入回调message字典中。(现在字典中包含两个key:responseId和responseData)
  8. 将message放到sendMessageQueue中,然后用预定义的protocol设置空iframe的src
  9. native端收到shouldStartLoad…..调用,通过evaluateJavascript…获取sendMessageQueue中的message
  10. 发现message中包含responseId,确认为自己主动发起的调用的回调
  11. 从responseCallbacks字典中根据responseId找到对应的block,用message中的responseData调用,整个流程完成。

end

整个bridge的库,基于底层两个互相通信的方法,实现了一整套优雅的机制,写的真是太棒了!

    原文作者:安然安然
    原文地址: https://www.jianshu.com/p/61fbabb3df36
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞