钉钉小程序快照技术初窥

钉钉小程序快照技术初窥_第1张图片

作者:孙然(煮虾)

对于小程序技术来说,容器加载和前端异步渲染的过程中固然不可避免的会有白屏或 loading 页的展示,短则一瞬间,长则需要数秒才能展示首屏。如果白屏时间长,将非常影响用户的体验。根据 Google 的统计,如果页面加载耗时超过了 3s,那么有 53% 的用户会选择直接退出该页面了。

为加速小程序首页的展示,支付宝和手淘运用了基于 HTML 的快照技术,主要思路都是缓存首页 HTML 供下次启动时与数据一起优先渲染以提前首屏展示的时间,适用于传统 WebView 渲染的小程序场景。这种基于 HTML 的快照技术能够极大缩短启动时的白屏时间,但首屏展示的速度还是不够快,期间用户仍然会有可见的白屏感受。并且快照展示的仍然是无法点击操作的页面,需要等待 JS 部分 ready 后才可点击交互。

为了追求极致的体验效果,我们提出了一种全新的小程序快照技术,目标是既做到彻底消除白屏现象,同时也要能够响应用户交互。

钉钉小程序快照技术初窥_第2张图片

核心思路

不同于现有的基于 HTML 的快照技术,我们提出了一种 native 的图像级别的快照技术,主要由以下三个步骤:

  • 步骤1:在小程序启动后合适的时机将小程序首页保存为图片,我们称之为快照
  • 步骤2:下次打开小程序时先展示上次保存的快照,再启动小程序
  • 步骤3:当小程序启动完后的合适时机,隐藏快照,展示出真实的小程序首页,并保存当前界面视图作为下次的快照(同步骤1)

钉钉小程序快照技术初窥_第3张图片

效果

现在钉钉中的新建 DING 日程页面就运用了快照技术,前后效果对比如下:

before after

可以看出,通过快照技术,该页面实现了首屏秒开的效果,启动白屏的现象彻底消失,页面的首屏渲染耗时从 1700ms 左右降低到了 300ms 以下。

下面我会对快照技术的几个关键考虑点进行详细介绍。

场景和时机

理想中的快照,应当是能够和首屏页面完全重合,并且在快照隐藏时不会产生任何视觉变化的。那么生成快照的时机和运用快照的场景就直接决定了快照技术能够达到的优化效果。

什么页面适合用快照?

并不是所有的小程序都适合使用快照技术来提升首屏体验。如果使用不当,快照可能还会成为体验的减分项。为达到最佳效果,一般首屏页面满足下面几个条件是比较适合使用快照的:

  • 首屏页面较为固定。如果首屏不固定,很难找到合适的快照时机来保证快照与下次的首屏重合
  • 首屏页面不含用户隐私数据。用户的隐私数据不应当被快照下来

什么时候生成快照?

如果快照的时机过早,快照可能展示的也是白屏或者未渲染完整的首页框架。

如果快照的时机过晚,可能用户已经对首屏有了交互(滚动、点击等),容易生成无法和首屏重合的快照。

所以需要根据不同的首屏场景来确定最佳的快照时机。一般我们考虑快照的时机有:

  • 小程序首屏 Page 的 onReady 生命周期回调里。但此时页面很可能仍然没有渲染完成,可以考虑适当延时后进行快照
  • 小程序首屏的数据如果需要远程拉取,可以在远程获取到首屏数据后进行
  • 用户发生滚动时、点击等交互后不再进行快照

什么时候隐藏快照?

我们一般考虑在生成快照时去隐藏当前已经展示的快照。二者的顺序一般是在隐藏快照后立即生成快照,才能实现快照和真实页面的无缝衔接。

当然,也需要考虑小程序启动可能失败的场景。这里需要对展示快照的时间设置一个展示的上限,如果展示时间达到上限时小程序首屏仍然没有启动成功,那么快照将直接隐藏,以防造成首屏对用户可见但一直不响应的尴尬局面。

这里我们在还隐藏快照时做了一个小小的视觉优化。考虑到在隐藏快照时,如果直接隐藏快照,一旦快照和真实页面稍有差别,在视觉上可能就会有闪烁的体感发生。

所以在隐藏快照时,我们会做一个 200ms 的淡出动画,来缓解这种快照和真实页面差异导致的闪烁感。因为有时快照的时机可能会稍提前于首页网络数据加载成功、图片加载成功等这些异步事件成功的时机,导致快照比真实页面元素缺少或者数据不准确,而淡出动画能够有效淡化这些差错造成的视觉异常感。下面的 demo 对比了这两种情况:

直出 淡出

可交互

由于快照和真实的首屏页面基本是相同的,从用户体感上用户会以为首屏已经成功展现,也应该是个可交互的页面。所以单纯展示死的快照页面是远远不够的,做到可交互是我们快照的重要能力

我们的快照支持响应用户的点击行为,具体方法就是在用户点击快照时先暂存用户的点击事件,待快照隐藏时将此次事件分发到真实页面上去。

钉钉小程序快照技术初窥_第4张图片

此过程中如果用户有多次点击事件,我们只会响应最后一次点击事件。

从用户体感上来说,可能用户会感觉到此次点击的响应会比较慢,但不会让用户感知到它点击的是快照还是真实首页。

如果是小程序启动较慢的场景,还可以考虑在用户点击后展示 loading:

为进一步提高快照层的可交互性,我们甚至还可以允许开发者设定一些快照层的点击区域和简单操作,使用户在点击快照层的时候快速响应点击事件。例如钉钉的工作台就非常适合这种场景:工作台中的各个应用一般不会频繁变化,并且有很明确的分块区域:

钉钉小程序快照技术初窥_第5张图片

可以配置好不同的点击区域以及对应的 action (例如:跳转到其它页面/应用),形如:

[{
  area: {
    left: 100, 
    top: 100, 
    width: 100, 
    height: 100
  },
  action: {
    type: 'openLink', 
    params: { url: 'http://xxx' }
  }
}, ...]

这样用户在点击快照指定区域时就能直接实现跳转,而无需等待到小程序启动完成。

存储与安全

快照属于敏感数据,并且只保存在客户端本地不能进行上传,管理其存储必须格外小心,否则很容易酿成一起公关事件。

对于快照的存储,我们考虑了以下几点:

加密存储

快照数据必须进行加密存储,这里加密方式用的是集团无线保镖里的加密方法。

隐私保护

快照里不能含有用户的隐私数据。也就是说,快照应当只含有一些 UI 元素或者无意义的默认数据,而不应该含有用户隐私数据。

不含用户隐私数据 含用户隐私数据
钉钉小程序快照技术初窥_第6张图片 钉钉小程序快照技术初窥_第7张图片

那么如何做到获取到不含用户隐私数据的首页快照呢?可以考虑在前端从网络、缓存中获取数据之前进行快照。但这样的快照必定是不完整的,会损失一定的体验,这也是我们不推荐在有用户数据的首屏场景使用快照的原因。

快照清理

快照被存储在客户端中,需要有存储上限。当快照数据达到一定量后,需要淘汰一些老的快照数据。\
其次,在小程序版本更新、用户登出切换用户时都应该考虑将现存的对应快照数据清理掉。

准确性

当快照上线后,我们需要对快照的用户体验进行感知。最佳的体验是用户根本就没有感知到快照的存在,也就是快照和真实页面完全重合;而如果快照和真实页面相差较大,则会让用户体验大大下降,这是我们需要感知到的。

这里我们主要关注快照的准确性指标,也就是快照和真实页面的相似(重合)程度。准确性越高,则说明快照与真实页面的过渡约自然,体验越好;反之不但不会提升体验,可能还会对用户带来困惑。

如何判断快照准确性

在每次生成快照时,我们会将本次快照与上一次快照进行对比,得出量化的指标进而反映快照的准确性。那么下一步的问题就变成了如何判断两张图片的相似度了。

这里可能首先会想到直接使用像素逐个对比的方式来计算出两张快照中不同像素点的比例,比例越高则快照越准确,但实际上这种方法无法体现出真正的相似度和用户的体感。例如,当两张快照位置只要稍有偏移,得出来的相似度值可能很低;或者是两张色差很小的快照,也可能得到很差的结果。并且,快照的像素数可能达到上百万量级,测试发现逐个的像素对比工作一次可能就会耗时数秒钟。

我们现在使用了 Google 以图搜图中用到的“感知哈希算法”来量化快照的准确性。算法本身流程大致是将图片压缩后得出一些“指纹”信息,然后通过对比不同图片的指纹信息计算出“差异指数”。差异指数越高,则说明二者相似度越低。此算法能够体现出两次快照的相似程度,并且其效率比像素逐个比对的方法有了极大提升,线上数据统计到整个算法的耗时不超过 3ms。

钉钉小程序快照技术初窥_第8张图片

我们对一些场景进行了实验并得出差异指数。可以看出,对于微小字符改变的场景,差异指数非常低;而有明显视觉差距的场景中,差异指数会变高。这样得出的量化值能够体现快照对用户带来的真实体感的影响。

场景差异指数视觉效果
少量字符变化 1
整体偏移 6

如何复原错误快照场景

能够感知快照的准确性后,对于准确性较差的快照,我们还需要知道快照和真实页面相差在什么地方,进而改进快照的时机。

这里,我们是通过获取快照时前端页面 DOM 树的方式来追查当前的真实页面情况。具体操作是在生成快照时获取当前小程序 HTML 页面脱敏后的 DOM 树信息,然后再依赖小程序框架的 CSS 文件,最后直接用浏览器就可以恢复出快照时的界面了

钉钉小程序快照技术初窥_第9张图片

其它能力

局部快照

快照的一个比较大的局限性就是无法适应多变的首屏场景,这种场景使用快照很容易导致每次快照都无法跟真实首页重合,反而降低了用户体验。所以我们考虑提供一种能力,只对首页中每次都基本不变的部分进行快照,而其它多变的部分不进行快照,这样也能够每次使首屏部分内容实现秒出。

例如钉钉里的人脉首页,上半部分是相对固定的展示,而下半部分 feed 流可能每次打开都会展示不同的信息。那么在这种场景下我们就没必要每次都对整个首页第一屏进行快照,可以指定一定高度的部分进行快照,让首页的一部分实现秒出。

钉钉小程序快照技术初窥_第10张图片

超一屏快照

当首页可滚动时,我们甚至可以考虑超过一屏长度的快照,并且下次小程序启动时展示快照时让快照可滚动。此方案需要注意两个问题:

  1. 快照大小\
    线上统计显示,一屏的快照文件平均大小在 100K 左右。如果是超一屏的快照,大小可能会达到几百 K。需要在生成快照时预估一个长度上限或快照大小上限,以防快照使用时在低端机中出现 OOM 等异常情况。
  2. 快照滚动\
    如果快照在展示时用户进行了滚动操作,那么在隐藏快照时需要记录当前滚动的偏移量,以便将真实首页也滚动到指定位置,才能让快照和真实页面重合。

性能

对于快照的性能表现,我们进行了实验室测试和线上的数据统计。

实验室测试中我们构造了一个超大的快照(5.2M)的极限场景,并在低端机上与正常快照进行了对比:

普通场景极端场景
快照大小 262K 5.2M
内存占用 1840K 3245K
加载视觉体验 直接出现钉钉小程序快照技术初窥_第11张图片 有极短暂延时钉钉小程序快照技术初窥_第12张图片

快照加载过程并没有影响正常的页面切换,只是在过大快照的加载可能有短暂的延时。

线上数据显示,带快照的页面加载耗时在 280ms 左右,快照的平均大小约 110K。

快照的生成和准确性检测等工作都是在异步线程中进行的,此时用户交互并未开始,并且在用户滚动、交互后不会进行快照,不会对性能造成太多影响。

这里还有个比较有趣的数据:用户对快照的平均点击次数是 0.6 次,首次点击时间约为 1500ms。也就是说,当快照展示 1.5s 后,有一半多的人会开始首次的交互。这也足矣说明让快照具备可交互能力的重要性。

展望

快照技术虽然缘起是为解决小程序启动性能问题,但实际运用场景可以扩展到更多地方。

理论上来说,任何形式的异步渲染场景,不论是现在 WebView 还是 weex 渲染的小程序,或者就是普通的 H5 网页,甚至是一些 native 的场景(需要 loading 的场景),只要是一块能够在客户端中展示的视图,都能够运用快照技术解决其过程中的白屏或 loading 问题,并且都能做到秒出、可交互。因为快照是一个纯 native 的技术,它的实现本来就不依赖于真实页面的渲染方式,它更需要关心的是更合适的快照时机和应用场景从而获取更佳的体验。

总结

我们提出了一种全新的小程序快照技术,实现了小程序首页的秒开和可交互。它能够彻底消除小程序打开过程中的 loading 或白屏现象,让小程序打开达到了 native 的体验,还可以响应用户点击交互。

它是一种纯 native 的技术,不依赖于小程序容器和前端的渲染,只要有视图就能快照,只要有快照数据就能立即展示,甚至可扩展运用于其它非小程序场景。

而其局限性主要是依赖首屏样式和快照时机选择,多变、含用户隐私数据的首屏不适合快照,而且优质快照的生成的时机要求比较苛刻。在快照准确性保障方面,快照的相似度对比方法上也仍然有很大的优化空间,这些都还需要在今后不断打磨。

你可能感兴趣的