跨平台应用开发进阶(四十六)webview方式嵌套H5应用加载慢解决方案

一、前言

uni-app项目中通过webview方式嵌套H5应用时,出现页面加载慢的用户体验问题。尤其当应用第一次加载H5应用时,页面白屏大致有3-4s!

经过分析发现,h5页面第一次加载时会下载页面静态资源(包括图片、字体库文件、css样式文件、js脚本等),后续加载时浏览器引擎在缓存机制的作用下会直接加载缓存信息,渲染较快。

  • 页面初次渲染
  • 页面二次渲染 其中,
  • finish:页面最后一个请求截止的时间,如果页面加载完成后,触发了ajax请求,那么该时间会变更。
  • DOMContentLoaded:dom内容加载并解析完成的时间,即页面白屏时间。
  • load:页面所有的资源(图片、音频、视频等)加载完成的时间。

老生常谈,从输入URL到页面展示,发生了什么?

  1. 首先从本地查找域名,有的话直接用hosts文件里的ip地址,否则查询DNS,得到ip地址;
  2. 建立TCP连接------进行所谓的"三次握手";
  3. 客户端发送http请求;
  4. 服务端处理,并返回结果给客户端;
  5. 关闭TCP连接------需要"四次挥手";
  6. 浏览器收到结果,开始解析资源(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项目" 的 新闻模板,那是一个标杆实现。自己用swiperscroll-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的那套框架。

优化包体积

  1. uni-app发行到小程序时,自带引擎只有几十K,主要是一个定制过的vue.js核心库。如果使用了es6转es5、css对齐的功能,可能会增大代码体积,可以配置这些编译功能是否开启。

  2. uni-app的H5端,自带了vue.jsvue-rooter及部分es6 polyfill库,这部分的体积gzip后只有92k,和web开发使用vue基本一致。而内置组件ui库(如pickerswitch等)、小程序的对齐js api等,相当于一个完善的大型ui库。但大多数应用不会用到所有内置组件和API。由此uni-app提供了摇树优化机制 ,未摇树优化前的uni-app整体包体积约500k,服务器部署gzip后162k。开启摇树优化需在manifest.json配置。

  3. uni-app的App端,因为自带了一个独立v8引擎和小程序框架,所以比HTML5Plus或mui等普通hybrid的App引擎体积要大。Android基础引擎约15M。App还提供了扩展模块,比如地图、蓝牙等,打包时如不需要这些模块,可以裁剪掉,以缩小发行包体积。在 manifest.json-App模块权限里可以选择。

  4. App端支持如果选择纯nvue项目(manifest里设置app-plus下的renderer:"native"),包体积可以进一步减少2M左右。

  5. uni-app的App端默认包含arm32和x86两个cpu的支持so库。这会增大包体积。如果你在意体积控制,可以在manifest里去掉x86 cpu的支持(manifest可视化界面-App其他设置里选择cpu),这可以减少包体积到9M。但代价是不支持intel的cpu了。一般手机都是arm的,仅个别少见的Android pad使用x86 cpu。另外as的模拟器里如果选择x86时也无法运行这种apk。

三、实施方案

从以下优化点进行页面优化:

  1. 优化项目结构,减小项目组包体积;
  2. 去除项目冗余字体文件;
  3. 启动应用服务器端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.confNginx配置文件)中,在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全称EntityTagsHTTP协议规格说明中定义"ETag"为"被请求变量的实体值"。

也可以把ETag理解为是一个客户端与服务器间的关联标记。这个记号告诉客户端,当前网页在上次请求之后是否有发生变化,当发生变化时,ETag的值重新计算,并返回200状态码。如果没有变化,返回304状态码。从而不会重新加载整个页面信息。

这样不如使用 etagNginx开启etag只需要在server配置( nginx.confhttp块内)里加上一行:​​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也会自动处理。

四、拓展阅读

相关推荐
也无晴也无风雨42 分钟前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
Martin -Tang1 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
阮少年、4 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
郝晨妤6 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
AvatarGiser6 小时前
《ElementPlus 与 ElementUI 差异集合》Icon 图标 More 差异说明
前端·vue.js·elementui
喝旺仔la6 小时前
vue的样式知识点
前端·javascript·vue.js
别忘了微笑_cuicui6 小时前
elementUI中2个日期组件实现开始时间、结束时间(禁用日期面板、控制开始时间不能超过结束时间的时分秒)实现方案
前端·javascript·elementui