目前本人所负责的APP中使用了大量的h5页面,所以在使用h5资源时,性能优化是作为客户端开发的重点。虽然现在APP已经使用了 webView自带缓存、硬件加速、等缓存方案,但是针对首次打开速度仍然很慢。因此项目组提出了h5离线方案。
h5 离线优点
1.节省流量:下载资源包为非一次性消耗资源,每次加载 h5 都可以从本地获取。
2.秒开:不需要通过远端加载 h5 资源。
3.防劫持:不需要从远端加载 h5 资源。
实现方案
离线方案对于不同开发人员有不同的实现方案,本人提供的方案也不一定是最好的方案,所以参考即可。
1.将 h5 需要的资源下载到本地。
2.根据 h5加载链接路径和本地资源路径做一个映射
3.加载时拦截 h5 资源,根据 h5 远程路径获取本地路径,直接加载本地数据。
目前使用的映射方案,例如:
远程链接:http://www.baidu.com?path1/path2/path3/index.html
对应的本地路径:根路径/www.baidu.com/path1/path2/path3/index.html
注:其中本地路径中:www.baidu.com 和 path1、path2 等都为文件夹名称。其中最后的 index.html 为具体文件。
ios 端实现方案
此处需要使用 URLProtocol 进行拦截。需要使用到 URLProtocol 的协议方法
func canInit(with request: URLRequest) -> Bool
func canonicalRequest(for request: URLRequest) -> URLRequest
func startLoading()
func stopLoading()
解决WKWebView 通信问题
在使用 URLProtocol 进行资源拦截的时候,如果使用 UIWebView 则没有问题。但是如果使用WKWebView 则会有问题:URLProtocol 无法拦截到 WKWebView 上的 request 请求。此时,需要使用私有 api ,通过注册 http(s) 则,可以使用 URLProtocol 拦截到 WKWebView 的 request。
类
browsingContextController
注册 scheme
registerSchemeForCustomProtocol
取消注册 scheme
unregisterSchemeForCustomProtocol
WKWebView 后遗症:body 丢失问题
WKWebView 那些坑
苹果源码
通过私有 api 虽然可以拦截到 http(s) 请求,但是对于 http(s) 的 post 请求则会丢失 body。原因为app 和 WKWebView 为两个进程,通过注册 scheme 可以将 WKWebView 中的 request 通过 IPC 通信传递到 app 进程中,所以才可以拦截到 request。但是在经过 IPC 通信时,会将 request 进行 encode,在 encode 的过程中,为了提高性能以及安全会将 body 和 bodyStream 进行丢失,从而造成 post 请求丢失 body 的情况。
为了解决 body 丢失问题,可以有以下方案
注:post 请求与是否拦截成功无关,是由于走 IPC 通信丢失的。
方案 1:使用 UIWebView
使用 UIWebView 不会造成跨进程,也就不需要跨进程通信,也就不需要注册 scheme 等,所以可以完美解决,但是如果必须使用 WKWebView,则使用其他方案。
方案 2:自定义 scheme
因为注册 http(s) 才能让 http(s) 请求通过 IPC 通信,而造成 body 丢失,所以可以选择使用自定义 scheme 的方案。这样就会让自定义的 scheme 通过 IPC 通信。
优点:
1、可以避开http(s) 经过 IPC 通信,从而避免丢失 body
缺点:
1、需要将h5 离线资源的 shceme 都换为自定义的 scheme
2、需要后端做大量工作
方案 3:WKURLSchemHandler
该方案和方案 2 一样,都是需要自定义 scheme 并且 WKURLSchemHandler 需要 iOS11 以后才能使用
方案 4:使用 hook-ajax 的方案
hook-ajax方案原理为使用一段 js 代码,将 xmlhttprequest 进行代理一份,这样避免 body 丢失。
可行性:
1.该方案需要注入一段 js 代码,网上有
2.作为客户端开发,最好需要对 js 代码有一定的了解
3.仅仅对 xmlhttprequest 有效,针对 fetch 请求无效。
注:hook-ajax 仅对 XMLHTTPRequest 有效,但是目前使用的 h5 资源中使用了大量的 fetch 请求所以暂时没有使用。
方案 5:使用频繁 register 和 unregister 的方案
因为当时 app 每次加载 url 都是使用一个新的 controller,所以可以在每次加载controller 时判断是否为离线资源链接,如果为离线资源链接,则 register scheme,否则 unregister scheme。
注:考虑到 h5 资源内部有一些上报埋点等 post 请求。如果此时 register scheme 则会造成埋点 post 请求丢失。
方案 6:使用本地服务器
该方案持续研究中。。。
总结
h5 离线开发可以提高加载 h5 速度,防止服务器劫持等优点,但是拦截 h5 资源需要使用 URLprotocol,但是 拦截 WKWebView 会出现 post 请求丢失 body 问题,所以使用 wkwebview 需要首先解决 post 请求 body 丢失问题。丢失原因为:
1.拦截 WKWebView 的 h5 资源需要执行私有 api,进行 register scheme 和 unregister scheme.
2.执行私有 api 会走 IPC 通信,造成 post 请求丢失 body 的情况。
注:鉴于post 请求丢失 body 问题和目前现有的解决方案不能 100% 避免带来的问题,或解决了改问题可能带来其他的问题,所以iOS 端暂未实行 h5 离线方案