「最佳实践」WebView 预加载与资源缓存

前文讲到 唯一一种拦截 WKWebView 资源请求的方式,本文将会把整套预加载和缓存方案整理下,供大家借鉴参考。

背景

方案目标

  1. 在 App 中,让Web 在线页面达到Web 离线包加载效率。
  2. 无需改变当前前端开发的流程以及基建。

在线方案与离线方案

简单讲一下在线方案达到离线方案的区分。

在线方案

Web 容器直接加载远程 URL,本质上与在浏览器上加载 URL 没什么不同,在实现上提供一套 JSSDK,提供一些局限的能力,比如获取 Token、获取当前信息等。

关键优点:前端 / 终端容器基本上是解耦的,只要适配竖屏 UI 的 Web 页面都可以在在线方案中加载。对前端开发体验来说也是很舒服的一种方案,只要关心前端本身即可。

关键缺点:

  1. 用户体验上差强人意,再怎么优化,用户也能感知出这个是浏览器加载的方式。
  2. JSSDK 只能提供局限的能力,因为有跨域问题,这里的跨域不是域名间跨域,而是 App 容器和 HTTP 间的跨域问题,比如 Web 页面访问 App 本地资源。
  3. 这一点是对第二点局限能力的补充,JSSDK 还需要各种加密安全保障(比如授权 Token 与 URL 绑定的方式),但其实也没什么卵用,这也是微信公众号多年不更新 JSSDK 能力的原因,因为无法保证能力安全。

这更适合资讯类、营销类、广告类等非核心业务实现。这其实也符合 iOS 对 Web 开发的定义,非核心业务,理论上 Web 页面不能超过整个 App 的 35%,不然有拒审的风险。

离线方案

Web 容器加载的是 App 本地的资源,这也是小程序能成的最重要的基础(原生混合渲染那些只是体验优化,所以这里不单指微信小程序,比如冷门点的云闪付小程序,其实就是把在线 URL 打包成压缩包上传)。

关键优缺点就是在线方案反过来,用户体验更好,能用各种 App 上的底层能力,但开发体验更差,没办法直接融入前端体系。

开发体验差的主要问题在于:

  1. (重点)调试难,微信/支付宝都有自己的 IDE 开发调试工具,“基本” 可以保证在 IDE 的效果与在 App 上是一致的(坑也是多的很,特别是支付宝小程序)。
  2. 无法直接复用前端架构/生态/基建,比如 BFF 层就用不了,导致相同功能需要开发离线版、在线版,重复劳动。

在线页面离线化

列举了以上的优缺点,那可以只要两个优点都要吗?

答案肯定是可以,不然笔者在这说什么废话 ~

在降本增效的大前提下,前端开发一套 Web 页面,可以在各个容器中正常使用,且达到与离线包一样的体验,就是我们的工作目标了。

思考

如何离线化在线页面,其实能选择的方式也不多,很多大厂也会选择使用一个极小化的 WebView 内核来提前加载资源,这样的好处也很明显,App 容器上无需更多的关心缓存的实现方式。

但带来的问题就是依托 WebView 进行资源缓存,那在预加载上就必须启动一个 WebView 容器,这一点其实有性能损耗的,如果有多个页面需要预缓存,那就必须开多个容器或者提供一个预加载队列,控制起来也是尤为费事,特别在预加载完之前用户就进入页面的情景下,缓存意外也是有可能的。

那对我们来说,在放弃了使用极小化的 WebView 内核进行资源缓存的方案后,能选择的就是提前把页面上的资源下载到 App 本地,然后通过请求拦截的方式进行本地资源加载。

方案

在之前的方案上,是通过直接下载 HTML,然后通过正则的方式解析其中的 CSS、JS 文件,进行提前预加载。

但这会有一个问题,那就是不能去缓存 HTML 文件,不然会导致更多的问题:

问题1:  缓存更新不及时。

虽然每次启动后都会预加载最新的页面,但如果预加载未完成且前端有更新,用户还是会返回到旧的 HTML。

这一点可以使用严格模式,在预加载之前,先清理掉缓存,访问的时候如果没有缓存,页面会按原始情况加载,不再使用缓存加速。

那对于用户来说每次重启 App 后都会拉取最新的。

问题2: 服务器抖动等情况下,下载到不正确的页面。

这一点是完全有可能的,毕竟我们是把页面当作资源下载下来,并没有经过 WebView 解析,所以我们要检测下载的内容是否正确。


而如果没有对 HTML 进行缓存,相当于离线化方案少了一条腿,并不能彻底的达到我们想要的加载秒开率。

那在优化后的方案是这样:

image.png

预加载

从上图中可以看到,我们在 Web 中增加了一个 manifest 的清单文件,它是一个 JSON 格式文件,里面描述了:

  • indexMD5 HTML 内容校验标识
  • indexURL HTML 访问地址
  • assets 资源列表

那在 App 上,预加载要做的事就变成:

  1. 通过 get 请求,拿到 mainfast.json
  2. 根据indexURL下载 HTML 内容
  3. 根据indexMD5校验下载内容是否正确
  4. 根据assets下载资源列表

职责更清晰了,且稳定性变的更高。

而预加载的时机上:

  • 在首页加载后3秒执行,尽量不影响首页加载效率。
  • 在用户从后台返回到前台时执行。

缓存

整体缓存分为几个部分:

  1. 清单和路由的关系缓存,这个采用 key-value 的方式持久化存储,key是清单地址,value是对应的路由列表。
  2. 清单对应的 HTML,也是采用 key-value 的方式持久化存储,key是清单地址,value是 HTML 内容。
  3. 清单对应的资源文件,采用本地文件映射的方式,把资源文件一一映射成本地路径,所以可以直接判断文件是否存在,不存在再下载,提高缓存性能。

image.png

而这还要注意处理的是缓存冗余,对于前2种 KV 缓存来说,缓存并不会产生冗余。但对于资源来说,会随着前端发包而变得越来越多,这时候就需要清理掉这部分过时资源。

清理手段也很简单,分为2步:

  1. 获取最新的 manifest,对assets进行差集对比,标记出已过时的资源。
  2. App 重新启动后,清理过时资源。

这样保证在保证预下载过程中,用户打开网页不会产生资源丢失等异常情况。

image.png

稳定性

安全第一一套新方案落地前,需要有完备的监控手段以及可快速降级手段,来避免上线风险。

而对缓存及预加载的要求,就是一旦发生异常情况,及时抛弃缓存,直接加载页面。这在有了清单文件后,变得十分可行:

  1. mainfast 是 JSON 数据,当返回异常场景下,读取 JSON 失败,则放弃本次缓存。
  2. 下载的 HTML 校验 md5 失败,则放弃本次缓存。
  3. 增加白屏监测,出现白屏现象则及时放弃缓存并刷新页面。
  4. 灰度出现问题反馈,及时关闭功能开关,降级为之前的实现方式。

总之就是一有异常就丢弃缓存,一有反馈及时停止实验。

资源访问

通过请求拦截的方式,具体见:唯一一种拦截 WKWebView 资源请求的方式

后续

这只是整套 Web 容器方案的一小部分,但笔者觉得也是值得拿出来分享一下,新 Web 容器会在近期灰度上线测试,到时候还有什么坑笔者也会持续分享 ~


感谢阅读,如果对你有用请点个赞 ❤️

中秋节GIF动图引导在看提示.gif

本文正在参加「金石计划」

猜你喜欢

转载自juejin.im/post/7223653286465273911