0


[HarmonyOS Next 性能]鸿蒙性能优化 - 长列表加载性能优化

长列表加载性能优化

长列表优化概述

在列表开发场景中,一些新闻应用、购物应用、聊天应用,列表数据往往会达到上万条,针对这类大量数据加载的长列表应用,如何对长列表的性能进行优化是非常重要的。一个正确、高性能的长列表应用能明显降低列表渲染时间、提升页面的滑动帧率、降低应用内存占用,大幅提升用户体验。本文将介绍如下5种优化手段,通过这些优化手段的单个使用或组合使用,可以对列表渲染时间、页面滑动帧率、应用内存占用等方面带来优化,提升性能和用户体验:

  • 懒加载
  • 缓存列表项
  • 动态预加载
  • 组件复用
  • 布局优化

下文将以 “HMOS世界”中首屏的长列表加载为例,通过5个测试来验证列表优化前后性能的收益,以证明这些优化手段的可行性。综合考虑业界共识指标和实际用户使用体验,测试将对比分析如下几个关键指标:

  • 完全显示所用时间
  • 丢帧率
  • 独占内存

其对比效果如下所示:
图1 10000条数据量下ForEach和LazyForEach最佳实践启动对比
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
图2 10000条数据量下ForEach和LazyForEach最佳实践滑动对比
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

懒加载

原理介绍

HarmonyOS应用框架为容器类组件的数据加载和渲染提供了2种方式:

  • 方式一,循环渲染:通过循环渲染(ForEach)从数组中获取数据,并为每个数据项创建相应的组件,可减少代码复杂度。
  • 方式二,数据懒加载:通过数据懒加载(LazyForEach)从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。

ForEach

ForEach循环渲染的过程如下:

  1. 从列表数据源一次性加载全量数据。
  2. 为列表数据的每一个元素都创建对应的组件,并全部挂载在组件树上。即,ForEach遍历多少个列表元素,就创建多少个ListItem组件节点并依次挂载在List组件树根节点上。
  3. 列表内容显示时,只渲染屏幕可视区内的ListItem组件,可视区外的ListItem组件滑动进入屏幕内时,因为已经完成了数据加载和组件创建挂载,直接渲染即可。

其数据加载、组件树挂载、页面渲染的示意图如下所示:

LazyForEach

LazyForEach懒加载的原理和渲染过程如下:

  1. LazyForEach会根据屏幕可视区能够容纳显示的组件数量按需加载数据。
  2. 根据加载的数据量创建组件,挂载在组件树上,构建出一棵短小的组件树。即,屏幕可以展示多少列表项组件,就按需创建多少个ListItem组件节点挂载在List组件树根节点上。
  3. 屏幕可视区只展示部分组件。当可视区外的组件需要在屏幕内显示时,需要从头完成数据加载、组件创建、挂载组件树这一过程,直至渲染到屏幕上。

其数据加载、组件树挂载、页面渲染的示意图如下所示:

使用场景和规则

使用场景

文了解到ForEach是从列表数据源一次性加载全量数据,且一次性并全部挂载在组件树上;LazyForEach是按需加载部分数据,只构建出一棵短小的组件树。针对数据加载和组件树构建这两个显著差异,可以对ForEach/LazyForEach做出如下选型判断:
渲染接口选型原则ForEach列表数据较少,数据一次性全量加载不是性能瓶颈时。ForEach相对LazyForEach,代码简单很多。LazyForEach列表数据较长,一次性加载所有的列表数据创建、渲染页面产生性能瓶颈时。

使用规则

详细的使用规则可以参考ForEach的使用建议和LazyForEach的使用限制。

性能分析

针对长列表这一场景,在本地模拟了10、100、1000、10000条数据,分别使用ForEach、LazyForEach,来测试关闭和开启懒加载情况下的完全显示所用时间、列表挂载时间、独占内存,并分析了其滑动过程中的丢帧率。其中,列表挂载时间是指创建组件和组件挂载数据的总时长。最终,使用IDE的Profiler工具检测下述指标,得到的数据如下所示:
alt
从测试数据可以看出:

  1. 在100条数据范围内ForEach和LazyForEach差距不大,总体而言两者各项性能指标都在可接受范围内,而ForEach代码逻辑比LazyForEach简单,此场景下使用ForEach即可。
  2. 当数据大于1000条,特别是当数据达到10000条时,ForEach在列表渲染、应用内存占用、丢帧率等各个方面都会有“指数级别”的显著劣化,滑动会出现明显的卡顿,甚至会出现应用crash等现象。
  3. 使用LazyForEach除了最后内存稍微增大以外,其列表渲染时间、丢帧率都不会出现明显变化,具有较好的性能。

缓存列表项

原理介绍

LazyForEach懒加载可以通过设置cachedCount来指定缓存数量,在设置cachedCount后,除屏幕内显示的ListItem组件外,还会预先将屏幕可视区外指定数量的列表项数据缓存。这样当一个屏幕数据加载完成后,再次向下滑动时,会先加载上一次请求的数据,加载完成后,再加载本次请求的数据。LazyForEach添加了cachedCount缓存列表项后,其渲染过程如下:

  1. 首先,请求n+cachedCount条数据,并在屏幕上显示n条数据。
  2. 当列表滑动,缓存列表项需要从屏幕可视区外进入可视区内时,此时只用渲染组件即可,相比不设置cachedCount提升了显示效率。
  3. 当列表不断滑动,屏幕可视区外缓存的列表项数量少于cachedCount设置数量时,会触发列表项数据加载事件,继续预加载下一组缓存列表项(cachedCount个)。
  4. 当上滑下滑间隔进行时,列表两个方向分别缓存cachedCount条数据。
  5. 如果不显式设置cachedCount,cachedCount默认为1。

其数据加载、组件树挂载、页面渲染的示意图如下所示:
alt

使用场景和规则

使用场景

缓存列表项适合加载列表项数据请求比较耗时的场景。比如,滑动列表中含有短视频、高清图片等数据量比较大的资源,可以通过预先从网络加载并缓存相关数据,缩短渲染前的准备时间,提升列表响应速度。

使用规则

使用限制为:缓存列表项仅在使用LazyForEach懒加载时有效,ForEach循环渲染会一次性加载全量数据,故无法也不需要设置缓存列表项。

性能分析

10000条数据量下不同cachedCount对列表滑动帧率的影响如下:
alt
一般而言,缓存的cachedCount=n/2(n为一屏显示的列表数)的时候,效果较好。

动态预加载

原理介绍

HarmonyOS提供了内容预取的能力Prefetcher,支持应用动态自适应网络状态,通过提前下载一些图片或资源,确保相关资源在需要时能立即显示,以尽可能减少白块出现的概率。其渲染过程如下:

  1. 首先,请求n条数据,并在屏幕上显示m条数据。
  2. 当列表滑动,缓存列表项需要从屏幕可视区外进入可视区内时,此时显示预渲染组件,屏幕可视区外会动态调整预取范围,相比仅设置cachedCount提升了显示效率。
  3. 当列表不断滑动,屏幕可视区外实时更新列表项、更新预取数据和预渲染数据。

alt

使用场景

动态预加载适合加载列表项数据请求比较耗时的场景。比如,滑动列表中含有图片等数据量比较大的资源,在LazyForEach的数据源中使用IDataSourcePrefetching的prefetch提前从网络加载并缓存相关数据,BasicPrefetcher则是在滚动体验和CPU节省方面有明显的提升。从这几个方面来提升应用的响应速度。

性能分析

本文案例中的长列表一屏可以加载6条数据,为了测试动态预加载方案与设置不同的cachedCount对应用性能的影响。来测试快速滑动场景下出现的白块数量、CPU开销占比以及首屏加载时长。如下对比场景设置数据cachedCount=5、cachedCount=40。最终,使用IDE的Profiler工具检测下述指标,得到的数据如下所示:

  1. 场景滑动对比数据设置首屏加载滑动过程滑块数量cachedCount=5首屏加载快滑动过程中白块很多cachedCount=40首屏加载慢滑动过程中没有白块或很少动态预加载首屏加载快滑动过程中没有白块或很少
  2. CPU开销对比数据设置CPU占比cachedCount=53.96%cachedCount=405.04%动态预加载4.12%
  3. 首屏加载时长对比数据设置首屏加载时长cachedCount=5530.4mscachedCount=401.8s动态预加载545.5ms

组件复用

原理介绍

HarmonyOS应用框架提供了组件复用能力,可复用组件从组件树上移除时,会进入到一个回收缓存区。后续创建新组件节点时,会复用缓存区中的节点,节约组件重新创建的时间。组件复用机制如下:

  1. 标记为@Reusable的组件从组件树上被移除时,组件和其对应的JSView对象都会被放入复用缓存中。
  2. 当列表滑动新的ListItem将要被显示,List组件树上需要新建节点时,将会从复用缓存中查找可复用的组件节点。
  3. 找到可复用节点并对其进行更新后添加到组件树中。从而节省了组件节点和JSView对象的创建时间。

alt

使用场景和规则

使用场景

若业务实现中存在列表滚动、动态布局更新、地图渲染等场景,并成为UI线程的帧率瓶颈,推荐使用组件复用

使用规则
  • 使用 @Reusable标识:@Reusable标识自定义组件具备可复用的能力,它可以被添加到任意的自定义组件上,但是开发者需要小心处理自定义组件的创建流程和更新流程以确保自定义组件在复用之后能展示出正确的行为;
  • 缓存和复用范围:可复用自定义组件的缓存和复用只能发生在同一父组件下,无法在不同的父组件下复用同一自定义节点的实例。e.g. A组件是可复用组件,其也是B组件的子组件,并进入了B组件的可复用节点缓存中,但是在C组件中创建A组件时,无法使用B组件缓存的A组件;
  • 组件结构无显著变化:自定义组件的复用带来的性能提升主要体现在节省了自定义组件的JS对象的创建时间并复用了自定义组件的组件树结构,若应用开发者在自定义组件复用的前后使用渲染控制语法显著的改变了自定义组件的组件树结构,那么将无法享受到组件复用带来的性能提升;
  • 仅在特定场景触发:组件复用仅发生在存在可复用组件从组件树上移除并再次加入到组件树的场景中,若不存在上述场景,将无法触发组件复用。e.g. 使用ForEach渲染控制语法创建可复用的自定义组件,由于ForEach渲染控制语法的全展开属性,不能触发组件复用。

性能分析

组件复用 组件复用前 组件复用后
丢帧率 3.7% 0%
BuildLazyItem耗时 10.277ms 0.749ms
BuildRecycle耗时 不涉及 0.221ms
总耗时 13.430ms 7.310ms

布局优化

原理介绍

列表不同于其他布局,包含了大量重复循环的ListItem,所以对每一个ListItem的布局优化格外重要。错误的布局方式可能会导致组件树和嵌套层数过多,在创建和布局绘制阶段产生较大的性能开销,导致界面卡顿。合理使用布局,减少嵌套层数,能提高布局效率。
alt

性能分析

布局相对布局(2层)线性布局(5层)额外嵌套的线性布局(25层)独占内存78.4MB80.1MB153.7MB丢帧率0%0%2.3%

原文链接

长列表加载性能优化


本文转载自: https://blog.csdn.net/weixin_44349071/article/details/143082048
版权归原作者 fxyyf1998 所有, 如有侵权,请联系我们删除。

“[HarmonyOS Next 性能]鸿蒙性能优化 - 长列表加载性能优化”的评论:

还没有评论