一、前言
uni-app
项目中通过webview
方式嵌套H5应用时,出现页面加载慢的用户体验问题。尤其当应用第一次加载H5应用时,页面白屏大致有3-4s!
经过分析发现,h5页面第一次加载时会下载页面静态资源(包括图片、字体库文件、css样式文件、js脚本等),后续加载时浏览器引擎在缓存机制的作用下会直接加载缓存信息,渲染较快。
- 页面初次渲染
- 页面二次渲染 其中,
finish
:页面最后一个请求截止的时间,如果页面加载完成后,触发了ajax请求,那么该时间会变更。DOMContentLoaded
:dom内容加载并解析完成的时间,即页面白屏时间。load
:页面所有的资源(图片、音频、视频等)加载完成的时间。
老生常谈,从输入URL到页面展示,发生了什么?
- 首先从本地查找域名,有的话直接用hosts文件里的ip地址,否则查询DNS,得到ip地址;
- 建立TCP连接------进行所谓的"三次握手";
- 客户端发送http请求;
- 服务端处理,并返回结果给客户端;
- 关闭TCP连接------需要"四次挥手";
- 浏览器收到结果,开始解析资源(
JS、CSS、HTML
),解析HTML
生成的dom
树,和同时解析css
生成的cssom
树结合生成渲染树;
我们可以在控制台输入window.performance.getEntriesByType('paint')
来获取 First Paint
(FP:文档中任意元素首次渲染时间)和 First Contentful Paint
(FCP:也就是我们常说的白屏时间 )
当然了,这两个值也不是固定的,比如在第一次打开页面和第二次打开页面时是不同的。
二、优化建议
-
使用自定义组件模式 使用自定义组件模式,在 manifest 中配置自定义组件模式(HBuilderX1.9起新建项目默认即为自定义组件模式)。
在复杂页面中,页面中嵌套大量组件,如果是非自定义组件模式,更新一个组件会导致整个页面数据更新。而自定义组件模式则可以单独更新一个组件的数据。
在App端,除了上述好处,自定义组件模式还新增了一个独立的js引擎,加快启动速度、减少js阻塞。
之前的非自定义组件模式已经不再推荐,如果你的应用还是非自定义组模式,请尽快升级。
-
避免使用大图 页面中若大量使用大图资源,会造成页面切换的卡顿,导致系统内存升高,甚至白屏崩溃。
尤其是不要把多张大图缩小后显示在一个屏幕内,比如上传图片前选了数张几M体积的照片,然后缩小在一个屏幕中展示多张几M的大图,非常容易白屏崩溃。
推荐通过阿里云oss,来压缩图片处理。
-
优化数据更新 在 uni-app 中,定义在 data 里面的数据每次变化时都会通知视图层重新渲染页面。 所以如果不是视图所需要的变量,可以不定义在 data 中,可在外部定义变量或直接挂载在vue实例上,以避免造成资源浪费。
-
长列表 长列表中如果每个item有一个点赞按钮,点击后点赞数字+1,此时点赞组件必须是一个单独引用的组件,才能做到差量数据更新。否则会造成整个列表数据重载。(要求自定义组件模式)
长列表中每个item并不一定需要做成组件,取决于你的业务中是否需要差量更新某一行item的数据,如没有此类需求则不应该引入大量组件。(点击item后背景变色,属于css调整,没有更新data数据和渲染,不涉及这个问题)
app端
nvue
的长列表应该使用list组件,有自动的渲染资源回收机制。vue页面使用页面滚动的性能,好于使用scroll-view
的区域滚动。如需要左右滑动的长列表,请参考"在HBuilderX新建uni-app项目" 的 新闻模板,那是一个标杆实现。自己用
swiper
和scroll-view
做很容易引发性能问题。 -
减少一次性渲染的节点数量 页面初始化时,逻辑层如果一次性向视图层传递很大的数据,使视图层一次性渲染大量节点,可能造成通讯变慢、页面切换卡顿,所以建议以局部更新页面的方式渲染页面。
如:服务端返回100条数据,可进行分批加载,一次加载50条,500ms 后进行下一次加载。
-
减少节点嵌套层级 深层嵌套的节点在页面初始化构建时往往需要更多的内存占用,并且在遍历节点时也会更慢些,所以建议减少深层的节点嵌套。
-
避免视图层和逻辑层频繁进行通讯 减少
scroll-view
组件的 scroll 事件监听,当监听scroll-view
的滚动事件时,视图层会频繁的向逻辑层发送数据;监听
scroll-view
组件的滚动事件时,不要实时的改变scroll-top/scroll-left
属性,因为监听滚动时,视图层向逻辑层通讯,改变scroll-top/scroll-left
时,逻辑层又向视图层通讯,这样就可能造成通讯卡顿。注意
onPageScroll
的使用,onPageScroll
进行监听时,视图层会频繁的向逻辑层发送数据; 多使用css动画,而不是通过js的定时器操作界面做动画。 -
优化页面切换动画 页面初始化时若存在大量图片或原生组件渲染和大量数据通讯,会发生新页面渲染和窗体进入动画抢资源,造成页面切换卡顿、掉帧。建议延时100ms~300ms渲染图片或复杂原生组件,分批进行数据通讯,以减少一次性渲染的节点数量。
App端动画效果可以自定义。
popin/popout
的双窗体联动挤压动画效果对资源的消耗更大,如果动画期间页面里在执行耗时的js,可能会造成动画掉帧。此时可以使用消耗资源更小的动画效果,比如slide-in-right/slide-out-right
。 -
优化样式渲染速度 如果页面背景是深色,在
vue
页面中可能会发生新窗体刚开始动画时是灰白色背景,动画结束时才变为深色背景,造成闪屏。这是因为webview
的背景生效太慢的问题。此时需将样式写在App.vue
里,可以加速页面样式渲染速度。App.vue
里面的样式是全局样式,每次新开页面会优先加载App.vue
里面的样式,然后加载普通vue
页面的样式。另外nvue
页面不存在此问题,也可以更改为nvue
页面。 -
使用 nvue 代替 vue 在 App 端 uni-app 的 nvue 页面是基于 weex 定制的原生渲染引擎,实现了页面原生渲染能力、提高了页面流畅性。若对页面性能要求较高可以使用此方式开发。
优化App启动速度注意事项
-
工程代码越多,包括背景图和本地字体文件越大,对App的启动速度有影响,应注意控制体积。组件引用的前景图不影响性能。
-
App端的 splash 关闭有白屏检测机制,如果首页一直白屏或首页本身就是一个空的中转页面,可能会造成 splash 10秒才关闭。
-
App端使用自定义组件模式时启动速度更快,首页为nvue页面时启动速度更快。
-
App设置为纯nvue项目(manifest里设置app-plus下的renderer:"native"),这种项目的启动速度更快,2秒即可完成启动。因为它整个应用都使用原生渲染,不加载基于webview的那套框架。
优化包体积
-
uni-app发行到小程序时,自带引擎只有几十K,主要是一个定制过的vue.js核心库。如果使用了es6转es5、css对齐的功能,可能会增大代码体积,可以配置这些编译功能是否开启。
-
uni-app的H5端,自带了
vue.js
、vue-rooter
及部分es6 polyfill
库,这部分的体积gzip后只有92k,和web开发使用vue基本一致。而内置组件ui库(如picker
、switch
等)、小程序的对齐js api等,相当于一个完善的大型ui库。但大多数应用不会用到所有内置组件和API。由此uni-app提供了摇树优化机制 ,未摇树优化前的uni-app整体包体积约500k,服务器部署gzip后162k。开启摇树优化需在manifest.json
配置。 -
uni-app的App端,因为自带了一个独立v8引擎和小程序框架,所以比HTML5Plus或mui等普通hybrid的App引擎体积要大。Android基础引擎约15M。App还提供了扩展模块,比如地图、蓝牙等,打包时如不需要这些模块,可以裁剪掉,以缩小发行包体积。在
manifest.json-App模块权限
里可以选择。 -
App端支持如果选择纯nvue项目(manifest里设置
app-plus
下的renderer:"native"
),包体积可以进一步减少2M左右。 -
uni-app的App端默认包含arm32和x86两个cpu的支持so库。这会增大包体积。如果你在意体积控制,可以在manifest里去掉x86 cpu的支持(manifest可视化界面-App其他设置里选择cpu),这可以减少包体积到9M。但代价是不支持intel的cpu了。一般手机都是arm的,仅个别少见的Android pad使用x86 cpu。另外as的模拟器里如果选择x86时也无法运行这种apk。
三、实施方案
从以下优化点进行页面优化:
- 优化项目结构,减小项目组包体积;
- 去除项目冗余字体文件;
- 启动应用服务器端gzip压缩;
3.1 优化项目结构,减小项目组包体积
3.2 去除项目冗余字体文件
由上图可知,项目中字体库文件大小为6.8M,那么该文件中具体包含哪些字体呢?是不是全部为项目中需要的字体文件呢?是否存在冗余字体文件?
3.2.1 字体筛选
解决方案 :通过识别常用5000汉字,将原有字体文件通过取子集、不压缩的方式获得小体积字体文件。压缩后的字体文件体量为原有字体库文件的 7% 左右!信息如下:
将大体量字体库文件经过常用5000汉字筛选后生成的字体库文件后,页面加载效果如下:
实验结果 :通过对比前后页面渲染效果,可知渲染时长缩短至原有的 32% 左右,页面白屏时间缩短至原有项目的 25% 左右,渲染性能大幅提升!以上字体文件仅是通过常用汉字取子集的方式生成的,还未利用压缩技术,再经过压缩后,相信字体库文件体量会大幅减小,页面渲染性能会再上一个新台阶!
3.2.2 字体压缩
通过3.2.1小节给出的常用汉字筛选,实践发现常用5000字居然不包括平时常用的汉字,例如:综!导致页面字体渲染结果不一致!
为了解决该问题,要么扩大汉字筛选范围(无统一标准,仍存在遗漏风险);要么不适用字体筛选策略!
通过不识别常用5000汉字,将原有字体文件只通过压缩的方式获得小体积字体文件。压缩后的字体文件体量为原有字体库文件的 67.6% 左右!信息如下:
将大体量字体库文件经过压缩生成的字体库文件后,页面加载效果如下:
实验结果 :通过对比前后页面渲染效果,可知渲染时长缩短至原有的 73% 左右,页面白屏时间缩短至原有项目的 48.5% 左右,渲染性能仅小幅提升!
此外,文件压缩方式也可以采用字蛛实现。
有关字体库文件压缩,详参博文《跨平台应用开发进阶(四十七)大体量字体库文件处理方案》。
3.3 gzip压缩
启用Gzip
压缩功能, 可以使网站的css、js 、xml、html
等静态资源在传输时进行压缩,经过Gzip
压缩后资源可以变为原来的30%甚至更小,尽管这样会消耗一定的cpu资源,但是会节约大量的出口带宽来提高访问速度。
Gzip
的压缩页面需要浏览器和服务器双方都支持,实际上就是服务器端压缩,传到浏览器后解压并解析。浏览器那里不需要我们担心,因为目前的大多数浏览器都支持解析Gzip
。
注意⚠️:不建议压缩图片和大文件:图片如jpg、png文件本身就会有压缩,所以就算开启gzip
后,压缩前和压缩后大小没有多大区别,所以开启了反而会白白的浪费CPU资源。(如果优化可以可以图片的生命周期设置长一点,让客户端来缓存)
而大文件资源会消耗大量的cpu资源,且不一定有明显的效果。
nginx如何配置gzip呢?
在 nginx.conf
(Nginx
配置文件)中,在http
块内或者在单个server
块里添加后重启nginx
。
shell
./nginx -s reload
shell
#开启gzip
gzip on;
# 检查是否存在请求静态文件的gz结尾的文件,如果有则直接返回该gz文件内容,不存在则先压缩再返回
gzip_static off;
#低于1kb的资源不压缩
gzip_min_length 1k;
#压缩级别1-9,越大压缩率越高,同时消耗cpu资源也越多,建议设置在5左右。
gzip_comp_level 5;
#需要压缩哪些响应类型的资源,多个空格隔开。不建议压缩图片.
gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css application/octet-stream;
#配置禁用gzip条件,支持正则。此处表示ie6及以下不启用gzip(因为ie低版本不支持)
gzip_disable "MSIE [1-6]\.";
#是否添加"Vary: Accept-Encoding"响应头
gzip_vary on;
其中,待压缩的资源类型可以从控制台Content-Type
看到:
服务端gzip
开启后,可在控制台看到如下信息:
页面渲染效果如下:
由开启gzip
之后的页面渲染时间,可知页面渲染完成时间提升了80%,白屏时间缩短至1s内!
3.4 ETag
gzip
属于HTTP
协议的内容。其缺点就是重新渲染的问题!
ETag
全称EntityTags
,HTTP
协议规格说明中定义"ETag
"为"被请求变量的实体值"。
也可以把ETag
理解为是一个客户端与服务器间的关联标记。这个记号告诉客户端,当前网页在上次请求之后是否有发生变化,当发生变化时,ETag
的值重新计算,并返回200
状态码。如果没有变化,返回304
状态码。从而不会重新加载整个页面信息。
这样不如使用 etag
,Nginx
开启etag
只需要在server
配置( nginx.conf
在http
块内)里加上一行:etag on;
即可。
注意⚠️:
Nginx
版本1.3.3
以下,不支持ETag
!- 开启
gzip
时,可能与etag
出现冲突,用浏览器多次请求此网站的静态元素,如果只返回200,不返回304,证明存在冲突,需要关闭gzip
解决冲突,即将上一步中的gzip on;
改为gzip off;
3.5 http/2
现在http2
已经席卷而来,而且其有一个强大的优势,在于对于一个域只进行一次tcp
连接,使用多路复用,传输多个资源(同时加载),这样就不必使用诸如雪碧图、合并css/js
文件等技术减少请求数了(使用雪碧图只有一个优点:减少请求次数,这和它不可避免的缺点(高清屏会失真、图片变化极不方便)相比,简直不足为道)。
这个技术的使用也很简单,只需要使用nginx 1.10.0和openssl 1.0.2以上版本,安装好后再配置文件中( ngnix.conf
,写在http
中的server
块中 )加上:listen 443 ssl http2;
即可。
当然,对于不兼容HTTP2
的浏览器,nginx
也会自动处理。