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 414 415## 补充说明 416 417开发者也可使用OpenHarmony三方库[@netteam/prefetcher](https://ohpm.openharmony.cn/#/cn/detail/@netteam%2Fprefetcher)开发预加载功能。该三方库提供了更多的接口,可以更加便捷有效地实现数据预加载。