转转Hybrid体系建设

前言

目前转转 App 是一个典型的 Hybrid App,采用的是业内主流的作法: 客户端内有大量业务页面使用 WebView 加载 H5 页面承载。html

其优势是显而易见的,即:前端

  1. Web 页面上线频度知足快速迭代的业务需求,不受客户端审核和发版的时间限制
  2. 能够将各个业务线的开发工做分摊到各个业务的前端团队上,使得个业务线能够并行开发。

其存在的问题也是很明显的,好比加载性能问题,白屏问题,界面展现的局限性、操做的局限性,没法使用系统功能等。node

转转 Hybrid App 架构

转转 Hybrid 架构设计以下图 image imagewebpack

转转整个 Hybrid 容器架构设计分为四层web

  1. 原生 Web 页面 这块就是你们常见的 Web 开发环境,能够经过 Vue、React 等实现。
  2. Web 加强 包括像离线包和 Web 页面容器等。
  3. 中间件层 中间件层使 Web 页面容器和转转底层框架有机结合起来,同时还提供各类生命周期机制、事件机制、扩展插件等内容。中间件层经过 JSBridge 将客户端以及中间件层提供的各类能力和 Web 前端代码进行联通。
  4. 转转基础架构 主要包含 native 相关的能力架构

通讯模块 - JSBridge

JSBridge 是 WebView 容器的基石,桥接了 JS 环境与 Native,实现了 Native 代码和浏览器环境的双向通讯。ajax

Native 代码能够经过调用浏览器提供的接口运行 JS,从而实现调用 JS 函数、传递参数到 JS 环境等。算法

而浏览器到 JS 环境的通讯是经过 Native 拦截浏览器的请求来实现。npm

经过 JSBridge 咱们就解决了 WebView 界面展现的局限性、操做的局限性和没法使用系统功能等问题。后端

JSBridge 原理以下图 image浏览器

预加载模块 - 离线包系统

为了作到良好的用户体验,咱们在容器中引入了离线包机制。经过离线包机制,咱们将原有从线上加载的 Web 页面,提早下发到本地,经过读取 IO 或者内存来进行页面的渲染,达到接近原生的用户体验。

经过发布平台,咱们能够将不一样的 Web 页面离线包,以单独应用的形式,进行不一样维度的下发,使原来 Native 发布模式,改成各业务线自行定制发布计划,自行制定发布标准,自行发布的并行发布形式,来知足业务的快速迭代。

Web 应用资源离线系统方案图 image

下面咱们从前端构建、测试、发布和 APP 端策略 两个方向分别来分别阐述:

前端构建、测试和发布

咱们采用腾讯 Alloy 团队出品的 Webpack 离线包插件:Ak-webpack-plugin,其能够根据配置,将 Webpack 的构建出的静态资源,压缩成映射了静态资源在 CDN 路径 URL 的 ZIP 压缩包。

同时在配置的过程当中,也能够选择排除掉部份文件(好比图片,并非全部构建出的图片都是用获得的)。其生成的压缩结构以下:

image

在此过程当中,咱们不须要关注资源之间加载的依赖关系,更不须要关注具体的业务逻辑。只须要关注 Webpack 构建后生成的资源文件夹的结构。

把生成和上传离线资源包的过程封装成在npm run build中,经过脚手架生成项目可让业务方无感接入。

生成的离线资源如何上传呢? 咱们利用持续集成和发布工具 Beetle(转转自研的 CI 系统,能够理解为 Jenkins)来自动发布。

在本团队的发布与上线流程中,Bettle 代替 Fe 工程师构建与部署前端项目。使前端项目也像传统的 App 与后端项目同样作到了开发与构建部署分离,提升了团队的效率。

而咱们生成和部署离线包的操做,也交由 Bettle 替咱们完成。当部署到测试环境时,Bettle 会把离线包发布到测试版的离线包环境中,当正式线上时,还会发布到正式环境。这样整个测试流程也能够测试离线包。避免离线包形成的一些问题。

APP 端策略

在客户端每次启动或从新打开一个 Webview 的时候,都会经过接口读取一份最新的各业务线的离线包与版本号的配置表。App 根据本地的配置文件和线上的作对比,下载须要更新的离线包资源。

资源包加载的优化

增量的资源更新(bsdiff/bspatch)

客户端内置(或者在 WIFI 环境下载)了各业务全量包的基础上,为了减小每次下载更新的资源包的体积,咱们采用了增量更新策略。 该策略具体为:每次发布版本的时候,若是此业务线以前已有离线包,则经过离线系统生成差分包放在 CDN

增量更新的策略使用的是基于 node 的 bsdiff/bspatch 二进制差量算法工具 npm 包 bsdp。客户端下载差分包后使用 Bspatch 合成更新包。

单独控制各个业务线 Web 应用是否使用离线机制

为了更好的监控离线包服务端和客户端的运行状况,而且下降使用离线资源带来的不可预料的风险,将隐患作到可控。 咱们在每个业务上都加入了使用离线资源的开关和灰度放量的控制。

数据一致性校验与数据安全性

为了防止客户端下载离线资源时数据在传输过程当中出现窜扰,致使下载的离线包没法解压,咱们在服务端经过接口中将资源包的 md5 值告知客户端,客户端下载后经过计算获得的资源包的 md5 值,与之比较,能够保证数据的一致性。

同时为了保证传输过程当中,资源文件不被篡改,咱们将上述的 md5 值经过 RSA 加密算法进行加密。在服务端和客户端分别使用一对非对称的密钥进行加解密。

离线包批量下载

在启动 App 时,App 会集中批量的下载各个业务线的离线包资源,咱们在存放离线包资源的 CDN 中使用了 HTTP/2 协议,这样客户端与 CDN 只须要创建一次链接,就能够并行下载全部的资源。

在须要下载离线包个数较多的状况下,能够比传统的 HTTP1 有更快的传输速度。 同时,客户端只须要运行一次下载器。减小屡次运行下载时对手机 CPU 的消耗。

基础包包自动更新

当增量包的体积达到基本包的 60% 以上。咱们就认为须要更换基础包。

离线包回退机制

在实际状况中,为了不用户下载离线资源或者解压资源失败等问题,致使没法加载相应的离线资源,咱们设计了回退机制,在

  • 本地内置的 Base 包(ZIP 文件)解压失败的时候
  • 离线系统接口超时
  • 下载离线资源失败
  • 增量的离线资源合并失败等状况下

遇到上述状况时,咱们会转而请求线上文件。

WKWebView 离线包实现

iOS 系统存在两种 Webview 类型分别是 WKWebViewUIWebViewUIWebViewiOS 2.0 就有,而 WKWebViewiOS 8.0 才有,毫无疑问 WKWebView 将逐步取代笨重的UIWebView

UIWebView 有不少问题,好比占用过多内存等。 WKWebView 提高是在多方面的, 好比网页加载速度有很大的提高,内存提高也很是明显,支持更多的 HTML5 的特性等。

转转在很早的时候就切换到 WKWebView。可是WKWebView 不能像安卓同样, 直接拦截请求。WKWebView 中实现离线包一共有三种方案,分别是

  • 私有 API 方案
  • 自定义协议方案
  • LocalWebServer 方案

转转在实践中都作了尝试,其中遇到了不少问题,这里就不单独展开了,你们能够关注咱们,期待咱们的下一篇文章《WKWebView 离线包实践》。

离线资源管理平台

对于接入了离线系统的各个业务线的前端工程生成的不一样时间版本的离线包,咱们须要有一个直观明晰的方案来对其进行管理。

因而,咱们开发了离线资源管理平台,对接离线后台系统,系统界面以下图 image

其主要的功能包含有:

  • 查看与管理各个业务线信息及其离线功能的灰度放量的比例。 对于新接入离线系统的前端工程,灰度放量可使得部分用户先使用其离线的特性,并防止不可预料的问题发生在全体用户上。

  • 经过业务线,版本号,发布时间等条件,查询各个版本的离线资源包的列表及其详细信息。 如 离线包的类型,体积,上线时间等属性。

  • 并在此基础上容许将某版本的离线包下线,以实现离线资源版本的回滚功能。

  • 针对全局的离线功能,提供了离线功能的开关。

公共资源预加载

在离线包系统中,由于咱们是按业务线划分,因此并无公共资源包。所以,咱们设计了一个利用浏览器缓存策略来缓存公共静态资源的策略。

App 端为提升 WebView 打开的效率,会使用 WebView 复用池策略。其策略的第一步就是在 App 启动时会初始化一个备用的 WebView。

咱们正是利用了初始化的 WebView,来加载一些公共的资源,这样可让以后的页面再加载该资源时,可使用 HTTP 缓存。

咱们开发了一个管理平台,用来实时的管理 App 须要加载的公共资源文件。 image

页面接口预请求

咱们经过离线包解决了资源加载的问题,可是首页的接口请求也是一个须要优化的地方。

现有 Web 页面加载流程能够简单归纳以下:先是加载 JS,在生命周期中请求数据,等待数据返回,渲染页面。

那咱们是否能够经过客户端提早帮忙请求接口,而后直接从客户端把数据取回来?答案是确定。

PS:接口预请求的方案,适合首页请求了多个接口的项目,若是只有一两个接口,效果没有明显提高。

如何告诉客户端咱们须要请求这些接口

咱们经过离线包的配置文件上新建一个ajax字段:

ajax: {
  "mode": "hash",
  "split": "#",
  "routes": [
    {
      "router": "/content/index",
      "requestOffline": true,
      "route":[
        {
          url: '/zzgift/creditandexchangecount',
          key: 'creditandexchangecount',
          des: '获取星星信息',
          method: 'post',
          isNeedLogin: false,
          params: {}
        }
      ]
    }
  ]
}
复制代码

首先,用户点击打开 Webview,App 请求 Ajax 列表的接口而且把本地的上次请求的数据删除,同时加载 WebView。

JS 在请求接口时,先经过 JSBridge 取数据,Key 支持多个值一块儿传。

若是数据在 JSBridge 请求以前返回,把数据写入本地对应 Key 的 Value,JSBridge 读取后返回数据。

如何没有返回,读取本地 Key 为空,直接返回空。

接口返回的数据 App 端不用作任何判断和处理。若是没有取到页面在次经过 JS 发起请求,以后的超时从新请求等策略保持一致。

图片骨架屏

Hybrid 界面体验问题有一个很大的问题就是页面白屏 image

转转经过图片骨架屏来解决页面白屏的问题,在 WebView 初始化时,客户端把一张设计师出的的图片,覆盖在 WebView 上面。当页面 WebView 加载完成,或者前端通知客户端加载完成。客户端经过渐隐动画来隐藏图片,达到完美的过渡效果。

读取配置文件

客户端在启动时读取图片骨架屏的配置文件:

// 传入设备分辨率ratioWidth:400, ratioHeight:500
{
  "code":0, // code 是0 表明请求成功 -1 表明图片骨架屏功能关闭
  "data":{
    "m.zhuanzhuan.com/enjoy-given/eg/index.html": {
      "rege": '#/content/index'
      "routes":{
        "#/content/index": {
          "downloadUrl": 'https://m.zhuanzhuan.com/pic.png?400*500'
          "imgName": 'pic.png',
          "id": '10001',
        }
      }
    },
  "msg":""
  }
}
复制代码

客户端在 App 启动的时候,读取接口。

若是是首次使用,遍历配置文件,创建路径,下载图片。

若是是二次使用,则对比两次的配置文件,若是图片名称不一样,删除 id 文件夹下面的图片,下载新的,若是相等,保持不变。

客户端在内存中创建文件

zzSkeleton/10001/pic.png
复制代码

解析 url 并展现

当客户端打开 webview 时,读取 url

例如:https://m.zhuanzhuan.com/enjoy-given/eg/index.html?__isonshowpro=1&metric=rhtjEzu4TBGu6npSjDyXcQ282171v&fromGiftPage=cateList&fromCateId=0#/content/index?ada=asdad

取出m.zhuanzhuan.com/enjoy-given/eg/index.html(location.host + location.pathname 在接口返回的配置文件中取出

{
  "rege": '#/content/index'
  "routes":{
    "#/content/index": {
      "downloadUrl": 'https://m.zhuanzhuan.com/pic.png?400*500'
      "imgName": 'pic.png',
      "id": '10001',
    }
  }
}
复制代码

根据 rege 解析出#/content/index路由,而后根据idimgName来从本地取出文件

而后根据 zzSkeleton + 10001 + pic.png 读取本地的图片,并展现在 WebView 上

如何解决图片拉伸的问题

后台配置图片的时候,能够根据不一样的分辨率来配置不一样的图片,相似 App 配置启动图的逻辑。在客户端请求配置文件时,会传入设备分辨率,接口会根据分辨率返回最接近的图片。

App 统一跳转平台

对于 App 中的每个页面、或者是每个行为,咱们都能使用一个 URI 来定义,这个 URI 在转转内部叫作统跳。

转转的统跳所有由统跳平台来管理,经过统跳平台能够更好的测试、分享和管理这些统跳地址。

image

系统比较简单,经过动态表单来添加参数。一键生成二维码来快速的测试。

image image

Hybrid 总结与展望

目前转转 Hybrid 总体的技术方案,包括离线包、图片骨架屏等,已经无痛的接入业务中使用,很好的解决了页面加载性能问题,白屏问题,界面展现的局限性、操做的局限性,没法使用系统功能等问题。

固然技术在不断的发展。转转 Hybrid 建设在追求技术进步的道路上。还有不少的地方须要提高。

  • 提供完整的 SPA 体验 目前页面跳转基本是从新打开一个 WebView,这样就损失了一些项目的性能。咱们计划经过前端接管原生的路由的方式,让端内作到真正的 SPA 体验。

  • 接口请求和埋点经过客户端发送 转转如今的请求和埋点都是经过前端本身发送的。可是存在埋点丢失等问题。咱们计划打通客户端和前端的请求发送。同时前端也能够利用客户端的安全鉴权等能力。

福利部分

预告下,接下来咱们会陆续发布转转在微前端、Umi、组件库等基础架构和中台技术相关的实践与思考,欢迎你们关注,指望与你们多多交流。文章在 “大转转FE” 公众号也会发送,而且公众号有抽奖活动,本文奖品是转转记念T恤一件,欢迎你们关注 ✿✿ヽ(°▽°)ノ✿