鸿蒙(HarmonyOS)性能优化实战-Swiper高性能开发

2024-04-23 23:04

本文主要是介绍鸿蒙(HarmonyOS)性能优化实战-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 时,预加载的结果如下:\


 Swiper 共 5 页,当开发者设置了 cacheCount 属性为 1 且 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 范围内的节点预加载耗时。

示例

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.log("aboutToAppear push" + this.myIndex)this.dataSource.addData(this.myIndex, { description: "" + this.myIndex, image: value })this.imageContent = value;})})} catch (err) {console.log("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.log("error code" + err);}}}).width('100%').height('100%')}
}

总结

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

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

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

码牛课堂也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线。大家可以进行参考学习:https://qr21.cn/FV7h05

①全方位,更合理的学习路径
路线图包括ArkTS基础语法、鸿蒙应用APP开发、鸿蒙能力集APP开发、次开发多端部署开发、物联网开发等九大模块,六大实战项目贯穿始终,由浅入深,层层递进,深入理解鸿蒙开发原理!

②多层次,更多的鸿蒙原生应用
路线图将包含完全基于鸿蒙内核开发的应用,比如一次开发多端部署、自由流转、元服务、端云一体化等,多方位的学习内容让学生能够高效掌握鸿蒙开发,少走弯路,真正理解并应用鸿蒙的核心技术和理念。

③实战化,更贴合企业需求的技术点
学习路线图中的每一个技术点都能够紧贴企业需求,经过多次真实实践,每一个知识点、每一个项目,都是码牛课堂鸿蒙研发团队精心打磨和深度解析的成果,注重对学生的细致教学,每一步都确保学生能够真正理解和掌握。

为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙开发学习手册》:https://qr21.cn/FV7h05

如何快速入门:

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

开发基础知识:https://qr21.cn/FV7h05

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

基于ArkTS 开发:https://qr21.cn/FV7h05

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

鸿蒙开发面试真题(含参考答案):https://qr21.cn/FV7h05

大厂鸿蒙面试题::https://qr18.cn/F781PH

鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH

1.项目开发必备面试题
2.性能优化方向
3.架构方向
4.鸿蒙开发系统底层方向
5.鸿蒙音视频开发方向
6.鸿蒙车载开发方向
7.鸿蒙南向开发方向

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



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

相关文章

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

使用 sql-research-assistant进行 SQL 数据库研究的实战指南(代码实现演示)

《使用sql-research-assistant进行SQL数据库研究的实战指南(代码实现演示)》本文介绍了sql-research-assistant工具,该工具基于LangChain框架,集... 目录技术背景介绍核心原理解析代码实现演示安装和配置项目集成LangSmith 配置(可选)启动服务应用场景

Deepseek使用指南与提问优化策略方式

《Deepseek使用指南与提问优化策略方式》本文介绍了DeepSeek语义搜索引擎的核心功能、集成方法及优化提问策略,通过自然语言处理和机器学习提供精准搜索结果,适用于智能客服、知识库检索等领域... 目录序言1. DeepSeek 概述2. DeepSeek 的集成与使用2.1 DeepSeek API

在Java中使用ModelMapper简化Shapefile属性转JavaBean实战过程

《在Java中使用ModelMapper简化Shapefile属性转JavaBean实战过程》本文介绍了在Java中使用ModelMapper库简化Shapefile属性转JavaBean的过程,对比... 目录前言一、原始的处理办法1、使用Set方法来转换2、使用构造方法转换二、基于ModelMapper

Tomcat高效部署与性能优化方式

《Tomcat高效部署与性能优化方式》本文介绍了如何高效部署Tomcat并进行性能优化,以确保Web应用的稳定运行和高效响应,高效部署包括环境准备、安装Tomcat、配置Tomcat、部署应用和启动T... 目录Tomcat高效部署与性能优化一、引言二、Tomcat高效部署三、Tomcat性能优化总结Tom

Java实战之自助进行多张图片合成拼接

《Java实战之自助进行多张图片合成拼接》在当今数字化时代,图像处理技术在各个领域都发挥着至关重要的作用,本文为大家详细介绍了如何使用Java实现多张图片合成拼接,需要的可以了解下... 目录前言一、图片合成需求描述二、图片合成设计与实现1、编程语言2、基础数据准备3、图片合成流程4、图片合成实现三、总结前

基于Python开发PPTX压缩工具

《基于Python开发PPTX压缩工具》在日常办公中,PPT文件往往因为图片过大而导致文件体积过大,不便于传输和存储,所以本文将使用Python开发一个PPTX压缩工具,需要的可以了解下... 目录引言全部代码环境准备代码结构代码实现运行结果引言在日常办公中,PPT文件往往因为图片过大而导致文件体积过大,

解读Redis秒杀优化方案(阻塞队列+基于Stream流的消息队列)

《解读Redis秒杀优化方案(阻塞队列+基于Stream流的消息队列)》该文章介绍了使用Redis的阻塞队列和Stream流的消息队列来优化秒杀系统的方案,通过将秒杀流程拆分为两条流水线,使用Redi... 目录Redis秒杀优化方案(阻塞队列+Stream流的消息队列)什么是消息队列?消费者组的工作方式每

nginx-rtmp-module构建流媒体直播服务器实战指南

《nginx-rtmp-module构建流媒体直播服务器实战指南》本文主要介绍了nginx-rtmp-module构建流媒体直播服务器实战指南,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有... 目录1. RTMP协议介绍与应用RTMP协议的原理RTMP协议的应用RTMP与现代流媒体技术的关系2