HarmonyOS开发实战( Beta5版)Swiper高性能开发指南

2024-09-04 16:04

本文主要是介绍HarmonyOS开发实战( Beta5版)Swiper高性能开发指南,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

背景

在应用开发中,Swiper 组件常用于翻页场景,比如:桌面、图库等应用。Swiper 组件滑动切换页面时,基于按需加载原则通常会在下一个页面将要显示时才对该页面进行加载和布局绘制,这个过程包括:

  • 如果该页面使用了@Component 装饰的自定义组件,那么自定义组件的 build 函数会被执行并创建内部的 UI 组件;

  • 如果使用了LazyForEach,会执行 LazyForEach 的 UI 生成函数生成 UI 组件;

  • 在 UI 组件构建完成后,会对 UI 组件进行布局测算和绘制。

针对复杂页面场景,该过程可能会持续较长时间,导致滑动过程中出现卡顿,对滑动体验造成负面影响,甚至成为整个应用的性能瓶颈。如在图库大图浏览场景中,若不使用预加载机制,每次都将在滑动开始的首帧去加载下一张图片,会导致首帧耗时过长甚至掉帧,拖慢应用性能。

为了解决上述问题,可以使用 Swiper 组件的预加载机制,利用主线程的空闲时间来提前构建和布局绘制组件,优化滑动体验。

使用场景

如果开发者的应用场景属于加载较为耗时的场景时,尤其是下列场景,推荐使用 Swiper 预加载功能。

  • Swiper 的子组件大于等于五个;

  • Swiper 的子组件具有复杂的动画;

  • Swiper 的子组件加载时需要执行网络请求等耗时操作;

  • Swiper 的子组件包含大量需要渲染的图像或资源。

Swiper 预加载机制说明

预加载机制是 Swiper 组件中一个重要的特性,允许 Swiper 滑动到下一个子组件之前提前加载后续页面的内容,其主要目的是提高应用滑动时的流畅性和响应速度。当用户尝试滑动到下一个子组件时,如果下一个子组件的内容已经提前加载完毕,那么滑动就会立即发生,否则 Swiper 组件需要在加载下一个子组件的同时处理滑动事件,对滑动体验造成负面影响。当前 Swiper 组件的预加载在用户滑动离手动效开始时触发,离手动效的计算在渲染线程中进行,因此主线程有空闲的时间可以进行预加载的操作。配合 LazyForEach 的按需加载和销毁能力,可以在优化滑动体验基础上节省内存占用。

使用指导

  • 预加载子组件的个数在cachedCount属性中配置。

Swiper 共 5 页,当开发者设置了 cacheCount 属性为 1 且 loop 属性为 false 时,预加载的结果如下:

loop=false


 Swiper 共 5 页,当开发者设置了 cacheCount 属性为 1 且 loop 属性为 true 时,预加载的结果如下:

loop=true

  • Swiper 组件的子组件使用LazyForEach动态加载和销毁组件。

示例

class MyDataSource implements IDataSource { // LazyForEach的数据源private list: number[] = [];constructor(list: number[]) {this.list = list;}totalCount(): number {return this.list.length;}getData(index: number): number {return this.list[index];}registerDataChangeListener(_: DataChangeListener): void {}unregisterDataChangeListener(): void {}
}@Component
struct SwiperChildPage { // Swiper的子组件@State arr: number[] = [];aboutToAppear(): void {for (let i = 1; i <= 100; i++) {this.arr.push(i);}}build() {Column() {List({ space: 20 }) {ForEach(this.arr, (index: number) => {ListItem() {Text(index.toString()).height('4.5%').fontSize(16).textAlign(TextAlign.Center).backgroundColor(0xFFFFFF)}.border({ width: 2, color: Color.Green })}, (index: number) => index.toString());}.height("95%").width("95%").border({ width: 3, color: Color.Red }).lanes({ minLength: 40, maxLength: 40 }).alignListItem(ListItemAlign.Start).scrollBar(BarState.Off)}.width('100%').height('100%').padding({ top: 5 });}
}@Entry
@Preview
@Component
struct SwiperExample {private dataSrc: MyDataSource = new MyDataSource([]);aboutToAppear(): void {let list: Array<number> = []for (let i = 1; i <= 10; i++) {list.push(i);}this.dataSrc = new MyDataSource(list);}build() {Column({ space: 5 }) {Swiper() {LazyForEach(this.dataSrc, (_: number) => {SwiperChildPage();}, (item: number) => item.toString());}.loop(false).cachedCount(1) // 提前加载后一项的内容.indicator(true).duration(100).displayArrow({showBackground: true,isSidebarMiddle: true,backgroundSize: 40,backgroundColor: Color.Orange,arrowSize: 25,arrowColor: Color.Black}, false).curve(Curve.Linear)}.width('100%').margin({ top: 5 })}
}

验证效果

为了更好地体现 Swiper 预加载机制带来的性能优化效果,用例采用下列前置条件:

  • Swiper 的子组件为带有 100 个 ListItem 的 List 组件;

  • Swiper 组件共有 10 个 List 子组件。

在该场景下,使用 Swiper 预加载机制可以为每个翻页动作节省约40%的时间,同时保证翻页时不丢帧,保证翻页的流畅度。

优化建议

由于组件构建和布局计算需要一定时间,cachedCount 的数量也不是设置得越大越好,过大的 cachedCount 可能会导致应用性能降低。当前 Swiper 组件滑动离手后的动效时间大约是 400ms,如果应用加载一个子组件的时间在 100ms~200ms 之间,为了在离手动效时间内完成组件的预加载,cachedCount 属性建议设置为 1 或 2,设置过大会导致主线程阻塞而产生卡顿。

那么方案可以继续优化,在抛滑场景时,Swiper 组件有一个OnAnimationStart回调接口,切换动画开始时触发该回调。此时,主线程空闲,应用可以充分利用这段时间进行图片等资源的预加载,减少后续 cachedCount 范围内的节点预加载耗时; 跟手滑动阶段不会触发OnAnimationStart回调,只有在离手后做切换动画(也就是抛滑阶段)才会触发。

示例

Swiper 子组件页面代码如下:

在子组件首次构建(生命周期执行到aboutToAppear)时,先判断 dataSource 中该 index 的数据是否有数据,若无数据则先进行资源加载,再构建节点。若有数据,则直接构建节点即可。

import image from '@ohos.multimedia.image';
import { MyDataSource } from './Index';@Component
export struct PhotoItem { //Swiper的子组件myIndex: number = 0;private dataSource: MyDataSource = new MyDataSource([]);context = getContext(this);@State imageContent: image.PixelMap | undefined = undefined;aboutToAppear(): void {console.info(`aboutToAppear` + this.myIndex);this.imageContent = this.dataSource.getData(this.myIndex)?.image;if (!this.imageContent) { // 先判断dataSource中该index的数据是否有数据,若无数据则先进行资源加载try {// 获取resourceManager资源管理器const resourceMgr = this.context.resourceManager;// 获取rawfile文件夹下item.jpg的ArrayBufferlet str = "item" + (this.myIndex + 1) + ".jpg";resourceMgr.getRawFileContent(str).then((value) => {// 创建imageSourceconst imageSource = image.createImageSource(value.buffer);imageSource.createPixelMap().then((value) => {console.info("aboutToAppear push" + this.myIndex)this.dataSource.addData(this.myIndex, { description: "" + this.myIndex, image: value })this.imageContent = value;})})} catch (err) {console.error("error code" + err);}}}build() {Column() {Image(this.imageContent).width("100%").height("100%")}}
}

Swiper 主页面的代码如下:

import Curves from '@ohos.curves';
import { PhotoItem } from './PhotoItem'
import image from '@ohos.multimedia.image';interface MyObject {description: string,image: image.PixelMap,
};export class MyDataSource implements IDataSource {private list: MyObject[] = []constructor(list: MyObject[]) {this.list = list}totalCount(): number {return this.list.length}getData(index: number): MyObject {return this.list[index]}registerDataChangeListener(listener: DataChangeListener): void {}unregisterDataChangeListener(listener: DataChangeListener): void {}addData(index: number, data: MyObject) {this.list[index] = data;}
}@Entry
@Component
struct Index {@State currentIndex: number = 0;cacheCount: number = 1swiperController: SwiperController = new SwiperController();private data: MyDataSource = new MyDataSource([]);context = getContext(this);aboutToAppear() {let list: MyObject[] = []for (let i = 0; i < 6; i++) {list.push({ description: "", image: this.data.getData(this.currentIndex)?.image })}this.data = new MyDataSource(list)}build() {Swiper(this.swiperController) {LazyForEach(this.data, (item: MyObject, index?: number) => {PhotoItem({myIndex: index,dataSource: this.data})})}.cachedCount(this.cacheCount).curve(Curves.interpolatingSpring(0, 1, 228, 30)).index(this.currentIndex).indicator(true).loop(false)// 在OnAnimationStart接口回调中进行预加载资源的操作.onAnimationStart((index: number, targetIndex: number) => {console.info("onAnimationStart " + index + " " + targetIndex);if (targetIndex !== index) {try {// 获取resourceManager资源管理器const resourceMgr = this.context.resourceManager;// 获取rawfile文件夹下item.jpg的ArrayBufferlet str = "item" + (targetIndex + this.cacheCount + 2) + ".jpg";resourceMgr.getRawFileContent(str).then((value) => {// 创建imageSourceconst imageSource = image.createImageSource(value.buffer);imageSource.createPixelMap().then((value) => {this.data.addData(targetIndex + this.cacheCount + 1, {description: "" + (targetIndex + this.cacheCount + 1),image: value})})})} catch (err) {console.error("error code" + err);}}}).width('100%').height('100%')}
}

总结

  • Swiper 组件的预加载机制与 LazyForEach 结合使用,能够达到最佳优化效果。

  • 预加载的 cachedCount 并非越大越好,需要结合单个子组件加载耗时来设置。假设一个子组件的加载耗时为 Nms,那么 cachedCount 推荐设置为小于 400/N。

  • 如果应用有非常高的性能优化需求,Swiper 预加载机制可搭配 OnAnimationStart 接口回调使用,进一步提升预加载的效率。

最后

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

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

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

GitCode - 全球开发者的开源社区,开源代码托管平台


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

​​

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

路线图适合人群:

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

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

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

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

                   

鸿蒙APP开发必备

​​​

总结

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

这篇关于HarmonyOS开发实战( Beta5版)Swiper高性能开发指南的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1136397

相关文章

Spring Boot集成/输出/日志级别控制/持久化开发实践

《SpringBoot集成/输出/日志级别控制/持久化开发实践》SpringBoot默认集成Logback,支持灵活日志级别配置(INFO/DEBUG等),输出包含时间戳、级别、类名等信息,并可通过... 目录一、日志概述1.1、Spring Boot日志简介1.2、日志框架与默认配置1.3、日志的核心作用

破茧 JDBC:MyBatis 在 Spring Boot 中的轻量实践指南

《破茧JDBC:MyBatis在SpringBoot中的轻量实践指南》MyBatis是持久层框架,简化JDBC开发,通过接口+XML/注解实现数据访问,动态代理生成实现类,支持增删改查及参数... 目录一、什么是 MyBATis二、 MyBatis 入门2.1、创建项目2.2、配置数据库连接字符串2.3、入

Apache Ignite 与 Spring Boot 集成详细指南

《ApacheIgnite与SpringBoot集成详细指南》ApacheIgnite官方指南详解如何通过SpringBootStarter扩展实现自动配置,支持厚/轻客户端模式,简化Ign... 目录 一、背景:为什么需要这个集成? 二、两种集成方式(对应两种客户端模型) 三、方式一:自动配置 Thick

SQL Server跟踪自动统计信息更新实战指南

《SQLServer跟踪自动统计信息更新实战指南》本文详解SQLServer自动统计信息更新的跟踪方法,推荐使用扩展事件实时捕获更新操作及详细信息,同时结合系统视图快速检查统计信息状态,重点强调修... 目录SQL Server 如何跟踪自动统计信息更新:深入解析与实战指南 核心跟踪方法1️⃣ 利用系统目录

使用IDEA部署Docker应用指南分享

《使用IDEA部署Docker应用指南分享》本文介绍了使用IDEA部署Docker应用的四步流程:创建Dockerfile、配置IDEADocker连接、设置运行调试环境、构建运行镜像,并强调需准备本... 目录一、创建 dockerfile 配置文件二、配置 IDEA 的 Docker 连接三、配置 Do

Python进行JSON和Excel文件转换处理指南

《Python进行JSON和Excel文件转换处理指南》在数据交换与系统集成中,JSON与Excel是两种极为常见的数据格式,本文将介绍如何使用Python实现将JSON转换为格式化的Excel文件,... 目录将 jsON 导入为格式化 Excel将 Excel 导出为结构化 JSON处理嵌套 JSON:

深入浅出SpringBoot WebSocket构建实时应用全面指南

《深入浅出SpringBootWebSocket构建实时应用全面指南》WebSocket是一种在单个TCP连接上进行全双工通信的协议,这篇文章主要为大家详细介绍了SpringBoot如何集成WebS... 目录前言为什么需要 WebSocketWebSocket 是什么Spring Boot 如何简化 We

java中pdf模版填充表单踩坑实战记录(itextPdf、openPdf、pdfbox)

《java中pdf模版填充表单踩坑实战记录(itextPdf、openPdf、pdfbox)》:本文主要介绍java中pdf模版填充表单踩坑的相关资料,OpenPDF、iText、PDFBox是三... 目录准备Pdf模版方法1:itextpdf7填充表单(1)加入依赖(2)代码(3)遇到的问题方法2:pd

Python极速搭建局域网文件共享服务器完整指南

《Python极速搭建局域网文件共享服务器完整指南》在办公室或家庭局域网中快速共享文件时,许多人会选择第三方工具或云存储服务,但这些方案往往存在隐私泄露风险或需要复杂配置,下面我们就来看看如何使用Py... 目录一、android基础版:HTTP文件共享的魔法命令1. 一行代码启动HTTP服务器2. 关键参

Python对接支付宝支付之使用AliPay实现的详细操作指南

《Python对接支付宝支付之使用AliPay实现的详细操作指南》支付宝没有提供PythonSDK,但是强大的github就有提供python-alipay-sdk,封装里很多复杂操作,使用这个我们就... 目录一、引言二、准备工作2.1 支付宝开放平台入驻与应用创建2.2 密钥生成与配置2.3 安装ali