前端性能优化方法与实战 - 前 58 集团技术副总监 - 拉勾教育
前几讲我们介绍了首屏时间、白屏时间、卡顿等的优化方案,它们一般适用于 App 端内和端外两种场景,App 端内就是 App 内的 H5 页面,端外是指 PC 站或者微信里面的 H5 等页面。接下来这一模块,我会介绍一下 Hybrid 下的专有优化方案。
为什么要介绍它呢?
因为 Hybrid 开发模式借助 WebView,把 Native 和 H5 的各自优势进行了结合。具体来说,它既具备 Native 体验好、操作硬件能力强、代码安全等优势,又具备了 H5 发版节奏快、Web 标准开发效率高等优势。
通过它,前端工程师几分钟内就能完成需求上线,不用等 App 一周的发版周期,不用等待审核。除此之外,使用 Hybrid 开发模式还可以将一个大的横向需求,切分到各个业务前端团队并行工作,大大提升了需求吞吐率和迭代速度。如今,许多 App,如美团、去哪儿、淘宝都在使用 Hybrid 开发模式。
不过它的缺点也明显,比如加载性能问题、白屏问题、界面展示和操作的局限性,无法使用系统功能等等。所以,这一讲,我将结合 H5 的加载流程,介绍 Hybrid App 下的性能优化整体分析,然后接下来几讲会详细介绍与之相关的具体优化,如离线包、图片骨架屏、服务端渲染、接口预加载、客户端代理数据接口等。
H5 是 Hybrid App 当中的一个核心,它可以通过 SDK 访问 App 底层系统,让前端页面获取调用传感器、存储、日历 / 联系人等原生能力。一般 H5 加载大致流程如下:
进入 App → 初始化 WebView → 客户端发起请求 → 下载 HTML 及 JS/CSS 资源 → 解析 JS 执行 → JS 请求数据→服务端处理并返回数据 → 客户端解析 DOM 并渲染 → 下载渲染图片 → 完成整体渲染。
从大的时间节点来看,初始化 WebView 之前都算是 App 启动阶段,从初始化 WebView 到客户端解析 Dom 并渲染,属于白屏时间,剩下的环节就是整体渲染后首屏结束,我们把这段称为页面首屏渲染阶段。
所以,接下来我就以这三个阶段——App 启动、页面白屏、页面首屏渲染来介绍下相关的优化方案。
App 启动阶段的优化方案
App 启动,尤其是冷启动(首次启动 App)时,并不会直接初始化 WebView,而是在创建 WebView 实例时,才会创建它的基础框架。系统打开 WebView 时,也不是直接建立连接发起请求,而是又一个启动浏览器内核的过程。
那么这中间大致需要多长时间呢?我们在一次二手业务列表页测试时发现,首次启动 WebView 平均需要 400ms 左右,二次启动平均有 220ms。按照页面秒开的目标,WebView 启动就占了 40% 的时间,所以我们要想办法来优化它。
对此,我建议使用 WebView 全局的优化方式,即在 App 启动时启动一个 WebView 后让其全局化。或者更彻底一点,把 Webview 的实例保存在一个公共池中,当用户访问这个 WebView 时,直接从公共池取来加载网页,而不是重新初始化一个新的 WebView。
通过这个办法,可以大大减少后续 WebView 在 App 中打开的时间。以我的一个优化实践经验(房产列表页)为例,可以减少 200ms 左右的启动时间。
页面白屏阶段的优化方案
在页面白屏阶段,也就是 H5 页面加载的下载 HTML 及 JS/CSS 资源环节当中,会有哪些情况影响性能,以及会用到哪些优化方案呢?
一般情况下,前端工程师将静态资源上线到 CDN 上后, WebView 会发起网络请求去获取。当用户在弱网或者网络比较差的环境下,页面白屏时间会特别长。此时,如果我们能将静态资源提前下载到本地,WebView 获取静态资源时就可以直接从本地获取,这样会大幅降低白屏时间,这就是离线包的作用。
所谓离线包,就是将包括 HTML、JavaScript、CSS 等页面内静态资源打包到一个压缩包内,App 预先内置该压缩包到本地,然后当用户在客户端打开 H5 页面时,直接从本地加载就行了。这样无须再向服务器端请求,最大程度地摆脱网络环境对 H5 页面的影响。
如果离线包是提前下载到本地,那么更新问题怎么解决呢?比如一个业务的静态资源更新了,我们该怎么保证用户请求到的离线包资源也是最新的呢?
我们可以在生成离线包的同时,生成一个配置文件,让 App 先根据这个静态文件判断是需要更新离线包,还是直接向业务服务器进行请求。
但如果静态资源本地版本实在太老,此时客户端将直接向服务器端请求资源(如离线包无法命中的情况),这该怎么办呢?它的优化方案就是骨架屏——我们可以给客户一个心理预期,在接口请求和渲染过程中,让他知道接下来要渐进式展示的内容和结构是什么。
骨架屏生成稍显复杂,它需要 UI 去做图,还需要客户端去展示和关闭,我在第 14 讲会详细介绍如何实现骨架屏,以及如何用它来优化白屏和页面加载。
虽然说骨架屏可以 “加快” 过程中的等待时间,但请注意,这个加快只是视觉和心理上的效果,实质上等待时间还是那么多。那有没有办法减少最后的渲染时间呢?我们可以使用 SSR 来优化。
SSR (Server Side Rendering,服务端渲染)是指客户端发起页面请求后,服务端直接将组件和页面内容渲染成 DOM 结构,返回给客户端。你可以通过 Chrome 的调试工具 DevTools 打开页面后,看到相关的源文件内容。
提到 SSR ,就不得不提下 CSR(Client Side Rendering)。目前许多人在移动端上还在使用 CSR,但其实在 CSR 方案下,它的页面资源请求和数据渲染流程相当烦琐冗长。
具体来说,在 CSR 方案中,HTML 文件仅作为入口,客户端在请求时,服务端不做任何处理,只返回一个 index.html 文件,然后客户端根据页面上的 JS 脚本去请求内容,生成 DOM 添加到 HTML 页面中,形成最终的内容。我以一个图为例,你可以看出同一个页面在不同技术方案下面白屏方面的差异。
从图中可以看到,在 SSR 下第三帧就可以看到内容,而在 CSR 下需要第六帧才能看到。
首屏渲染阶段的优化方案
处理完静态资源后,马上进入服务端处理和返回数据请求过程,这就是首屏渲染阶段。这阶段要解决的主要问题是怎样减少数据接口加载的时间。
有什么优化方案呢?
一方面,我们可以通过服务端优化来降低后端接口的响应时间,比如从 200ms 降低到 100ms。但优化到 100ms 之后,其优化空间就非常有限了。另一方面,可以从接口和页面并行加载的角度去做优化加载时间,也就是接口预加载。它有以下几种形式。
第一种,通过客户端代理数据接口请求,在客户端初始化 WebView 的同时,直接由 Native 发起网络请求,H5 页面初始化完成后(对于 CSR 页面,也就是 index.html 加载完成后)直接通过 SDK 向 Native 获取数据。
第二种,根据业务场景选择预加载。举个例子,我们在滚动下拉列表的页面,根据用户滚动条的位置,提前加载一页的展示数据,这样用户在滚动下拉时,不会有停滞的感觉,非常流畅。
第三种,在一些旅行类 App 上,用户在订酒店的时候,当他进行了目的地和日期选择操作后,将结果上传服务端后,服务端会根据用户的操作路径,判断打开搜索结果页的概率。如果概率超过某一个值,就会启动搜索结果页的数据获取,这样在进入搜索结果页后,已经有了接口数据,大大节省了时间。
小结
好了,我们以 App H5 的加载流程,介绍了 Hybrid 下的整体优化方案。除了以上所讲的,在实际过程中还有不少注意事项。
- 离线包命中率的统计,因为离线包即便不命中也不影响页面效果,所以出现问题很难发现,为此,在业务上线的日常运行中要对命中率进行统计。
- WebView 的优化,全局 WebView Pool 时一定要注意及时销毁,不然对 App 资源的占用会比较大。
- 很多公司在预加载数据的基础上发展出了预渲染,但在实施过程中我们发现,它对 App 内存占用过大。
最后给你留一个问题:
Hybrid 下的优化方案,有哪些是你们在用的呢?
欢迎大家在评论区和我沟通,接下来我们将进入具体优化方案——保证首次加载为秒开的离线包设计。