HarmonyOS开发实战( Beta5版)滑动白块问题解决最佳实践

news2024/9/25 9:33:06

当应用程序需要使用列表显示内容时,通常会使用List+LazyForEach组件来实现。但是列表中需要显示耗时加载的内容时,仅依靠List+LazyForEach不足以获得最优的用户体验。例如显示在线网络图片,在弱网以及快速滑动浏览的场景下,由于来不及完成图片加载、解码显示,列表中图片显示位置会出现白块占位符,影响用户浏览体验。

问题场景

假设开发者想要在应用中开发一个在线音乐显示列表,列表中每一个Item包含专辑封面、歌曲名称都要在线实时下载后再显示。专辑封面图片的下载和显示需要一些时间,具体取决于网络的通道质量、图像大小等因素。如果当前Item显示在屏幕上时,其对应的图像尚未加载完成,则将出现白块(图像的占位符)。为列表显示提供数据加载能力常用方法是使用LazyForEach。LazyForEach会在提供的数据源上进行迭代,并在每次迭代中创建相应的组件。当在列表组件中使用LazyForEach时,ArkUI框架会在列表的可见区域按需创建Item组件。当Item超出屏幕时,ArkUI框架会销毁并回收组件,以减少内存占用。目前仅List、Grid、Swpier和WaterFlow组件支持使用LazyForEach。

优化思路

动态预加载会根据历史任务加载耗时情况,动态调整屏幕可视区域外数据预取数量,配合懒加载设置,可在列表不断滑动时,屏幕可视区外实时更新列表数据,通过预取和预渲染数据提升列表滑动体验。

优化前代码示例

设置cachedCount=5

// ...
build() {
  Column() {
    List() {
      LazyForEach(this.dataSource, (item: SongInfoItem) => {
        ListItem() {
          ListItemComponent({ songInfo: item })
            .height(`${100 / this.ITEMS_ON_SCREEN}%`)
            .margin({ left: 20, bottom: 20 })
        }
      })
    }
    .cachedCount(5)
    .width("100%")
    .height("100%")
    .friction(0.4)
  }
}

处理白块问题的常用方案是使用LazyForEach的cachedCount属性来减少白块(设置cachedCount属性,可以支持列表预加载屏幕以外的Item项)。如上图所示,可以看到当用户在滑动列表时,依旧出现了很多白块。

如若使用更大的cachedCount值来解决,设置cachedCount=40

如上图所示,可以看到在滑动过程中白块确实变少了。但是新的问题出现了:与较小的cachedCount相比,首屏加载需要更长的时间,这同样影响用户使用体验。

过小的cachedCount值

过小的cachedCount值会导致列表预取的Item数量不足。当用户滑动列表时,后台可能来不及准备好足够多的预取项,特别是内容数据量大或网络条件特别差的时候,列表滑动过程就容易出现很多白块。

过大的cachedCount值

虽然较大的cachedCount值可以缓解缺少预取项的情况,但在列表没有滑动时,过大的cachedCount值可能导致可见区域的加载时间过长。这在首屏加载场景尤其明显:cachedCount越大,完成可见区域所需的加载耗时就越长,因为许多不可见Item需要被预取,占用资源。“首屏问题”在快速滑动后(使用ScrollBar快速滑动),屏幕加载Item也会出现耗时过长现象。cachedCount值越大,快速滚动后完成下载可见区域中项目所需的时间就越多。

在良好的网络中设置过大的cachedCount值可能会导致资源浪费:预取了过多的Item,导致额外的CPU、网络开销浪费。

结论:仅依靠cachedCount无法完美解决内容白块问题,需要引入一种能够动态适应外部条件变化(网络条件、内存变化等)的机制来解决这个问题。

优化指导

动态预加载根据历史任务加载耗时情况,动态调整屏幕可视区域外数据预取数量,配合懒加载设置,可在列表不断滑动时,屏幕可视区域外实时更新列表数据,通过预取和预渲染数据提升列表滑动体验。

Prefetcher支持应用动态自适应网络状态,通过提前下载一些图片或资源,确保相关资源在需要时能立即显示,以尽可能减少白块出现的概率。

LazyForEach懒加载可以通过使用Prefetcher来预取和预渲染数据,在使用Prefetcher后,除屏幕内显示的ListItem组件外,还会预先将屏幕可视区外的部分列表项数据进行预渲染和预取。这样当列表向下滑动时,会先显示预渲染组件,屏幕可视区外会动态调整预取范围。预取逻辑在Prefetcher的BasicPrefetcher类中实现,BasicPrefetcher支持预取和预渲染(图像解码、添加到组件树等)过程分离、自适应调整与获取范围、优先加载可视区域、以及取消不必要任务(快速滚动列表的场景下,智能取消不必要任务),其渲染过程如下:

1.首先请求n条数据,并在屏幕上显示m条数据。

2.当列表滑动,缓存列表项需要从屏幕可视区外进入可视区内时,此时显示预渲染组件,屏幕可视区外会动态调整预取范围,相比仅设置cachedCount提升了显示效率。

3.当列表不断滑动,屏幕可视区外实时更新列表项、更新预取数据和预渲染数据。

图1 动态预加载渲染过程示意图

优化后代码示例

实现DataSourcePrefetching类,继承IDataSourcePrefetching接口,并实现prefetch和cancel方法,如下代码所示(源码参考):

import { SongInfoItem } from '../model/LearningResource';
import { HashMap } from '@kit.ArkTS';
import fs from '@ohos.file.fs';
import { IDataSourcePrefetching } from '@kit.ArkUI';
import { rcp } from '@kit.RemoteCommunicationKit';

let PREFETCH_ENABLED: boolean = false;
const CANCEL_CODE: number = 1007900992;
const IMADE_UNAVAILABLE = $r('app.media.startIcon')

export default class DataSourcePrefetching implements IDataSourcePrefetching {
  private dataArray: Array<SongInfoItem>;
  private listeners: DataChangeListener[] = [];
  private readonly requestsInFlight: HashMap<number, rcp.Request> = new HashMap();
  private readonly session: rcp.Session = rcp.createSession();
  private readonly cachePath = getContext().getApplicationContext().cacheDir;

  constructor(dataArray: Array<SongInfoItem>) {
    this.dataArray = dataArray;
  }
  
  async prefetch(index: number): Promise<void> {
    PREFETCH_ENABLED = true;
    if (this.requestsInFlight.hasKey(index)) {
      throw new Error('Already being prefetched')
    }
    const item = this.dataArray[index];
    if (item.cachedImage) {
      return;
    }
    // 数据请求
    const request = new rcp.Request(item.albumUrl, 'GET');
    // 缓存网络请求对象,便于在需要取消请求的时候进行处理
    this.requestsInFlight.set(index, request);
    try {
      // 发送http请求获得响应
      const response = await this.session.fetch(request);
      if (response.statusCode !== 200 || !response.body) {
        throw new Error('Bad response');
      }
      // 将加载的数据信息存储到缓存文件中
      item.cachedImage = await this.cache(item.songId, response.body);
      // 删除指定元素
      this.requestsInFlight.remove(index);
    } catch (err) {
      if (err.code !== CANCEL_CODE) {
        item.cachedImage = IMADE_UNAVAILABLE;
        // 移除有异常的网络请求任务
        this.requestsInFlight.remove(index);
      }
      throw err as Error;
    }
  }

  cancel(index: number) {
    if (this.requestsInFlight.hasKey(index)) {
      // 返回MAP对象指定元素
      const request = this.requestsInFlight.get(index);
      // 取消数据请求
      this.session.cancel(request);
      // 移除被取消的网络请求对象
      this.requestsInFlight.remove(index);
    }
  }
}
// ...

在应用列表界面,首先创建DataSourcePrefetching、BasicPrefetcher对象,然后在List的onScrollIndex回调中调用BasicPrefetcher的visibleAreaChanged方法,传入List的可见区域起始坐标。至此完成代码的优化。源码参考

import { SongInfoItem } from '../model/LearningResource';
import DataSourcePrefetching from '../model/ArticleListData';
import { ObservedArray } from '../utils/ObservedArray';
import { ReusableArticleCardView } from '../components/ReusableArticleCardView';
import Constants from '../constants/Constants';
import { util } from '@kit.ArkTS';
import PageViewModel from '../components/PageViewModel';
import { BasicPrefetcher } from '@kit.ArkUI';

@Entry
@Component
export struct LazyForEachListPage {
  @State collectedIds: ObservedArray<string> = ['1', '2', '3', '4', '5', '6'];
  @State likedIds: ObservedArray<string> = ['1', '2', '3', '4', '5', '6'];
  @State isListReachEnd: boolean = false;
  // 创建DataSourcePrefetching对象,具备任务预取、取消能力的数据源
  private readonly dataSource = new DataSourcePrefetching(PageViewModel.getItems());
  // 创建BasicPrefetcher对象,默认的动态预取算法实现
  private readonly prefetcher = new BasicPrefetcher(this.dataSource);

  build() {
    Column() {
      Header()
      List({ space: Constants.SPACE_16 }) {
        LazyForEach(this.dataSource, (item: SongInfoItem) => {
          ListItem() {
            Column({ space: Constants.SPACE_12 }) {
              ReusableArticleCardView({ articleItem: item })
            }
          }
          .reuseId('article')
        })
      }
      .cachedCount(5)
      .onScrollIndex((start: number, end: number) => {
        // 列表滚动触发visibleAreaChanged,实时更新预取范围,触发调用prefetch、cancel接口
        this.prefetcher.visibleAreaChanged(start, end)
      })
      .width(Constants.FULL_SCREEN)
      .height(Constants.FULL_SCREEN)
      .margin({ left: 10, right: 10 })
      .layoutWeight(1)
    }
    .backgroundColor($r('app.color.text_background'))
  }
}
// ...

优化前后对比

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

滑动列表场景对比

cachedCount = 5cachedCount = 40动态预加载

数据设置首屏加载滑动过程白块数量
cachedCount=5首屏加载快滑动过程中白块很多
cachedCount=40首屏加载慢滑动过程中没有白块或很少
动态预加载首屏加载快滑动过程中没有白块或很少

CPU开销对比

利用Profiler工具分析得到相关trace图,追踪流程为应用侧的APP_LIST_FLING(列表从开始滚动到结束)的整个过程,从而观察应用的CPU占比。(注:不同设备特性和具体应用场景的多样性,所获得的性能数据存在差异,提供的数值仅供参考)

图2 cachedCount=5 CPU占比trace图

cachedCount=5的CPU占比为3.96%。

图3 cachedCount=40 CPU占比trace图

cachedCount=40的CPU占比为5.04%。

图4 动态预加载CPU占比trace图

动态预加载的CPU占比为4.12%。

数据设置CPU占比
cachedCount=53.96%
cachedCount=405.04%
动态预加载4.12%

首屏加载时长对比

利用Profiler工具分析得到相关trace图,追踪流程从Create Process(应用进程创建阶段)标签开始,到首屏全部图片加载完毕结束,从而观察应用的首屏加载时长。(注:不同设备特性和具体应用场景的多样性,所获得的性能数据存在差异,提供的数值仅供参考)

图5 cachedCount=5首屏加载时长trace图

cachedCount=5首屏加载时长为530.4ms

图6 cachedCount=40首屏加载时长trace图

cachedCount=40首屏加载时长为1.8s

图7 动态预加载首屏加载时长trace图

动态预加载首屏加载时长为545.5ms

数据设置首屏加载时长
cachedCount=5530.4ms
cachedCount=401.8s
动态预加载545.5ms

总结

从实验数据可以看出:

1)当cachedCount=5时,首屏加载时间短,滑动过程中出现大量白块,滑动时CPU占比较小。

2)当cachedCount=40时,首屏加载时间过长,滑动过程中并未出现白块,滑动时CPU占比较大。

3)当在cachedCount=5时的基础上设置动态预加载时,首屏加载时间短,滑动过程中并未出现白块,滑动时CPU占比较小。

因此当用户使用LazyForEach在线加载含有图片等数据量比较大的资源时,可以考虑使用动态预加载来预防弱网以及快速滑动场景中出现的白块问题。

动态预加载是在模拟弱网以及快速滑动的状态下加载数据测试而得出的数据结论。当利用网络数据来探讨LazyForEach代码如何进行网络数据的加载和优化时,可以使用动态预加载,使用动态预加载这项技术后,因将预取和预渲染分离且在滑动过程中实时更新列表项、预取数据和预渲染数据,故能在弱网和快速滑动场景中明显减少滑动过程中出现的白块现象。

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,不同的角度的问了一些问题,我明显感觉到一点,那就是许多人参与鸿蒙开发,但是又不知道从哪里下手,因为资料太多,太杂,教授的人也多,无从选择。有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)文档用来跟着学习是非常有必要的。 

为了确保高效学习,建议规划清晰的学习路线,涵盖以下关键阶段:

希望这一份鸿蒙学习文档能够给大家带来帮助~


 鸿蒙(HarmonyOS NEXT)最新学习路线

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

2.视频教程+学习PDF文档

(鸿蒙语法ArkTS、TypeScript、ArkUI教程……)

 纯血版鸿蒙全套学习文档(面试、文档、全套视频等)

                   

鸿蒙APP开发必备

​​

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2089866.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

哪种无线可视挖耳勺最实用?五大优良黑马机型测评

无线可视挖耳勺是热度特别高的个人清洁工具&#xff0c;不仅能够更加清晰地观察自己耳内的状况&#xff0c;从而更加安全、有效地清洁耳朵&#xff0c;可以发挥多种多样的作用&#xff0c;但也存在品牌繁杂的情况&#xff0c;其中还有一些不专业的产品。在市面上&#xff0c;我…

Win11 本地部署大模型 WebUI + ComfyUI

Open WebUI 是一个可扩展、功能丰富且用户友好的自托管 Web 用户界面&#xff08;WebUI&#xff09;&#xff0c;它被设计用于完全离线操作。该项目最初被称为 Ollama WebUI&#xff0c;后来更名为 Open WebUI。Open WebUI 的主要目的是为本地的大语言模型&#xff08;LLMs&…

一条执行24s的SQL产生7小时主备延时(案例详解)

前言 在之前的文章《一条执行24s的SQL竟产生7小时数据延时&#xff0c;数据库高可用做了个寂寞》中&#xff0c;我们描述了一个问题现象&#xff1a;一条在主库执行24秒的SQL语句&#xff0c;却导致了主从延时长达7小时。文章发布后&#xff0c;引发了小伙伴的热烈讨论&#x…

zabbix安装过程中仓库不可用问题解决

官网文档安装zabbix-server&#xff1a;Download and install Zabbix 安装zabbix-server过程中碰到如下报错&#xff1a; 导致原因是仓库url被qiang&#xff0c;创建如下仓库&#xff1a; 仓库文件&#xff1a; [centos-sclo-rh] nameCentOS-7 - SCLo rh baseurlhttps://mirr…

6 款 AI 视频全自动国产剪辑软件推荐

这两年刷抖音、B站&#xff0c;你是不是也常看到AI做的视频火得一塌糊涂&#xff0c;流量爆棚&#xff0c;还帮作者赚了钱?那没学过剪辑的你&#xff0c;想不想零成本、零门槛&#xff0c;也用AI软件赚点外快呢?告诉你&#xff0c;绝对行得通!现在AI技术牛了&#xff0c;网上…

顺序表

目录 1. 数据结构 2. 顺序表 1&#xff09;线性表 2&#xff09;顺序表分类 3、动态顺序表的实现 1. 数据结构 数据&#xff1a;常见的数值1、2、3、4.....、教务系统里保存的用户信息&#xff08;姓名、性别、年龄、学历等 等&#xff09;、网页里肉眼可以看到的信息&…

ARM体系结构及接口技术(四)LED灯实验---Makefile文件解析

文章目录 一、汇编版本二、C语言版本&#xff08;一&#xff09;Makefile文件1. .elf文件2. .map文件3. wildcard函数4. patsubst函数 &#xff08;二&#xff09;map.lds&#xff08;三&#xff09;start.S 一、汇编版本 # 工程名对应的变量 NAMEasm-led# 交叉编译器的前缀的…

十、软件工程基础知识(考点篇)试题

降低需求变更成本&#xff0c;第一想到的就是原型法&#xff0c;后面对于已完成开发工作的的反馈意见&#xff0c;已完成开发工作只有增量式的吧&#xff0c;先开发核心的&#xff0c;然后发布一版&#xff0c;得到用户反馈再修改并开发次核心。快速原型强调的是&#xff0c;先…

BP神经网络学习内容分享:数据降维

在数据分析和机器学习的领域中&#xff0c;数据降维是一项非常重要的技术。它旨在减少数据集中的特征数量&#xff0c;同时尽可能保留原始数据的重要信息。这不仅有助于减少计算复杂度和提高算法效率&#xff0c;还能有效避免过拟合&#xff0c;提升模型的泛化能力。本文将简要…

数学建模--皮尔逊相关系数、斯皮尔曼相关系数

目录 1.总体的皮尔逊相关系数 2.样本的皮尔逊相关系数 3.对于皮尔逊相关系数的认识 4.描述性统计以及corr函数 ​编辑 5.数据导入实际操作 6.引入假设性检验 6.1简单认识 6.2具体步骤 7.p值判断法 8.检验正态分布 8.1jb检验 8.2威尔克检验&#xff1a;针对于p值进行…

【单片机原理及应用】实验:数字秒表显示器

目录 一、实验目的 二、实验内容 三、实验步骤 四、记录与处理 五、思考 六、成果文件提取链接 一、实验目的 熟悉中断和定时/计数器工作原理&#xff0c;掌握定时器的C51编程与调试方法。 二、实验内容 【参照图表】 图A.6 &#xff08;1&#xff09;创建一个包含80C51固…

【OWOD论文】开放世界中OD代码_2_模型部分

简介 本文记录OWOD代码中的模型代码部分。数据部分可看我上一个博客【【OWOD论文】开放世界中OD代码_1_数据部分-CSDN博客】 模型代码 1 起步 在代码中找到 detectron2\engine\defaults.py DefaultTrainer类 __init__方法 根据上述 build_model 回溯到 detectron2\modeling\…

OCC笔记:Windows下OCC的编译

一、源码下载 进OCC官网下载https://dev.opencascade.org/release即可&#xff0c;或直接Clone它的Git库https://dev.opencascade.org/resources/git_repository&#xff0c;本文用的源码库版本为7.4.0&#xff08;我本机安装的VS2013&#xff0c;我又想用到AIS_ViewCube&…

使用Blender云渲染的好处是什么?

​Blender是一款功能强大的开源3D创作软件&#xff0c;用于包括建模、动画、仿真、渲染、合成和视频编辑在内的多种应用。然而&#xff0c;Blender的渲染过程有时可能非常耗费资源&#xff0c;特别是处理复杂的3D场景时。作为CG行业不可或缺的一部分&#xff0c;云渲染通过使用…

chat2DB体验

文章目录 Chat2DB体验的印象Chat2DB是什么&#xff1f;流水帐数据库示例新建数据表生成测试数据查询数据特殊查询 Chat2DB 体验的印象 主页是https://chat2db-ai.com/ 因为最近物理研究需要用到很多数据&#xff0c;所以试用了一个号称神级AI数据库系统。 首先&#xff0c; …

【论文解析】基于脉动阵列的层融合注意力模型加速器结构

作者及发刊详情 刘晓航, 姜晶菲, 许金伟. 基于脉动阵列的层融合注意力模型加速器结构[J]. Computer Engineering & Science/Jisuanji Gongcheng yu Kexue, 2023, 45(5). 摘要 正文 主要工作贡献 1&#xff09;)提出了硬件协同控制的注意力机制矩阵分块方法 2&#xf…

数据仓库系列14:数据清洗和转换的常见方法有哪些?

数据仓库的建立不仅仅是数据的简单存储&#xff0c;更是对数据的深度利用。而数据清洗和转换是确保数据质量和一致性的重要环节。在这篇文章中&#xff0c;我们将深入探讨数据清洗和转换的常见方法&#xff0c;帮助你在数据仓库中更高效地处理数据。 目录 为什么数据清洗和转换…

任务通知笔记

1、任务通知简介 任务通知 用来通知任务的&#xff0c;任务控制块中的结构体成员变量ulNotifiedValue就是这个通知值。 任务通知与队列、信号量和时间标志组的区别 任务通知的优势及劣势 优势 效率更高&#xff1a;使用任务通知向任务发送事件或者数据比使用队列、事件标志…

C语言典型例题56

《C程序设计教程&#xff08;第四版&#xff09;——谭浩强》 例题4.8 将范围为100~200的不能被3整除的数输出。 代码&#xff1a; //《C程序设计教程&#xff08;第四版&#xff09;——谭浩强》 //例题4.8 将范围为100~200的不能被3整除的数输出。//#include <stdio.h>…

您下一款项目管理工具何必是它,10款软件推荐

国内外主流的 10 款项目管理系统对比&#xff1a;PingCode、Worktile、Teambition、明道云、泛微E-cology、Asana、Trello、Monday.com、ClickUp、Wrike。 在项目管理的世界里&#xff0c;选择合适的管理工具似乎是一个令人头疼的问题。你是否经常在众多选项中感到迷茫&#xf…