Hybrid资源离线化及离线加载

8/13/2018 iOS

# 背景

2018年随着App组件化以及完成,H5在App中由XXXBLWebViewJSBridge和XXXBIZLenderWeb两大组件支撑,其中XXXBLWebViewJSBridge负责Native和H5之间进交互的通道管理,XXXBIZLenderWeb则是H5运行的容器和业务Handler,比如:方法调用、服务调用、页面跳转等。由于H5在App中加载的特性,开发人员必须关注H5的性能,比如:白屏时间、可交互时间、完全加载时间等。App中所有的H5加载时间都会对用户体验有直接的影响,进而影响整个App的体验。

于是我们在Native侧构建了用于离线和优化H5加载的组件-WebViewLoader,下图是WebViewLoader组件架构,本文主要介绍WebViewLoader组件是如何对H5进行加载和离线缓存的。

组件架构

# Hybrid前端资源缓存优化方案

对于Hybrid前端资源的优化大致分为两种做法:

  • 一种是加载之前对URL进行分析,转换为本地对应的File,从本地进行加载,支持UIWebView和WKWebView
  • 一种是拦截Request,将Request中的资源从本地加载,支持UIWebView,WKWebView不支持

第一种方案比较适合多页应用,第二种方案多页应用和单页应用均适合。不过总的目的是相同的,就是将前端的资源下载到移动本地,从移动本地加载对应的资源,包括HTML、CSS、JS和Image等。下面分别对两种方式进行介绍

# URL转换为本地文件加载

在流行单页应用之前,每一个页面对应着一个HTML文件,前端资源离线化及加载非常适用这么做,前端文件Zip包下载到移动端,解压的对应目录,加载的时候将http链接转换为本地File文件进行加载即可。

资源文件离线化和加载过程如下面两个流程图所示,流程图已经非常清晰,不再赘述。

资源更新解压过程:

解压更新

加载资源过程

加载ur

# 拦截Request加载

拦截Request常见的方法是通过NSURLProtocol和Hook两种方式,而对于WebView的加载,使用NSURLProtocol拦截即可,但是不能够支持WKWebView,因为WKWebView的回调都是IPC跨进程的,网上流传的私有接口拦截有一个大问题就是Post请求会丢失掉body,iOS11虽然又提供了拦截注册,但是不允许注册http和https这种系统默认的schema,即便使用自定义schema,对H5侧则有比较大的侵入,而且在App中存在大量的第三方页面,比如论坛、文章等,并不能让所有业务方都配合改造,而此时XXXBLWebViewJSBridge和XXXBIZLenderWeb两大组件又同时支持UIWebView和WKWebView,最终决定采用拦截Request加载,并切换到UIWebView的方式进行优化。

# WebViewLoader架构

组件架构

WebViewLoader主要实现的思路是,提前请求DOM,拦截DOM和资源请求,使用Native网络请求DOM和资源文件并做缓存和更新。

  1. SessionManager

    负责管理所有DOM和资源请求的session,通过传入URL,创建对应的Session

  2. Session

    DOM和资源所使用的会话,负责URL请求的完整状态流程

  3. Cache

    继承自YYCache,负责为Session提供缓存处理

  4. CacheItem

    存储缓存的数据,responseHeader,并管理缓存的过期时间

  5. Request

    Session加载URL使用的请求,并存储请求、 状态、返回和回调等

  6. RequestManager

    负责管理请求,通过特定的operationQueue完成网络请求,并将请求的结果通过block回调给Session

  7. URLProtocol

    负责拦截WebView的DOM和资源请求,并将Session发起的网络流桥接给WebKit

# 首次加载

首次加载网页的时候,在未创建UIWebView之前先通过SessionManager创建Session并发起网络请求,然后UIWebView创建并发起DOM资源请求,URLProtocol层负责拦截DOM资源和图片、样式、JavaScript等资源,并将提前发起的数据流返回给WebKit,从而实现网络提前并行加载。在网络流完成结束后,对DOM数据和资源数据进行缓存保存,缓存规则会根据资源的max-age或者Expires进行设置有效期,否则缓存默认过期时间为5分钟。

首次加载

# 缓存未过期加载

在DOM或者资源文件有缓存且未过期的情况下,Session在start之前会判断有没有缓存和缓存有没有过期,如果缓存没过期,则直接返回,URLProtocol在拦截请求后的startLoading方法里注册回调,Session直接将Cache的response、data和finish桥接给URLProtocol,并将数据流返回给WebKit

缓存未过期加载

# 缓存过期加载

在DOM或者资源文件有缓存且过期的情况下,会先加载Cache的内容,并且Session发起网络请求,如果返回回来的DOM有更新则首先更新缓存,然后通过回调通知WebView刷新Request,而此时新的数据已经在Cache中,相当于从Cache中重新加载DOM,如果数据没有更新,则更新缓存中的header和过期时间。

缓存过期加载

备注: 关于资源文件的加载,一般情况下我们要求css,js和图片资源url不会重复,所以也不会出现update的情况,只有URL相同的DOM更新。

# 优化效果

App Web加载展示的性能一直通过听云来看的,看一下优化前后的对比效果。可以发现,从web趋势来看完全加载、白屏、首屏和可交换所消耗时间均有明显的下降。从页面分解图来看,HTML加载,DOM解析和页面渲染的耗时都有明显下降。从网络加载分解图来看,DNS、TCP、SSL、首包和剩余包的加载耗时也同样有明显下降。在不需要前端优化的情况下iOS平台加载耗时如下:

  • 完全加载:1.876s
  • 白屏时间: 0.162s
  • 首屏时间:0.162s
  • 可交互:0.57s

优化效果

再经过Web端的优化目前可以优化到:

  • 完全加载:0.983s
  • 白屏时间:0.099s
  • 首屏时间:0.099s
  • 可交互时间:0.409

# 配合配置中心进行精细化管理缓存时间

在首次加载中,假如资源未设置max-age和Expires,缓存默认过期时间为5分钟。而随着配置中心的上线,通过配置中心可以对某个站点或者某一类URL进行精细话设置缓存过期时间,WebViewLoader会根据资源URL匹配配置中心设置的过期时间配置,从而可以差异化设置缓存过期时间。 比如将某个URL的DOM设置为不缓存,则可以在配置中心进行如下配置: [{"url":"https://xxx.123.com/eucharist/8","time":0}] WebViewLoader设置DOM缓存时,匹配到此URL,则会根据配置的time时间设置缓存时间

# 总结

WebView目前能够有效的缓存DOM、图片等资源,并根据配置中心的配置进行精细化管理缓存时间。大幅降低了Web页面加载时长,提高了用户体验,唯一不足的地方是对WKWebView不支持。后续会继续探索如何支持WKWebView。

Last Updated: 1/15/2023, 2:48:14 PM