目录

页面向上滑动不流畅,到达底部时频繁等待加载

问题现象

浏览应用内页面时,频繁出现滑动到底后需要等待加载的情况,使用体验不流畅。

背景知识

问题定位

  1. 使用ArkUI Inspector可以看到相关页面是通过WaterFlow瀑布流实现的。

  2. 使用SmartPerf打开Trace文件,在应用包名主泳道找到H:APP_LIST_FLING子泳道,通过泳道中的Trace点可以得知抛滑动作全过程。
  3. 在抛滑动作结束后可以在应用包名主线程看到有明显耗时异常的Trace点,根据Duration字段可得知单帧耗时54ms左右。
  4. 框选异常Trace点,在Slices中可以看到这一帧内创建FlowItemView和LazyItem的数量较多。

  5. 在左上角搜索框中分别搜索FlowItemView和LazyItem,可以看到H:CustomNode:BuildItem [FlowItemView]有15次,共耗时18.633ms;H:Builder:BuildLazyItem有23次,共耗时14.311ms。因此是瀑布流无限滚动至末尾时触发的onReachEnd事件回调中对LazyForEach增加新数据耗时导致整体加载耗时。

    如下示例代码实现瀑布流的无限滚动会出现滑动至底部停顿加载的现象。

     class WaterFlowDataSource implements IDataSource {
       private dataArray: number[] = []
       private listeners: DataChangeListener[] = []
    
       constructor() {
         for (let i = 0; i < 100; i++) {
           this.dataArray.push(i)
         }
       }
    
       public getData(index: number): number {
         return this.dataArray[index]
       }
    
       notifyDataReload(): void {
         this.listeners.forEach(listener => {
           listener.onDataReloaded()
         })
       }
    
       notifyDataAdd(index: number): void {
         this.listeners.forEach(listener => {
           listener.onDataAdd(index)
         })
       }
    
       notifyDataChange(index: number): void {
         this.listeners.forEach(listener => {
           listener.onDataChange(index)
         })
       }
    
       notifyDataDelete(index: number): void {
         this.listeners.forEach(listener => {
           listener.onDataDelete(index)
         })
       }
    
       notifyDataMove(from: number, to: number): void {
         this.listeners.forEach(listener => {
           listener.onDataMove(from, to)
         })
       }
    
       public totalCount(): number {
         return this.dataArray.length
       }
    
       registerDataChangeListener(listener: DataChangeListener): void {
         if (this.listeners.indexOf(listener) < 0) {
           this.listeners.push(listener)
         }
       }
    
       unregisterDataChangeListener(listener: DataChangeListener): void {
         const pos = this.listeners.indexOf(listener)
         if (pos >= 0) {
           this.listeners.splice(pos, 1)
         }
       }
    
       public addNewItems(count: number): void {
         let len = this.dataArray.length;
         for (let i = 0; i < count; i++) {
           this.dataArray.push(this.dataArray.length);
         }
         this.listeners.forEach(listener => {
           listener.onDatasetChange([{ type: DataOperationType.ADD, index: len, count: count }]);
         })
       }
     }
    
     @Reusable
     @Component
     struct ReusableFlowItem {
       @State item: number = 0;
    
       build() {
         Text(this.item + 1 + '')
           .fontWeight(FontWeight.Regular)
           .fontSize(20)
           .fontColor(Color.Black)
       }
     }
    
     @Entry
     @Component
     export struct WaterFlowPage {
       private dataSource: WaterFlowDataSource = new WaterFlowDataSource();
       scroller: Scroller = new Scroller();
    
       @Builder
       itemFoot() {
         Row() {
           LoadingProgress()
             .color(Color.Blue).height(50).aspectRatio(1).width('20%')
           Text(`正在加载`)
             .fontSize(20)
             .width('30%')
             .height(50)
             .align(Alignment.Center)
             .margin({ top: 2 })
         }.width('100%').justifyContent(FlexAlign.Center)
       }
    
       build() {
         Column({ space: 2 }) {
           WaterFlow({ footer: this.itemFoot(), layoutMode: WaterFlowLayoutMode.SLIDING_WINDOW }) {
             LazyForEach(this.dataSource, (item: number) => {
               FlowItem() {
                 ReusableFlowItem({ item: item })
               }
               .width('100%')
               .height(200)
               .border({
                 width: 1,
                 color: Color.Black
               })
             }, (item: string) => item)
           }
           .columnsTemplate('1fr '.repeat(3))
           .backgroundColor(Color.White)
           .width('100%')
           .height('100%')
           .layoutWeight(1)
           // 触底加载数据
           .onReachEnd(() => {
             setTimeout(() => {
               this.dataSource.addNewItems(100);
             }, 500);
           })
         }
       }
     }

分析结论

瀑布流无限滚动至末尾时新增组件耗时导致整体加载耗时。

修改建议

调整增加新数据的时机,在LazyForEach还剩余若干个数据未遍历的情况下提前新增数据。