WkWebView拦截替换本地音频,图片

3,730 阅读4分钟

需求

需求是要把网页缓存到本地,以防用户无信号使用时无法使用。

执行

那就开始做吧,以往做的只是App和H5的交互,传传数据啊,获取获取事件并作出响应啊之类的。下面是思路:

  • 感谢以下大神给的启发:webView离线缓存机制,这里解释了好几种方案,但都不可实施,但这里学到了很多关于webView缓存的知识和方案,有的在UIWebView上是可以的,但是在WKWebView上不可以。
  • 后来又查到了这篇文章WKWebView的缓存,我们在前面看到WKWebView虽然走canInitWithRequest方法,但是后面的整个流程就与NSURLProtocol无关了,大神的思路来源是iOS WKWebView (NSURLProtocol)拦截js、css,图片资源,找到了使用WKBrowsingContextController和registerSchemeForCustomProtocol,通过反射拿到私有class/selector,通过kvc取到browsingContextController,通过把注册http和https请求交给NSURLProtocol处理。
    [NSURLProtocol wk_registerScheme:@"http"];
    [NSURLProtocol wk_registerScheme:@"https"];
  • 看到这里就可以实现WKWebView的缓存了,捋一下思路,通过kvc的方式拿到私有接口browsingContextController,在调用registerSchemeForCustomProtocol的时候把http和https也加进去,这样就能拦截到http和https的请求了,然后自己写继承NSURLProtocol的自定义类,重写canInitWithRequest,startLoading等方法,在canInitWithRequest里就可以拦截到http(s)请求了,需要判断是否已经拦截处理过,否则会一直循环调用的,在startLoading中检测本地缓存数据,如果有的话就使用并且开启异步线程去更新缓存,如果没有就去下载调用NSURLSession缓存到NSURLCache中,至此就完成了。

新问题

又发现了新的问题,网页刚打开的时候是可以缓存看到的数据的,但是当点击播放一段音频或者显示一张图片时,就会发现这部分数据未缓存下来。由于这部分属于h5中的点击之后才能请求下来的,也就是说用户必须把所有的点击事件点击一遍才能把需要的图片或音频按照上面的方式缓存到本地,万一用户进来什么也没点,就出去,没网了再打开网页发现只能看,点击之后的音频或图片都不能用,这达不到我们的需求。

下面是我自己完成后的思路

  • 既然WkWebView不能自动缓存所有需要的数据,那缓存不了的数据就让我们自己来下载吧,但是如何把本地数据放入网页中呢。
  • 安卓小伙伴先实现了这样的功能,并从他那边得到了思路,android webview Not allowed to load local resource错误的解决办法,他的实现思路是替换本地路径给到网页,但是发现网页掉本地路径会出现not allowed to load local,文章的大神给出了拦截URL替换资源的方法,我在想苹果是不是也可以呢?
  • 但是苹果没有类似安卓 shouldInterceptRequest的方法,安卓的思路是如果请求包含约定的字段说明是要拿本地的数据,我们在这里重新构造WebResourceResponse 将数据已流的方式传入。
  • 刚开始想我在webView Finish 中用document.image[0] 替换本地流的方式,倒是替换了,但是这样的话不能替换我所要替换的图片啊,不能图片都是下标 0 1 2 3的替换吧,到底在哪儿写替换呢,我又如何获取到需要替换图片的路径,然后把资源给它呢,也不能获取到js代码,在里面操作吧?
  • 那天整理WKWebView的缓存机制流程,发现其实NSURLProtocol也有相应的方法,就是在startloading中,也是拿到缓存数据流并去网页更新缓存的,如果我在这里判断拦截到的路径是图片的话,我就可以把本地的数据流以原来从NSURLCache拿到数据流更新网页的方式来替换网页数据了。
  • 试了一下果然可以,既然图片转成NSData数据流的方式可以,那么音频应该也可以,果不其然完美实现了数据流的替换,这样就实现了网页所有数据的缓存。只不过一部分是网页自动缓存好的,一部分它自己没有自动缓存的,我们拿自己下载好的本地数据流。