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 ![loop=false](figures/swiper_loop_false.png)
39e41f4b71Sopenharmony_ci
40e41f4b71Sopenharmony_ci\
41e41f4b71Sopenharmony_ci Swiper 共 5 页,当开发者设置了 cacheCount 属性为 1 且 loop 属性为 true 时,预加载的结果如下:\
42e41f4b71Sopenharmony_ci ![loop=true](figures/swiper_loop_true.png)
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