写点什么

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

  • 2023-04-16
    上海
  • 本文字数:5238 字

    阅读完需:约 17 分钟

跨平台应用开发进阶(四十六)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


 ./nginx -s reload
复制代码


#开启gzipgzip  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也会自动处理。

四、拓展阅读

发布于: 2023-04-16阅读数: 24
用户头像

No Silver Bullet 2021-07-09 加入

岂曰无衣 与子同袍

评论

发布
暂无评论
跨平台应用开发进阶(四十六)webview方式嵌套H5应用加载慢解决方案_webview_No Silver Bullet_InfoQ写作社区