汽车之家主机厂离线化 H5 Hybrid 实践

1.背景

H5 页面做秒开优化是业务的常规操作,一般正常通过网络请求的 H5 页面,我们都是围绕资源加载速度优化展开。优化手段主要分两个方向,一个是提升网络速度,一个是减少资源大小。

提升网络速度,一般的手段有 DNS 预解析、多域名、升级 HTTP2、使用 CDN、SSR。而即使有静态资源的网络缓存,HTML 也只能用协商缓存,需要消耗一次网络请求。这也注定了无法避免因网络问题导致的页面白屏时间较长的问题,在我们真实的数据中也能得到印证,无论怎么优化,页面的 1.5 秒开稳定在 90% 以上非常困难。

因此,如果想实现 95% 以上甚至 99% 以上秒开,离线化 H5 是必然的选择。同时根据历史经验,随着 iOS 和 Android 手机的性能不断提升,Webview 的渲染性能也不断提升,目前大部分手机的 H5 离线化渲染都可以实现无白屏体验,无限接近原生的交互体验。

 

2.收益

在实践过程中,我们分两个场景构建离线化 H5 基座,一个是由 H5 开发的新 APP,一个是汽车之家 APP。

主机厂内部有一个应用,第一版是 Native 和 H5 的混合开发,会通过网络请求资源,虽然有网络缓存,但是第一次打开也很慢,非常影响用户体验,内部性能监控平台显示首屏平均耗时 1s。后面全面改造成内置离线 H5 应用,Native 只提供桥功能,首屏平均耗时减少到 237ms,加载速度提升 4 倍,大部分情况下实现无白屏体验。业内曾经做过人眼识别白屏的最小时间测试,当降到 200ms 左右时,人眼几乎无法识别白屏。

新 APP 我们主要使用 H5 开发,而不是用 Flutter 或者 RN 等技术,最主要的原因是人才储备不足,在业务场景并没有特别复杂的原生体验情况下,我们发现业务迭代,对客户端的依赖大大减少,沟通成本降低,迭代效率有明显提高,团队不需要面对复杂的 Flutter 和 RN 引擎,也不需要熟悉客户端的开发模式。

汽车之家 APP 中,由于历史原因,不能预置离线包 H5,所以只能有选择性地进行动态预加载,而这会导致在开屏等特殊场景下,访问速度仍然不高。整体上首屏时间从 1240.4ms 缩短为 505.4ms,加载速度提升一倍。

 

3.技术架构

 离线化 H5 Hybrid 架构上,主要分成四大模块:H5 离线包管理工具、APP 开发工具、Native 运行时和 Webview 运行时。

 

4.离线包管理工具

H5 离线包管理工具包含:离线包管理平台、之家云打包脚本。离线包管理平台包含:APP 应用管理、H5 应用管理、发布回滚、开关控制四大功能。通过和之家云发布流水线联动,可以实现网络版和离线版 H5 的版本同步,发布操作也实现同步,由于之家云功能限制,回滚目前还不支持联动。

管理的物料包括:H5 离线包(H5 代码、离线包配置文件)、APP 离线包配置文件。APP 配置文件,在没有预置离线包的情况下,下载资源是阻塞性的。为了提升下载速度和可用性,APP 配置文件也被维护成了一个 CDN 上的 JSON 文件。

4.1

离线包发布流程

在之家云发布平台,使用离线包管理 CLI 工具,执行上传离线包命令,并配置一个离线包发布的 Webhook,即可实现自动化离线包发布。离线管理平台会同步之家云平台的离线包版本号。

4.2

离线包设计

和普通的 H5 打包文件相比,离线包新增了一个专属的离线包配置文件 config.json,同时会把资源打包成 gzip 压缩包,从而提升整体资源的下载速度。

资源目录

h5id
├── js/
├── css/
├── img/
├── pages
│   ├── index.html
│   └── list.html
└── config.json(配置文件)

配置文件

interface HybridConfig {
  // 离线 H5 APP ID
  h5id: string;
  // 匹配页面和静态资源的规则
  mapping: {
    [env: string]: {
      pages: Resource[];
      resources: Resource[];
    }
  };
  package: {
    // 离线包资源目录路径,根目录相对路径
    file: string;
    // 包含的文件,和 excludes 互斥,只能同时有一个
    includes: {
      ext: string[];
      file: string[];
    };
    // 不包含的文件
    excludes: {
      ext: string[];
      file: string[];
    };
    // Native APP 版本适配
    appRules: {
      // 离线包管理平台的应用 ID
      [appid: string]: {
        // [最小版本,最大版本]
        ios: [string, string];
        android: [string, string];
      }
    };
  }
}

interface Resource {
  // 拦截到的请求 url 的规则,不提供 http 或者 https,支持单个文件和文件目录。
  // 例如:example.com/page,example.com/static/img.png
  remoteUrl: string;
  // 和 downloadUrl 必须有一个存在,相对离线包所在目录的文件路径。
  // 「path」值会替换掉请求 url 中的「remoteUrl」字符串。
  path?: string;
  // 和 path 必须有一个存在,指定下载资源的 url,
  // 下载后存放在离线包的 vendor 目录下。
  // 并把存储 path 同步到配置文件的 path 字段。
  downloadUrl?: string;
  // 可选的 mime type,如果不提供,通过文件名后缀自动补偿
  contentType?: string;
}

 

4.3 

打包命令行工具

  1.  打包发布脚本发布到之家私有源,以脚手架命令形式调用,提供打包、上传命令;

  2. 业务方结合自身编译上线流程进行调用,上传完成则自动进行发布;

  3.  前端静态资源按照页面/工程纬度打包成zip;

  4. zip包含js/css/img/pages/config.json配置文件;

// 脚本安装
npm i @auto/dt-fe-cli

// 编译上传
// 指定脚本的配置文件,打包并上传至服务器,默认配置文件为 config.json,可以使用 --config 指定配置文件
dt-fe-cli offline --config hybrid-config.json

 

4.4 

管理平台

为了更好管理离线包,我们提供了一个简洁的管理后台,用来管理 H5 应用和 APP 应用的关系,记录之家云编译好的离线包,同时提供 APP 配置给客户端查询。为了提高 APP 配置下载速度和可靠性,我们用 CDN 上的 JSON 文件来存储 APP 配置。

►4.4.1 APP 配置

interface APPConfig {

  appid: string;
  version: string;
  updateTime: string;
  isIosEnable: boolean;
  isAndroidEnable: boolean;
  H5Apps: H5App[];
}

interface H5App {
  // H5 应用的 APP ID
  h5Id: string;
  versions: H5Config[];
  lastVerison: {};
  latestUrl: string;
}

interface H5Config {
  version: string;
  // true,开启离线化
  isEnable: boolean;
  // true,开启 iOS APP 离线化
  isIosEnable: boolean;
  // true,开启 Android APP 离线化
  isAndroidEnable: boolean;
  // true,需要预先加载
  isPreLoad: boolean;
  pages: Resource[];
  appRules: AppRules;
  downloadUrl: string;
}

interface Resource {
  // 拦截到的请求 url 的规则,支持单个文件和文件目录。例如:/page,/static/img.png
  remoteUrl: string;
  // 和 downloadUrl 必须有一个存在,相对离线包所在目录的文件路径。
  // path 会替换掉请求 URL 中的「remoteUrl」字符串
  path?: string;
  // 和 path 必须有一个存在,指定下载下载资源的 url,下载后存放在离线包的 vendor 目录下。
  // 并把存储 path 同步到配置文件的 path 字段。
  // 下载失败,则该匹配规则失效自动访问网络资源
  downloadUrl?: string;
  // 可选的 mime type,如果不提供,通过文件名后缀自动补偿
  contentType?: string;
}

interface AppRules {
  // ios APP 的开始和结束版本,最大版本可设置 infinite
  ios: [string,string];
  android: [string,string];
}

 

4.4.2 管理平台截图

5.客户端设计

作为整体 Hybrid 离线包应用架构中的重要一环,端内 Hybrid 离线包 SDK 包括 Webview 管理、离线包管理、Bridge 三个模块。

5.1 

Webview管理

  1. 定制 Hybrid 浏览器,设置可通过特定 Scheme 协议打开;

  2. 如果没有离线资源,可以降级 HTTP 请求,也可以选择阻塞下载;

  3. 通过 H5 应用映射表匹配当前页面 Url 和缓存资源,存在缓存资源时,Hybrid 浏览器拦截 H5 所有资源请求,执行本地缓存逻辑:命中缓存时直接返回本地资源;未命中缓存则交还给WebView进行默认处理;

  4. 关闭 Hybrid 浏览器时触发离线包管理逻辑,进行资源更新。

5.2 

离线包管理

  1.  APP 预置:APP 打包时可以使用命令行工具批量下载需要预置的离线包,并集成到 APP 中;

  2. 预加载:有时候出于 APP 体积的考虑,我们不能预置所有离线包,为了提高离线包的加载体验,可以开启预加载,在 APP 启动后的空闲时间主动进行离线包下载;

  3. 更新:根据唯一性原则,同一个 H5 应用同时只保留一份离线资源;

  4. 磁盘空间管理:及时删除下载失败或已解压完成的 ZIP 包;清理旧版本离线资源;结合 LRU 算法进行离线包缓存上限管理。

  5. 环境隔离

5.3 

环境区分及降级处理

  1. H5 应用区分测试、生产环境,不同环境匹配不同离线资源;

  2. 通过预支的字段开关可以控制是否启用离线包逻辑,开关关闭时直接使用线上资源。

5.4 

Hybrid 离线包方案的下一步规划

Hybrid 方案在预加载模式下取得了较好的效果,有效的提升了 H5 页面的秒开率,后续将在以下几个方面继续提升 Hybrid 方案的能力,更好的为主机厂相关业务助力。

  • 增加并完善预置离线包能力:在 APP 大小可控的前提下,在 APP 内预置关键页面的离线包,弥补预加载逻辑在第一次打开时命中率低的不足。APP接入预置离线包后,页面第一次打开时预计资源命中率提高到100%。完成相关方案如下:

  • 增加 One Shot 能力(类小程序):支持在 Hybrid 浏览器首次访问 H5 应用时,实时下载离线资源包并匹配离线资源。APP接入One Shot 能力后,页面第二次打开时预计资源命中率提高到100%。相关方案如下:

6.踩过的坑

6.1 

Post 请求丢失 Body

 iOS 系统,浏览器拦截协议有 NSURLProtocol 和 WKURLSchemeHandler 两种,并且都存在 Post 请求丢失 Body 的问题,针对 Post 请求都需专门处理。

开始时使用的是 NSURLProtocol 协议,优点是可挑选处理请求,不需要处理的可抛回浏览器,但是在特殊场景下出现问题:NSURLProtocol 是全局拦截,打开后所有浏览器都会进行拦截,所以在多个浏览器同时存在,并且非Hybrid浏览器还有发送 Post 请求时,Post 请求 Body 会丢失,导致请求失败。NSURLProtocol 的全局拦截问题无法解决,于是又将目光移向 WKURLSchemeHandler,开始了完全自己实现Http请求。

6.2 

无侵入式拦截

 WKURLSchemeHandler 需要解决的问题有很多,包括Cookies,重定向,Post请求支持等。最开始沿用了NSURLProtocol Post 请求处理方法,业务方将Post请求通过桥的方式,扔给Native进行请求,但紧接着就遇到另一个问题:需要业务方配合改造。作为一个通用平台,接入成本过高是一个致命问题,直接影响业务方接入的意愿。

实现无侵入式拦截,是我们必须要解决的问题,最终通过多次实验,采用了JS注入拦截的方式,具体流程主要为以下几点:

  • 若命中离线,加载网页时开启Handler拦截,注入 Fetch /  XMLHttpRequest 拦截请求脚本;

  • 发送请求时,Post请求通过Bridge发送给Native,Get请求又传递给原生应用进行存储处理WKURLSchemeHandler;

  • Handler拦截的请求若命中离线,走本地资源匹配,匹配到后模拟请求返回H5,未命中时Native发送请求;

  • Post请求,Native通过桥拿到url和Body信息,通过原生请求发送,结果透传给H5。

 

7.总结

随着移动设备整体性能的持续提升,离线化 H5 Hybrid技术架构在加载和交互体验方面取得了显著改善。在许多标准应用场景中,它能够提供接近原生应用的用户体验。此外,它的跨平台、低成本、充足的人才资源、丰富的生态系统和动态更新等优势,使其在与其他跨平台解决方案的比较中脱颖而出。

 

作者| 主机厂BU-技术部

热门相关:总裁的天价小妻子