跨平台应用开发进阶(四十六)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也会自动处理。

四、拓展阅读

相关推荐
恋猫de小郭8 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅15 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606115 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了15 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅16 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅16 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅16 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment16 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅17 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊17 小时前
jwt介绍
前端