一、需求背景
为了提高用户体验,针对 APP 内嵌的 H5 页面,有一种比较常见且有效的方式就是接入离线包,通过把页面加载需要的静态资源提前下载到客户端本地,避免页面加载时静态资源网络请求的开销,从而来提高页面的加载速度。
二、知识储备
1.webview加载h5的过程:
打开一个h5页面通常会有较长时间出现白屏,以及几秒后出现内容,在这几秒中到底做了什么事情?因为在这其中做了很多事情,大约包括:
初始化 webview -> 请求页面 -> 下载数据 -> 解析HTML -> 请求 js/css 资源 -> dom 渲染 -> 解析 JS 执行 -> JS 请求数据 -> 解析渲染 -> 下载渲染图片
一般来说, Webview渲染需要经过下面的几个步骤:
- 解析HTML文件
- 加载 JavaScript 和 CSS 文件
- 解析并执行 JavaScript
- 构建 DOM 结构
- 加载图片等资源
- 页面加载完毕
一般页面是在dom渲染后才能展示,h5首屏渲染白屏问题的原因关键在于,如何去优化请求下载->渲染之间的耗时成了重点.
2.WKWebView拦截请求:
研究了业内已有的 WKWebView
请求拦截方案,主要分为如下两种:
NSURLProtocol
NSURLProtocol是一个抽象类,作为URL Loading System
系统的一部分,能够帮助我们拦截所有的URL Loading System
的请求,在此进行各种自定义的操作,是网络层实现AOP(面向切面编程)的利器。
WKURLSchemeHandler
WKURLSchemeHandler
是 iOS 11 引入的新特性,负责自定义请求的数据管理,如果需要支持 scheme 为 http 或 https请求的数据管理则需要 hook WKWebView
的 handlesURLScheme
: 方法,然后返回NO即可。
两种方案对比
| NSURLProtocol | WKURLSchemeHandler |
| 隔离性 | 一经注册就是全局开启。使用了 NSURLProtocol
的方式后会导致应用内合作的三方页面也会被拦截从而被污染。 | 可以以页面为维度进行隔离,可以通过WKWebViewConfiguration
进行配置。 |
| 稳定性 | NSURLProtocol
拦截过程中会丢失 Body | WKURLSchemeHandler
在 iOS 11.3 之前 (不包含) 也会丢失 Body,在 iOS 11.3 以后 WebKit 做了优化只会丢失 Blob(二进制的一种数据类型) 类型数据 |
| 一致性 | WKWebView
发出的请求被 NSURLProtocol
拦截后行为可能发生改变,比如想取消 video 标签的视频加载一般都是将资源地址 (src) 设置为空,但此时 stopLoading
方法却不会调用 | 表现正常 |
****结论:****WKURLSchemeHandler
在隔离性、稳定性、一致性上表现优于NSURLProtocol
,但是想在生产环境投入使用必须要解决 Body 丢失的问题。
三、技术实现
H5离线包的基本原理是将html、js、css、图片等静态资源打包成压缩包,然后下载到客户端并解压,H5加载时直接从本地读取静态资源文件,减少网络请求,提高速度。
3.1 总体流程
客户端启动后,先去远程配置服务器拉取离线包相关的功能配置,然后检查更新,如果有更新则下载离线包。webview加载时,如果本地缓存命中,则从本地磁盘加载html、js、css、图片等静态资源。。
3.2 离线包和非离线包对比
webView最简单的做法是直接通过URL去加载一个线上页面。当从浏览器输入一个URL到页面中间经历了什么?
从上面看出用户加载一个web页面都要经过多次网络加载,中间是极易受到网络波动的影响,在无法保证页面加载的时长和成功率的情况下,会很大影响用户体验。
于是把html、js、css都抽离出来做成离线包放在本地,这样加载一个web页面的过程就如下图
3.3 行业方案
| 方案名 | 优点 | 缺点 | 备注 |
| 加载本地路径 | 简单可靠,无需hook和调用私有API | 有跨域问题,影响cookie和localstorage,H5需做少量改动 | 货拉拉方案 |
| 请求拦截 | 不修改加载URL,没有跨域问题,且支持网页部分资源离线化,灵活性和兼容性好 | iOS端目前提供的NSURLProtocol和WKSURLSchemehandler拦截方案有缺陷,前期实现成本高 | 网易云音乐方案zhuanlan.zhihu.com/p/347592487 |
| 本地Web Server | 兼容性好 | 对客户端耗电和CPU性能有影响 | 暂未发现有公司采用,juejin.cn/entry/68449… |
| Service Worker | 前端兼容性好 | iOS端WKWebView不提供官方支持,实现技术难度大 | 爱奇艺方案zhuanlan.zhihu.com/p/148931732 |
四、接入工作:
目前我们采用的方案是:本地文件路径(file://协议),可能存在一些问题,所以在接入离线包前,我们需要对 H5 项目进行改造。
4.1 问题和解决办法
| 存在问题 | 解决方法 |
| cgi 请求跨域问题 | 在网关或者后端服务的跨域请求头增加 null 域支持 |
| cookie 跨域问题 | 我们接入的项目中无 cookie 操作,如果有的话,需要改成用请求 header 的方式。 |
| localStorage 跨域问题 | 我们接入的项目中 localStorage 不涉及域名隔离问题,如果有需要,可以采取调用原生的方式来做前端存储。 |
| 资源引用的绝对路径在离线包模式下不支持 | 改成相对路径 |
4.2 接口跨域处理
目前我们线上模式的请求链路大致为H5 → 网关 → 后台服务
,大部分的项目都是通过一级通用网关来处理跨域问题,但是由于离线包模式下需要网关设置允许 origin: null
的场景跨域,直接在通用网关处理可能存在安全问题。
目前我们的实现如下:
通过hook header["是否是接口请求"]判断是否拦截请求,然后替换根据header["x-request-host"]来替换当前的host;一句户就是app帮助h5请求网络再把结果回调给h5。
五、参考资料
苹果官方文档https://developer.apple.com/documentation/webkit/wkwebview?language=objc
货拉拉H5接入离线包的实践总结 zhuanlan.zhihu.com/p/538288299
WKWebView之离线加载以及遇到的问题https://blog.csdn.net/Lu_Ca/article/details/125723040
WKWebview秒开实践分享及问题解决方案(抖音)https://juejin.cn/post/6887161842406260744#heading-6
WKWebView 请求拦截探索与实践(网易)https://juejin.cn/post/6922625242796032007