鸿蒙(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

相关文章

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

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

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

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

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

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

PyQt5 GUI 开发的基础知识

《PyQt5GUI开发的基础知识》Qt是一个跨平台的C++图形用户界面开发框架,支持GUI和非GUI程序开发,本文介绍了使用PyQt5进行界面开发的基础知识,包括创建简单窗口、常用控件、窗口属性设... 目录简介第一个PyQt程序最常用的三个功能模块控件QPushButton(按钮)控件QLable(纯文本

PyTorch中的词嵌入层(nn.Embedding)详解与实战应用示例

《PyTorch中的词嵌入层(nn.Embedding)详解与实战应用示例》词嵌入解决NLP维度灾难,捕捉语义关系,PyTorch的nn.Embedding模块提供灵活实现,支持参数配置、预训练及变长... 目录一、词嵌入(Word Embedding)简介为什么需要词嵌入?二、PyTorch中的nn.Em

小白也能轻松上手! 路由器设置优化指南

《小白也能轻松上手!路由器设置优化指南》在日常生活中,我们常常会遇到WiFi网速慢的问题,这主要受到三个方面的影响,首要原因是WiFi产品的配置优化不合理,其次是硬件性能的不足,以及宽带线路本身的质... 在数字化时代,网络已成为生活必需品,追剧、游戏、办公、学习都离不开稳定高速的网络。但很多人面对新路由器

在IntelliJ IDEA中高效运行与调试Spring Boot项目的实战步骤

《在IntelliJIDEA中高效运行与调试SpringBoot项目的实战步骤》本章详解SpringBoot项目导入IntelliJIDEA的流程,教授运行与调试技巧,包括断点设置与变量查看,奠定... 目录引言:为良驹配上好鞍一、为何选择IntelliJ IDEA?二、实战:导入并运行你的第一个项目步骤1

Spring Boot3.0新特性全面解析与应用实战

《SpringBoot3.0新特性全面解析与应用实战》SpringBoot3.0作为Spring生态系统的一个重要里程碑,带来了众多令人兴奋的新特性和改进,本文将深入解析SpringBoot3.0的... 目录核心变化概览Java版本要求提升迁移至Jakarta EE重要新特性详解1. Native Ima

Spring Boot 与微服务入门实战详细总结

《SpringBoot与微服务入门实战详细总结》本文讲解SpringBoot框架的核心特性如快速构建、自动配置、零XML与微服务架构的定义、演进及优缺点,涵盖开发环境准备和HelloWorld实战... 目录一、Spring Boot 核心概述二、微服务架构详解1. 微服务的定义与演进2. 微服务的优缺点三

SpringBoot集成MyBatis实现SQL拦截器的实战指南

《SpringBoot集成MyBatis实现SQL拦截器的实战指南》这篇文章主要为大家详细介绍了SpringBoot集成MyBatis实现SQL拦截器的相关知识,文中的示例代码讲解详细,有需要的小伙伴... 目录一、为什么需要SQL拦截器?二、MyBATis拦截器基础2.1 核心接口:Interceptor