1e41f4b71Sopenharmony_ci# Swiper高性能开发指导 2e41f4b71Sopenharmony_ci 3e41f4b71Sopenharmony_ci## 背景 4e41f4b71Sopenharmony_ci 5e41f4b71Sopenharmony_ci在应用开发中,Swiper 组件常用于翻页场景,比如:桌面、图库等应用。Swiper 组件滑动切换页面时,基于按需加载原则通常会在下一个页面将要显示时才对该页面进行加载和布局绘制,这个过程包括: 6e41f4b71Sopenharmony_ci 7e41f4b71Sopenharmony_ci- 如果该页面使用了@Component 装饰的自定义组件,那么自定义组件的 build 函数会被执行并创建内部的 UI 组件; 8e41f4b71Sopenharmony_ci 9e41f4b71Sopenharmony_ci- 如果使用了[LazyForEach](../quick-start/arkts-rendering-control-lazyforeach.md),会执行 LazyForEach 的 UI 生成函数生成 UI 组件; 10e41f4b71Sopenharmony_ci 11e41f4b71Sopenharmony_ci- 在 UI 组件构建完成后,会对 UI 组件进行布局测算和绘制。 12e41f4b71Sopenharmony_ci 13e41f4b71Sopenharmony_ci针对复杂页面场景,该过程可能会持续较长时间,导致滑动过程中出现卡顿,对滑动体验造成负面影响,甚至成为整个应用的性能瓶颈。如在图库大图浏览场景中,若不使用预加载机制,每次都将在滑动开始的首帧去加载下一张图片,会导致首帧耗时过长甚至掉帧,拖慢应用性能。 14e41f4b71Sopenharmony_ci 15e41f4b71Sopenharmony_ci为了解决上述问题,可以使用 Swiper 组件的预加载机制,利用主线程的空闲时间来提前构建和布局绘制组件,优化滑动体验。 16e41f4b71Sopenharmony_ci 17e41f4b71Sopenharmony_ci## 使用场景 18e41f4b71Sopenharmony_ci 19e41f4b71Sopenharmony_ci如果开发者的应用场景属于加载较为耗时的场景时,尤其是下列场景,推荐使用 Swiper 预加载功能。 20e41f4b71Sopenharmony_ci 21e41f4b71Sopenharmony_ci- Swiper 的子组件大于等于五个; 22e41f4b71Sopenharmony_ci 23e41f4b71Sopenharmony_ci- Swiper 的子组件具有复杂的动画; 24e41f4b71Sopenharmony_ci 25e41f4b71Sopenharmony_ci- Swiper 的子组件加载时需要执行网络请求等耗时操作; 26e41f4b71Sopenharmony_ci 27e41f4b71Sopenharmony_ci- Swiper 的子组件包含大量需要渲染的图像或资源。 28e41f4b71Sopenharmony_ci 29e41f4b71Sopenharmony_ci## Swiper 预加载机制说明 30e41f4b71Sopenharmony_ci 31e41f4b71Sopenharmony_ci预加载机制是 Swiper 组件中一个重要的特性,允许 Swiper 滑动到下一个子组件之前提前加载后续页面的内容,其主要目的是提高应用滑动时的流畅性和响应速度。当用户尝试滑动到下一个子组件时,如果下一个子组件的内容已经提前加载完毕,那么滑动就会立即发生,否则 Swiper 组件需要在加载下一个子组件的同时处理滑动事件,对滑动体验造成负面影响。当前 Swiper 组件的预加载在用户滑动离手动效开始时触发,离手动效的计算在渲染线程中进行,因此主线程有空闲的时间可以进行预加载的操作。配合 LazyForEach 的按需加载和销毁能力,可以在优化滑动体验基础上节省内存占用。 32e41f4b71Sopenharmony_ci 33e41f4b71Sopenharmony_ci## 使用指导 34e41f4b71Sopenharmony_ci 35e41f4b71Sopenharmony_ci- 预加载子组件的个数在[cachedCount](../reference/apis-arkui/arkui-ts/ts-container-swiper.md#属性)属性中配置。 36e41f4b71Sopenharmony_ci 37e41f4b71Sopenharmony_ciSwiper 共 5 页,当开发者设置了 cacheCount 属性为 1 且 loop 属性为 false 时,预加载的结果如下:\ 38e41f4b71Sopenharmony_ci  39e41f4b71Sopenharmony_ci 40e41f4b71Sopenharmony_ci\ 41e41f4b71Sopenharmony_ci Swiper 共 5 页,当开发者设置了 cacheCount 属性为 1 且 loop 属性为 true 时,预加载的结果如下:\ 42e41f4b71Sopenharmony_ci  43e41f4b71Sopenharmony_ci 44e41f4b71Sopenharmony_ci- Swiper 组件的子组件使用[LazyForEach](../quick-start/arkts-rendering-control-lazyforeach.md)动态加载和销毁组件。 45e41f4b71Sopenharmony_ci 46e41f4b71Sopenharmony_ci**示例** 47e41f4b71Sopenharmony_ci 48e41f4b71Sopenharmony_ci```TypeScript 49e41f4b71Sopenharmony_ciclass MyDataSource implements IDataSource { // LazyForEach的数据源 50e41f4b71Sopenharmony_ci private list: number[] = []; 51e41f4b71Sopenharmony_ci 52e41f4b71Sopenharmony_ci constructor(list: number[]) { 53e41f4b71Sopenharmony_ci this.list = list; 54e41f4b71Sopenharmony_ci } 55e41f4b71Sopenharmony_ci 56e41f4b71Sopenharmony_ci totalCount(): number { 57e41f4b71Sopenharmony_ci return this.list.length; 58e41f4b71Sopenharmony_ci } 59e41f4b71Sopenharmony_ci 60e41f4b71Sopenharmony_ci getData(index: number): number { 61e41f4b71Sopenharmony_ci return this.list[index]; 62e41f4b71Sopenharmony_ci } 63e41f4b71Sopenharmony_ci 64e41f4b71Sopenharmony_ci registerDataChangeListener(_: DataChangeListener): void { 65e41f4b71Sopenharmony_ci } 66e41f4b71Sopenharmony_ci 67e41f4b71Sopenharmony_ci unregisterDataChangeListener(): void { 68e41f4b71Sopenharmony_ci } 69e41f4b71Sopenharmony_ci} 70e41f4b71Sopenharmony_ci 71e41f4b71Sopenharmony_ci@Component 72e41f4b71Sopenharmony_cistruct SwiperChildPage { // Swiper的子组件 73e41f4b71Sopenharmony_ci @State arr: number[] = []; 74e41f4b71Sopenharmony_ci 75e41f4b71Sopenharmony_ci aboutToAppear(): void { 76e41f4b71Sopenharmony_ci for (let i = 1; i <= 100; i++) { 77e41f4b71Sopenharmony_ci this.arr.push(i); 78e41f4b71Sopenharmony_ci } 79e41f4b71Sopenharmony_ci } 80e41f4b71Sopenharmony_ci 81e41f4b71Sopenharmony_ci build() { 82e41f4b71Sopenharmony_ci Column() { 83e41f4b71Sopenharmony_ci List({ space: 20 }) { 84e41f4b71Sopenharmony_ci ForEach(this.arr, (index: number) => { 85e41f4b71Sopenharmony_ci ListItem() { 86e41f4b71Sopenharmony_ci Text(index.toString()) 87e41f4b71Sopenharmony_ci .height('4.5%') 88e41f4b71Sopenharmony_ci .fontSize(16) 89e41f4b71Sopenharmony_ci .textAlign(TextAlign.Center) 90e41f4b71Sopenharmony_ci .backgroundColor(0xFFFFFF) 91e41f4b71Sopenharmony_ci } 92e41f4b71Sopenharmony_ci .border({ width: 2, color: Color.Green }) 93e41f4b71Sopenharmony_ci }, (index: number) => index.toString()); 94e41f4b71Sopenharmony_ci } 95e41f4b71Sopenharmony_ci .height("95%") 96e41f4b71Sopenharmony_ci .width("95%") 97e41f4b71Sopenharmony_ci .border({ width: 3, color: Color.Red }) 98e41f4b71Sopenharmony_ci .lanes({ minLength: 40, maxLength: 40 }) 99e41f4b71Sopenharmony_ci .alignListItem(ListItemAlign.Start) 100e41f4b71Sopenharmony_ci .scrollBar(BarState.Off) 101e41f4b71Sopenharmony_ci 102e41f4b71Sopenharmony_ci }.width('100%').height('100%').padding({ top: 5 }); 103e41f4b71Sopenharmony_ci } 104e41f4b71Sopenharmony_ci} 105e41f4b71Sopenharmony_ci 106e41f4b71Sopenharmony_ci@Entry 107e41f4b71Sopenharmony_ci@Preview 108e41f4b71Sopenharmony_ci@Component 109e41f4b71Sopenharmony_cistruct SwiperExample { 110e41f4b71Sopenharmony_ci private dataSrc: MyDataSource = new MyDataSource([]); 111e41f4b71Sopenharmony_ci 112e41f4b71Sopenharmony_ci aboutToAppear(): void { 113e41f4b71Sopenharmony_ci let list: Array<number> = [] 114e41f4b71Sopenharmony_ci for (let i = 1; i <= 10; i++) { 115e41f4b71Sopenharmony_ci list.push(i); 116e41f4b71Sopenharmony_ci } 117e41f4b71Sopenharmony_ci this.dataSrc = new MyDataSource(list); 118e41f4b71Sopenharmony_ci } 119e41f4b71Sopenharmony_ci 120e41f4b71Sopenharmony_ci build() { 121e41f4b71Sopenharmony_ci Column({ space: 5 }) { 122e41f4b71Sopenharmony_ci Swiper() { 123e41f4b71Sopenharmony_ci LazyForEach(this.dataSrc, (_: number) => { 124e41f4b71Sopenharmony_ci SwiperChildPage(); 125e41f4b71Sopenharmony_ci }, (item: number) => item.toString()); 126e41f4b71Sopenharmony_ci } 127e41f4b71Sopenharmony_ci .loop(false) 128e41f4b71Sopenharmony_ci .cachedCount(1) // 提前加载后一项的内容 129e41f4b71Sopenharmony_ci .indicator(true) 130e41f4b71Sopenharmony_ci .duration(100) 131e41f4b71Sopenharmony_ci .displayArrow({ 132e41f4b71Sopenharmony_ci showBackground: true, 133e41f4b71Sopenharmony_ci isSidebarMiddle: true, 134e41f4b71Sopenharmony_ci backgroundSize: 40, 135e41f4b71Sopenharmony_ci backgroundColor: Color.Orange, 136e41f4b71Sopenharmony_ci arrowSize: 25, 137e41f4b71Sopenharmony_ci arrowColor: Color.Black 138e41f4b71Sopenharmony_ci }, false) 139e41f4b71Sopenharmony_ci .curve(Curve.Linear) 140e41f4b71Sopenharmony_ci 141e41f4b71Sopenharmony_ci }.width('100%') 142e41f4b71Sopenharmony_ci .margin({ top: 5 }) 143e41f4b71Sopenharmony_ci } 144e41f4b71Sopenharmony_ci} 145e41f4b71Sopenharmony_ci 146e41f4b71Sopenharmony_ci``` 147e41f4b71Sopenharmony_ci 148e41f4b71Sopenharmony_ci## 验证效果 149e41f4b71Sopenharmony_ci 150e41f4b71Sopenharmony_ci为了更好地体现 Swiper 预加载机制带来的性能优化效果,用例采用下列前置条件: 151e41f4b71Sopenharmony_ci 152e41f4b71Sopenharmony_ci- Swiper 的子组件为带有 100 个 ListItem 的 List 组件; 153e41f4b71Sopenharmony_ci 154e41f4b71Sopenharmony_ci- Swiper 组件共有 10 个 List 子组件。 155e41f4b71Sopenharmony_ci 156e41f4b71Sopenharmony_ci在该场景下,使用 Swiper 预加载机制可以为每个翻页动作节省约40%的时间,同时保证翻页时不丢帧,保证翻页的流畅度。 157e41f4b71Sopenharmony_ci 158e41f4b71Sopenharmony_ci## 优化建议 159e41f4b71Sopenharmony_ci 160e41f4b71Sopenharmony_ci由于组件构建和布局计算需要一定时间,cachedCount 的数量也不是设置得越大越好,过大的 cachedCount 可能会导致应用性能降低。当前 Swiper 组件滑动离手后的动效时间大约是 400ms,如果应用加载一个子组件的时间在 100ms\~200ms 之间,为了在离手动效时间内完成组件的预加载,cachedCount 属性建议设置为 1 或 2,设置过大会导致主线程阻塞而产生卡顿。 161e41f4b71Sopenharmony_ci 162e41f4b71Sopenharmony_ci那么方案可以继续优化,在抛滑场景时,Swiper 组件有一个[OnAnimationStart](../reference/apis-arkui/arkui-ts/ts-container-swiper.md#事件)回调接口,切换动画开始时触发该回调。此时,主线程空闲,应用可以充分利用这段时间进行图片等资源的预加载,减少后续 cachedCount 范围内的节点预加载耗时; 163e41f4b71Sopenharmony_ci跟手滑动阶段不会触发[OnAnimationStart](../reference/apis-arkui/arkui-ts/ts-container-swiper.md#事件)回调,只有在离手后做切换动画(也就是抛滑阶段)才会触发。 164e41f4b71Sopenharmony_ci 165e41f4b71Sopenharmony_ci**示例** 166e41f4b71Sopenharmony_ci 167e41f4b71Sopenharmony_ciSwiper 子组件页面代码如下: 168e41f4b71Sopenharmony_ci 169e41f4b71Sopenharmony_ci在子组件首次构建(生命周期执行到[aboutToAppear](../quick-start/arkts-page-custom-components-lifecycle.md))时,先判断 dataSource 中该 index 的数据是否有数据,若无数据则先进行资源加载,再构建节点。若有数据,则直接构建节点即可。 170e41f4b71Sopenharmony_ci 171e41f4b71Sopenharmony_ci```TypeScript 172e41f4b71Sopenharmony_ciimport image from '@ohos.multimedia.image'; 173e41f4b71Sopenharmony_ciimport { MyDataSource } from './Index'; 174e41f4b71Sopenharmony_ci 175e41f4b71Sopenharmony_ci@Component 176e41f4b71Sopenharmony_ciexport struct PhotoItem { //Swiper的子组件 177e41f4b71Sopenharmony_ci myIndex: number = 0; 178e41f4b71Sopenharmony_ci private dataSource: MyDataSource = new MyDataSource([]); 179e41f4b71Sopenharmony_ci context = getContext(this); 180e41f4b71Sopenharmony_ci @State imageContent: image.PixelMap | undefined = undefined; 181e41f4b71Sopenharmony_ci 182e41f4b71Sopenharmony_ci aboutToAppear(): void { 183e41f4b71Sopenharmony_ci console.info(`aboutToAppear` + this.myIndex); 184e41f4b71Sopenharmony_ci this.imageContent = this.dataSource.getData(this.myIndex)?.image; 185e41f4b71Sopenharmony_ci if (!this.imageContent) { // 先判断dataSource中该index的数据是否有数据,若无数据则先进行资源加载 186e41f4b71Sopenharmony_ci try { 187e41f4b71Sopenharmony_ci // 获取resourceManager资源管理器 188e41f4b71Sopenharmony_ci const resourceMgr = this.context.resourceManager; 189e41f4b71Sopenharmony_ci // 获取rawfile文件夹下item.jpg的ArrayBuffer 190e41f4b71Sopenharmony_ci let str = "item" + (this.myIndex + 1) + ".jpg"; 191e41f4b71Sopenharmony_ci resourceMgr.getRawFileContent(str).then((value) => { 192e41f4b71Sopenharmony_ci // 创建imageSource 193e41f4b71Sopenharmony_ci const imageSource = image.createImageSource(value.buffer); 194e41f4b71Sopenharmony_ci imageSource.createPixelMap().then((value) => { 195e41f4b71Sopenharmony_ci console.info("aboutToAppear push" + this.myIndex) 196e41f4b71Sopenharmony_ci this.dataSource.addData(this.myIndex, { description: "" + this.myIndex, image: value }) 197e41f4b71Sopenharmony_ci this.imageContent = value; 198e41f4b71Sopenharmony_ci }) 199e41f4b71Sopenharmony_ci }) 200e41f4b71Sopenharmony_ci } catch (err) { 201e41f4b71Sopenharmony_ci console.error("error code" + err); 202e41f4b71Sopenharmony_ci } 203e41f4b71Sopenharmony_ci } 204e41f4b71Sopenharmony_ci } 205e41f4b71Sopenharmony_ci 206e41f4b71Sopenharmony_ci build() { 207e41f4b71Sopenharmony_ci Column() { 208e41f4b71Sopenharmony_ci Image(this.imageContent) 209e41f4b71Sopenharmony_ci .width("100%") 210e41f4b71Sopenharmony_ci .height("100%") 211e41f4b71Sopenharmony_ci } 212e41f4b71Sopenharmony_ci } 213e41f4b71Sopenharmony_ci} 214e41f4b71Sopenharmony_ci``` 215e41f4b71Sopenharmony_ci 216e41f4b71Sopenharmony_ciSwiper 主页面的代码如下: 217e41f4b71Sopenharmony_ci```TypeScript 218e41f4b71Sopenharmony_ciimport Curves from '@ohos.curves'; 219e41f4b71Sopenharmony_ciimport { PhotoItem } from './PhotoItem' 220e41f4b71Sopenharmony_ciimport image from '@ohos.multimedia.image'; 221e41f4b71Sopenharmony_ci 222e41f4b71Sopenharmony_ciinterface MyObject { 223e41f4b71Sopenharmony_ci description: string, 224e41f4b71Sopenharmony_ci image: image.PixelMap, 225e41f4b71Sopenharmony_ci}; 226e41f4b71Sopenharmony_ci 227e41f4b71Sopenharmony_ciexport class MyDataSource implements IDataSource { 228e41f4b71Sopenharmony_ci private list: MyObject[] = [] 229e41f4b71Sopenharmony_ci 230e41f4b71Sopenharmony_ci constructor(list: MyObject[]) { 231e41f4b71Sopenharmony_ci this.list = list 232e41f4b71Sopenharmony_ci } 233e41f4b71Sopenharmony_ci 234e41f4b71Sopenharmony_ci totalCount(): number { 235e41f4b71Sopenharmony_ci return this.list.length 236e41f4b71Sopenharmony_ci } 237e41f4b71Sopenharmony_ci 238e41f4b71Sopenharmony_ci getData(index: number): MyObject { 239e41f4b71Sopenharmony_ci return this.list[index] 240e41f4b71Sopenharmony_ci } 241e41f4b71Sopenharmony_ci 242e41f4b71Sopenharmony_ci registerDataChangeListener(listener: DataChangeListener): void { 243e41f4b71Sopenharmony_ci } 244e41f4b71Sopenharmony_ci 245e41f4b71Sopenharmony_ci unregisterDataChangeListener(listener: DataChangeListener): void { 246e41f4b71Sopenharmony_ci } 247e41f4b71Sopenharmony_ci 248e41f4b71Sopenharmony_ci addData(index: number, data: MyObject) { 249e41f4b71Sopenharmony_ci this.list[index] = data; 250e41f4b71Sopenharmony_ci } 251e41f4b71Sopenharmony_ci} 252e41f4b71Sopenharmony_ci 253e41f4b71Sopenharmony_ci@Entry 254e41f4b71Sopenharmony_ci@Component 255e41f4b71Sopenharmony_cistruct Index { 256e41f4b71Sopenharmony_ci @State currentIndex: number = 0; 257e41f4b71Sopenharmony_ci cacheCount: number = 1 258e41f4b71Sopenharmony_ci swiperController: SwiperController = new SwiperController(); 259e41f4b71Sopenharmony_ci private data: MyDataSource = new MyDataSource([]); 260e41f4b71Sopenharmony_ci context = getContext(this); 261e41f4b71Sopenharmony_ci 262e41f4b71Sopenharmony_ci aboutToAppear() { 263e41f4b71Sopenharmony_ci let list: MyObject[] = [] 264e41f4b71Sopenharmony_ci for (let i = 0; i < 6; i++) { 265e41f4b71Sopenharmony_ci list.push({ description: "", image: this.data.getData(this.currentIndex)?.image }) 266e41f4b71Sopenharmony_ci } 267e41f4b71Sopenharmony_ci this.data = new MyDataSource(list) 268e41f4b71Sopenharmony_ci } 269e41f4b71Sopenharmony_ci 270e41f4b71Sopenharmony_ci build() { 271e41f4b71Sopenharmony_ci Swiper(this.swiperController) { 272e41f4b71Sopenharmony_ci LazyForEach(this.data, (item: MyObject, index?: number) => { 273e41f4b71Sopenharmony_ci PhotoItem({ 274e41f4b71Sopenharmony_ci myIndex: index, 275e41f4b71Sopenharmony_ci dataSource: this.data 276e41f4b71Sopenharmony_ci }) 277e41f4b71Sopenharmony_ci }) 278e41f4b71Sopenharmony_ci } 279e41f4b71Sopenharmony_ci .cachedCount(this.cacheCount) 280e41f4b71Sopenharmony_ci .curve(Curves.interpolatingSpring(0, 1, 228, 30)) 281e41f4b71Sopenharmony_ci .index(this.currentIndex) 282e41f4b71Sopenharmony_ci .indicator(true) 283e41f4b71Sopenharmony_ci .loop(false) 284e41f4b71Sopenharmony_ci // 在OnAnimationStart接口回调中进行预加载资源的操作 285e41f4b71Sopenharmony_ci .onAnimationStart((index: number, targetIndex: number) => { 286e41f4b71Sopenharmony_ci console.info("onAnimationStart " + index + " " + targetIndex); 287e41f4b71Sopenharmony_ci if (targetIndex !== index) { 288e41f4b71Sopenharmony_ci try { 289e41f4b71Sopenharmony_ci // 获取resourceManager资源管理器 290e41f4b71Sopenharmony_ci const resourceMgr = this.context.resourceManager; 291e41f4b71Sopenharmony_ci // 获取rawfile文件夹下item.jpg的ArrayBuffer 292e41f4b71Sopenharmony_ci let str = "item" + (targetIndex + this.cacheCount + 2) + ".jpg"; 293e41f4b71Sopenharmony_ci resourceMgr.getRawFileContent(str).then((value) => { 294e41f4b71Sopenharmony_ci // 创建imageSource 295e41f4b71Sopenharmony_ci const imageSource = image.createImageSource(value.buffer); 296e41f4b71Sopenharmony_ci imageSource.createPixelMap().then((value) => { 297e41f4b71Sopenharmony_ci this.data.addData(targetIndex + this.cacheCount + 1, { 298e41f4b71Sopenharmony_ci description: "" + (targetIndex + this.cacheCount + 1), 299e41f4b71Sopenharmony_ci image: value 300e41f4b71Sopenharmony_ci }) 301e41f4b71Sopenharmony_ci }) 302e41f4b71Sopenharmony_ci }) 303e41f4b71Sopenharmony_ci } catch (err) { 304e41f4b71Sopenharmony_ci console.error("error code" + err); 305e41f4b71Sopenharmony_ci } 306e41f4b71Sopenharmony_ci } 307e41f4b71Sopenharmony_ci }) 308e41f4b71Sopenharmony_ci .width('100%') 309e41f4b71Sopenharmony_ci .height('100%') 310e41f4b71Sopenharmony_ci } 311e41f4b71Sopenharmony_ci} 312e41f4b71Sopenharmony_ci 313e41f4b71Sopenharmony_ci``` 314e41f4b71Sopenharmony_ci 315e41f4b71Sopenharmony_ci## 总结 316e41f4b71Sopenharmony_ci 317e41f4b71Sopenharmony_ci- Swiper 组件的预加载机制与 LazyForEach 结合使用,能够达到最佳优化效果。 318e41f4b71Sopenharmony_ci 319e41f4b71Sopenharmony_ci- 预加载的 cachedCount 并非越大越好,需要结合单个子组件加载耗时来设置。假设一个子组件的加载耗时为 Nms,那么 cachedCount 推荐设置为小于 400/N。 320e41f4b71Sopenharmony_ci 321e41f4b71Sopenharmony_ci- 如果应用有非常高的性能优化需求,Swiper 预加载机制可搭配 OnAnimationStart 接口回调使用,进一步提升预加载的效率。 322