Android骨架屏详解#
前言#
本文是AndroidUI加载优化系列的文章
作为客户端开发者,一定对形如菊花的loading进度条组件非常熟悉。简单来说,菊花图及菊花图衍生出来的加载动画是作为提升UI加载性能的解决方案之一,深受开发者和设计师喜爱。
本文要介绍的骨架屏则被当今主流大厂前端视为菊花图升级版方案,用骨架屏代替菊花图的实践,愈发在Web和App开发中得到运用。
本文主要基于UI加载优化的角度回答以下问题
- 骨架屏是什么
- 骨架屏能解决什么问题
- 骨架屏实现原理
- 骨架屏的技术选型
- 骨架屏有哪些最佳实践
骨架屏介绍#
提起骨架屏,必须要提到骨架屏(Skeleton Screen)一词的发明者——Luke Wroblewski ,Google产品总监,早在2013年他首次提出了骨架屏的概念,并将这一概念运用到他的产品Polar App中,
- 页面的空白版本,包含文本或元素的基本轮廓
- 空白版本用于传递正在渐进加载的信息
典型的骨架屏布局参考
Facebook#
饿了么#
京东Plus#
马蜂窝#
骨架屏场景#
为什么需要骨架屏?
用户所需
在最开始关于 MIT 2014 年的研究[1]中已有提到,用户大概会在 200ms 内获取到界面的具体关注点,在数据获取或页面加载完成之前,给用户首先展现骨架屏,骨架屏的样式、布局和真实数据渲染的页面保持一致,这样用户在骨架屏中获取到关注点,并能够预知页面什么地方将要展示文字什么地方展示图片,这样也就能够将关注焦点移到感兴趣的位置。当真实数据获取后,用真实数据渲染的页面替换骨架屏,如果整个过程在 1s 以内,用户几乎感知不到数据的加载过程和最终渲染的页面替换骨架屏,而在用户的感知上,出现骨架屏那一刻数据已经获取到了,而后只是数据渐进式的渲染出来。这样用户感知页面加载更快了。
客户端框架的性能约束
前端: React、Vue、Angular 已经占据了主导地位,市面上大多数前端应用也都是基于这三个框架或库完成,这三个框架有一个共同的特点,都是 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 结构,其它需求基本都可以满足,也更加聚焦于节点的处理。
主要实现流程如下图:
骨架屏开源库#
Android#
名称 | 用途 |
---|---|
Skeleton | |
spruce-android | |
ShimmerRecyclerView |
Web#
名称 | 用途 |
---|---|
dps(draw-page-structure) | |
page-skeleton-webpack-plugin |