Android骨架屏详解

Android骨架屏详解#

前言#

本文是AndroidUI加载优化系列的文章

作为客户端开发者,一定对形如菊花的loading进度条组件非常熟悉。简单来说,菊花图及菊花图衍生出来的加载动画是作为提升UI加载性能的解决方案之一,深受开发者和设计师喜爱。


图1-菊花图

本文要介绍的骨架屏则被当今主流大厂前端视为菊花图升级版方案,用骨架屏代替菊花图的实践,愈发在Web和App开发中得到运用。

本文主要基于UI加载优化的角度回答以下问题

  • 骨架屏是什么
  • 骨架屏能解决什么问题
  • 骨架屏实现原理
  • 骨架屏的技术选型
  • 骨架屏有哪些最佳实践

骨架屏介绍#

提起骨架屏,必须要提到骨架屏(Skeleton Screen)一词的发明者——Luke Wroblewski ,Google产品总监,早在2013年他首次提出了骨架屏的概念,并将这一概念运用到他的产品Polar App中,


图2-骨架屏发明者
Luke Wroblewski认为骨架屏应该有以下几个元素:
  • 页面的空白版本,包含文本或元素的基本轮廓
  • 空白版本用于传递正在渐进加载的信息

典型的骨架屏布局参考

Facebook#


图3-Facebook骨架屏首页实践

饿了么#


图4-饿了么骨架屏首页实践

京东Plus#


图5-京东plus骨架屏

马蜂窝#


图6-马蜂窝骨架屏实践
## 其他

图7-其他采用骨架屏大厂

骨架屏场景#

为什么需要骨架屏?

  • 用户所需

    在最开始关于 MIT 2014 年的研究[1]中已有提到,用户大概会在 200ms 内获取到界面的具体关注点,在数据获取或页面加载完成之前,给用户首先展现骨架屏,骨架屏的样式、布局和真实数据渲染的页面保持一致,这样用户在骨架屏中获取到关注点,并能够预知页面什么地方将要展示文字什么地方展示图片,这样也就能够将关注焦点移到感兴趣的位置。当真实数据获取后,用真实数据渲染的页面替换骨架屏,如果整个过程在 1s 以内,用户几乎感知不到数据的加载过程和最终渲染的页面替换骨架屏,而在用户的感知上,出现骨架屏那一刻数据已经获取到了,而后只是数据渐进式的渲染出来。这样用户感知页面加载更快了。

  • 客户端框架的性能约束

    前端: ReactVueAngular 已经占据了主导地位,市面上大多数前端应用也都是基于这三个框架或库完成,这三个框架有一个共同的特点,都是 JS 驱动,在 JS 代码解析完成之前,页面不会展示任何内容,也就是所谓的白屏。用户是极其不喜欢看到白屏的,什么都没有展示,用户很有可能怀疑网络或者应用出了什么问题。 拿 Vue 来说,在应用启动时,Vue 会对组件中的 data 和 computed 中状态值通过 Object.defineProperty 方法转化成 set、get 访问属性,以便对数据变化进行监听。而这一过程都是在启动应用时完成的,这也势必导致页面启动阶段比非 JS 驱动(比如 jQuery 应用)的页面要慢一些。

    移动端:Android和IOS仍然占据统治地位,Android通常由xml->View对象->SurfaceFinger的形式将布局显示到屏幕上,IOS也类似有一个创建对象->设置数据->显示到屏幕的过程。这些过程都是在页面启动的过程完成的。如何通过降低页面启动时间,留存用户迫在眉睫。

骨架屏通用方案#

名称 优点 缺点
UI 骨架屏图 粗暴实现,实现速度块 需要UI设计师介入
不支持自动生成
需求变更维护成本大
手写骨架屏 为目标页定制骨架屏 定制实现工作量较大
不支持自动生成
需求变更维护成本大
自动生成静态骨架屏 自动为每个页面生成,自动插入到目标页面 脚本维护成本较大
生成的是静态页
自动生成动态骨架屏 自动为每个页面生成骨架屏代码,自动插入到目标页面 不支持开发环境
仅支持线上URL生成骨架屏
存在重定向的页面,自动生成的页面效果较差

骨架屏最佳实践#

饿了么前端最佳实践#

饿了么前端实现骨架屏经过了2个阶段[2]

实现方式 优点 缺点
手写实现,为每个页面复刻真实样式 简单 需求变更不仅需要修改业务代码,同时也需要修改骨架屏的样式和布局;
机械重复的工作量较大,手写实现维护成本较大
page-skeleton-webpack-plugin 自动生成,自动维护 饿了么自研,且需要腾出人力来维护插件

苹果公司最佳实践#

苹果公司也将骨架屏的概念写入到iOS Human Interface Guidelines 手册中,使用了新的launch images概念代替了骨架屏的说法

马蜂窝电商前端最佳实践#

基于对现有方案的借鉴,我们[3]想到了在配置文件中指定要生成骨架屏的页面 URL 和文件输出的目录,运行时读取配置文件中的配置项,通过 Puppeteer 打开指定的页面并注入 evalDom.js 的方法。因为此 JS 是在 Puppeteer 里面执行的,所以可以获取到当前页面完整的 DOM 结构,这给我们留下了非常大的发挥空间。

最初我们是从获取到的 DOM 结构中的 body 标签出发,递归去处理页面上的所有节点,处理完成后用生成的 DIV 替换原有元素的位置。第一版方案中通过 getBoundingClientRect 和 getComputedStyle 的方法来获取元素所有计算属性和相对于视口的宽高和位置,然后结合元素本身的样式属性递归渲染,保留页面原始 DOM 嵌套层次。

但由于能够决定元素位置的属性实在太多,如 position,z-index、width、height、top、display、box-sizing、flex 等都需要考虑,导致无法聚焦对页面 DOM 结构处理的逻辑,而且这些属性在处理完成后还需要加到最终生成骨架屏节点的 style 上,这样骨架屏文件可能比原来完整的页面结构还大,这肯定不是我们希望的。

优化后的方案是用 getBoundingClientRect 和 getComputedStyle 获取元素相关属性,然后直接通过绝对定位的方式来生成最终的骨架屏节点。这样在页面上最终需要的属性主要是 position、z-index、top、left、width、height、background、border-radius。除了无法保证页面原始的 DOM 结构,其它需求基本都可以满足,也更加聚焦于节点的处理。

主要实现流程如下图:


图8-马蜂窝电商骨架屏

骨架屏开源库#

Android#

名称 用途
Skeleton img
spruce-android img
ShimmerRecyclerView img

Web#

名称 用途
dps(draw-page-structure)
page-skeleton-webpack-plugin 5aebdc81eee0f

参考#

点击查看
-------------------本文结束 感谢您的阅读-------------------