• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# @ohos.arkui.Prefetcher (Prefetching)
2<!--Kit: ArkUI-->
3<!--Subsystem: ArkUI-->
4<!--Owner: @maorh-->
5<!--Designer: @lixingchi1-->
6<!--Tester: @TerryTsao-->
7<!--Adviser: @HelloCrease-->
8
9配合LazyForEach,为List、Grid、WaterFlow和Swiper等容器组件滑动浏览时提供内容预加载能力,提升用户浏览体验。
10
11>  **说明:**
12>
13>  本模块首批接口从API version 12开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。
14
15## 导入模块
16
17```ts
18import { BasicPrefetcher, IDataSourcePrefetching, IPrefetcher } from '@kit.ArkUI';
19```
20
21
22## IPrefetcher
23实现此接口以提供预取能力。
24
25**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
26
27**系统能力:** SystemCapability.ArkUI.ArkUI.Full
28
29### setDataSource
30setDataSource(dataSource: IDataSourcePrefetching): void;
31
32设置支持预取的DataSource以绑定到Prefetcher。
33
34**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
35
36**系统能力:** SystemCapability.ArkUI.ArkUI.Full
37
38**参数:**
39
40| 参数名        | 类型                                                | 必填 | 说明         |
41|------------|---------------------------------------------------|----|------------|
42| dataSource | [IDataSourcePrefetching](#idatasourceprefetching) | 是  | 支持预取能力的数据源。 |
43
44```typescript
45class MyPrefetcher implements IPrefetcher {
46  private dataSource?: IDataSourcePrefetching;
47
48  setDataSource(dataSource: IDataSourcePrefetching): void {
49    this.dataSource = dataSource;
50  }
51
52  visibleAreaChanged(minVisible: number, maxVisible: number): void {
53    this.dataSource?.prefetch(minVisible);
54  }
55}
56```
57
58### visibleAreaChanged
59visibleAreaChanged(minVisible: number, maxVisible: number): void;
60
61当可见区域边界发生改变时调用此方法。支持与`List`、`Grid`、`WaterFlow`和`Swiper`组件配合使用。
62
63**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
64
65**系统能力:** SystemCapability.ArkUI.ArkUI.Full
66
67**参数:**
68
69| 参数名        | 类型     | 必填 | 说明        |
70|------------|--------|----|-----------|
71| minVisible | number | 是  | 列表可见区域的上界。 |
72| maxVisible | number | 是  | 列表可见区域的下界。 |
73
74```typescript
75class MyPrefetcher implements IPrefetcher {
76  private dataSource?: IDataSourcePrefetching;
77
78  setDataSource(dataSource: IDataSourcePrefetching): void {
79    this.dataSource = dataSource;
80  }
81
82  visibleAreaChanged(minVisible: number, maxVisible: number): void {
83    this.dataSource?.prefetch(minVisible);
84  }
85}
86```
87
88## BasicPrefetcher
89BasicPrefetcher是IPrefetcher的基础实现。它提供了一种智能数据预取算法,以根据屏幕上可见区域的实时变化和预取持续时间的变化来决定应预取哪些数据项。它还可以根据用户的滚动操作来确定哪些预取请求应该被取消。
90
91**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
92
93**系统能力:** SystemCapability.ArkUI.ArkUI.Full
94
95### constructor
96constructor(dataSource?: IDataSourcePrefetching);
97
98传入支持预取的DataSource以绑定到Prefetcher。
99
100**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
101
102**系统能力:** SystemCapability.ArkUI.ArkUI.Full
103
104**参数:**
105
106| 参数名        | 类型                                                | 必填 | 说明         |
107|------------|---------------------------------------------------|----|------------|
108| dataSource | [IDataSourcePrefetching](#idatasourceprefetching) | 否  | 支持预取能力的数据源。 |
109
110### setDataSource
111setDataSource(dataSource: IDataSourcePrefetching): void;
112
113设置支持预取的DataSource以绑定到Prefetcher。
114
115**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
116
117**系统能力:** SystemCapability.ArkUI.ArkUI.Full
118
119**参数:**
120
121| 参数名        | 类型                                                | 必填 | 说明         |
122|------------|---------------------------------------------------|----|------------|
123| dataSource | [IDataSourcePrefetching](#idatasourceprefetching) | 是  | 支持预取能力的数据源。 |
124
125### visibleAreaChanged
126visibleAreaChanged(minVisible: number, maxVisible: number): void;
127
128当可见区域边界发生改变时调用此方法。支持与`List`、`Grid`、`WaterFlow`和`Swiper`组件配合使用。
129
130**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
131
132**系统能力:** SystemCapability.ArkUI.ArkUI.Full
133
134**参数:**
135
136| 参数名        | 类型     | 必填 | 说明        |
137|------------|--------|----|-----------|
138| minVisible | number | 是  | 列表可见区域的上界。 |
139| maxVisible | number | 是  | 列表可见区域的下界。 |
140
141## IDataSourcePrefetching
142
143继承自[IDataSource](./arkui-ts/ts-rendering-control-lazyforeach.md#idatasource)。实现该接口,提供具备预取能力的DataSource。
144
145**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
146
147**系统能力:** SystemCapability.ArkUI.ArkUI.Full
148
149### prefetch
150prefetch(index: number): Promise\<void\> | void;
151
152从数据集中预取指定的元素。该方法可以为同步,也可为异步。
153
154**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
155
156**系统能力:** SystemCapability.ArkUI.ArkUI.Full
157
158**参数:**
159
160| 参数名   | 类型     | 必填 | 说明       |
161|-------|--------|----|----------|
162| index | number | 是  | 预取数据项索引值。 |
163
164**返回值:**
165
166| 类型 | 说明 |
167| ----------------------- | -------- |
168| Promise\<void\> \| void | 异步执行时返回Promise对象,同步执行时无返回值。Promise仅表示操作完成,无实际返回内容。 |
169
170### cancel
171cancel?(index: number): Promise\<void\> | void;
172
173取消从数据集中预取指定的元素。该方法可以为同步,也可为异步。
174
175**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
176
177**系统能力:** SystemCapability.ArkUI.ArkUI.Full
178
179**参数:**
180
181| 参数名   | 类型     | 必填 | 说明         |
182|-------|--------|----|------------|
183| index | number | 是  | 取消预取数据项索引值。 |
184
185**返回值:**
186
187| 类型 | 说明 |
188| ----------------------- | -------- |
189| Promise\<void\> \| void | 异步执行时返回Promise对象,同步执行时无返回值。Promise仅表示操作完成,无实际返回内容。 |
190
191列表内容移出屏幕时(比如列表快速滑动场景下),预取算法判断屏幕以外的Item可以被取消预取时,该方法即会被调用。例如,如果HTTP框架支持请求取消,则可以在此处取消在prefetch中发起的网络请求。
192
193## 示例
194
195下面示例展示了Prefetcher的预加载能力。该示例采用分页的方式,配合LazyForEach实现懒加载效果,并通过添加延时来模拟加载过程。
196
197```typescript
198import { BasicPrefetcher, IDataSourcePrefetching } from '@kit.ArkUI';
199import { image } from '@kit.ImageKit';
200
201const ITEMS_ON_SCREEN = 8;
202
203@Entry
204@Component
205struct PrefetcherDemoComponent {
206  private page: number = 1;
207  private pageSize: number = 50;
208  private breakPoint: number = 25;
209  private readonly fetchDelayMs: number = 500;
210  private readonly dataSource = new MyDataSource(this.page, this.pageSize, this.fetchDelayMs);
211  private readonly prefetcher = new BasicPrefetcher(this.dataSource);
212
213  build() {
214    Column() {
215      List() {
216        LazyForEach(this.dataSource, (item: PictureItem, index: number) => {
217          ListItem() {
218            PictureItemComponent({ info: item })
219              .height(`${100 / ITEMS_ON_SCREEN}%`)
220          }
221          .onAppear(() => {
222            if (index >= this.breakPoint) {
223              this.dataSource.getHttpData(++this.page, this.pageSize);
224              this.breakPoint = this.dataSource.totalCount() - this.pageSize / 2;
225            }
226          })
227        }, (item: PictureItem) => item.title)
228      }
229      .onScrollIndex((start: number, end: number) => {
230        this.prefetcher.visibleAreaChanged(start, end);
231      })
232    }
233  }
234}
235
236@Component
237struct PictureItemComponent {
238  @ObjectLink info: PictureItem;
239
240  build() {
241    Row() {
242      Image(this.info.imagePixelMap)
243        .objectFit(ImageFit.Contain)
244        .width('40%')
245      Text(this.info.title)
246        .width('60%')
247    }
248  }
249}
250
251@Observed
252class PictureItem {
253  readonly color: number;
254  title: string;
255  imagePixelMap: image.PixelMap | undefined;
256  key: string;
257
258  constructor(color: number, title: string) {
259    this.color = color;
260    this.title = title;
261    this.key = title;
262  }
263}
264
265type ItemIndex = number;
266type TimerId = number;
267
268class MyDataSource implements IDataSourcePrefetching {
269  private readonly items: PictureItem[];
270  private readonly fetchDelayMs: number;
271  private readonly fetches: Map<ItemIndex, TimerId> = new Map();
272  private readonly listeners: DataChangeListener[] = [];
273
274  constructor(pageNum: number, pageSize: number, fetchDelayMs: number) {
275    this.items = [];
276    this.fetchDelayMs = fetchDelayMs;
277    this.getHttpData(pageNum, pageSize);
278  }
279
280  async prefetch(index: number): Promise<void> {
281    const item = this.items[index];
282    if (item.imagePixelMap) {
283      return;
284    }
285
286    // 模拟高耗时操作
287    return new Promise<void>(resolve => {
288      const timeoutId = setTimeout(async () => {
289        this.fetches.delete(index);
290        const bitmap = create10x10Bitmap(item.color);
291        const imageSource: image.ImageSource = image.createImageSource(bitmap);
292        item.imagePixelMap = await imageSource.createPixelMap();
293        resolve();
294      }, this.fetchDelayMs);
295
296      this.fetches.set(index, timeoutId)
297    });
298  }
299
300  cancel(index: number): void {
301    const timerId = this.fetches.get(index);
302    if (timerId) {
303      this.fetches.delete(index);
304      clearTimeout(timerId);
305    }
306  }
307
308  // 模拟分页方式加载数据
309  getHttpData(pageNum: number, pageSize:number): void {
310    const newItems: PictureItem[] = [];
311    for (let i = (pageNum - 1) * pageSize; i < pageNum * pageSize; i++) {
312      const item = new PictureItem(getRandomColor(), `Item ${i}`);
313      newItems.push(item);
314    }
315    const startIndex = this.items.length;
316    this.items.splice(startIndex, 0, ...newItems);
317    this.notifyBatchUpdate([
318      {
319        type: DataOperationType.ADD,
320        index: startIndex,
321        count: newItems.length,
322        key: newItems.map((item) => item.title)
323      }
324    ]);
325  }
326
327  private notifyBatchUpdate(operations: DataOperation[]) {
328    this.listeners.forEach((listener: DataChangeListener) => {
329      listener.onDatasetChange(operations);
330    });
331  }
332
333  totalCount(): number {
334    return this.items.length;
335  }
336
337  getData(index: number): PictureItem {
338    return this.items[index];
339  }
340
341  registerDataChangeListener(listener: DataChangeListener): void {
342    if (this.listeners.indexOf(listener) < 0) {
343      this.listeners.push(listener);
344    }
345  }
346
347  unregisterDataChangeListener(listener: DataChangeListener): void {
348    const pos = this.listeners.indexOf(listener);
349    if (pos >= 0) {
350      this.listeners.splice(pos, 1);
351    }
352  }
353}
354
355function getRandomColor(): number {
356  const maxColorCode = 256;
357  const r = Math.floor(Math.random() * maxColorCode);
358  const g = Math.floor(Math.random() * maxColorCode);
359  const b = Math.floor(Math.random() * maxColorCode);
360
361  return (r * 256 + g) * 256 + b;
362}
363
364function create10x10Bitmap(color: number): ArrayBuffer {
365  const height = 10;
366  const width = 10;
367
368  const fileHeaderLength = 14;
369  const bitmapInfoLength = 40;
370  const headerLength = fileHeaderLength + bitmapInfoLength;
371  const pixelSize = (width * 3 + 2) * height;
372
373  let length = pixelSize + headerLength;
374
375  const buffer = new ArrayBuffer(length);
376  const view16 = new Uint16Array(buffer);
377
378  view16[0] = 0x4D42;
379  view16[1] = length & 0xffff;
380  view16[2] = length >> 16;
381  view16[5] = headerLength;
382
383  let offset = 7;
384  view16[offset++] = bitmapInfoLength & 0xffff;
385  view16[offset++] = bitmapInfoLength >> 16;
386  view16[offset++] = width & 0xffff;
387  view16[offset++] = width >> 16;
388  view16[offset++] = height & 0xffff;
389  view16[offset++] = height >> 16;
390  view16[offset++] = 1;
391  view16[offset++] = 24;
392
393  const b = color & 0xff;
394  const g = (color >> 8) & 0xff;
395  const r = color >> 16;
396  offset = headerLength;
397  const view8 = new Uint8Array(buffer);
398  for (let y = 0; y < height; y++) {
399    for (let x = 0; x < width; x++) {
400      view8[offset++] = b;
401      view8[offset++] = g;
402      view8[offset++] = r;
403    }
404    offset += 2;
405  }
406
407  return buffer;
408}
409```
410
411演示效果如下:
412
413![Prefetcher-Demo](./figures/prefetcher-demo.gif)
414
415## 补充说明
416
417开发者也可使用OpenHarmony三方库[@netteam/prefetcher](https://ohpm.openharmony.cn/#/cn/detail/@netteam%2Fprefetcher)开发预加载功能。该三方库提供了更多的接口,可以更加便捷有效地实现数据预加载。