天天看点

淘特 Flutter 流式场景的深度优化一、前言二、渲染机制三、性能优化思路四、流式场景五、优化成果六、总结七、参考资料

一、前言

    淘特在很多业务场景都使用了 Flutter,加上业务场景本身具有一定的复杂性,使得 Flutter 在低端机流式场景的滑动浏览过程中卡顿、跳帧对比使用原生(Android/iOS)开发明显。通过分析业务层在 Flutter 渲染流程中的每个阶段存在的性能问题进行了一系列的深度优化后,平均帧率已经达到50帧之上超越了原生的表现, 但卡顿率依然达不到最佳的体验效果,遇到了难以突破的瓶颈和技术挑战,需要进行技术尝试和突破。

    本文会从底层原理、优化思路、实际场景的优化策略、核心技术实现、优化成果、总结和参考资料进行讲述,期望可以为大家带来一定的启发和帮助,也欢迎多多交流与指正,共建美好的 Flutter 技术社区。

二、渲染机制

原生 vs Flutter

Flutter 本身是基于原生系统之上的,所以渲染机制和 Native 是非常接近的,引用 Google Flutter 团队 Xiao Yu分享[1],如下图所示:

淘特 Flutter 流式场景的深度优化一、前言二、渲染机制三、性能优化思路四、流式场景五、优化成果六、总结七、参考资料

渲染流程

淘特 Flutter 流式场景的深度优化一、前言二、渲染机制三、性能优化思路四、流式场景五、优化成果六、总结七、参考资料

如图左中,Flutter 从接收到 VSync 信号之后整体经历 8 个阶段,其中 Compositing 阶段后会将数据提交给 GPU。

Semantics 阶段会将

RenderObject

marked 需要做语义化更新的信息传递给系统,实现辅助功能,通过语义化接口可以帮助有视力障碍的用户来理解UI内容,和整体绘制流程关联不大。

Finalize Tree 阶段会将所有添加到

_inactiveElements

的不活跃

Element

全部 unmount 掉,和整体绘制流程关联不大。

所以,Flutter 整体渲染流程主要关注 图右 中的阶段:

GPU Vsync

  • Flutter Engine 在收到垂直同步信号后,会通知 Flutter Framework 进行 beginFrame,进入 Animation 阶段。

Animation

  • 主要执行了

    transientCallbacks

    回调。

Flutter Engine 会通知 Flutter Framework 进行 drawFrame,进入 Build 阶段

Build

  • 构建要呈现的UI组件树的数据结构,即创建对应的

    Widget

    以及对应的

    Element

Layout

  • 目的是要计算出每个节点所占空间的真实大小进行布局;
  • 然后更新所有 dirty render objects 的布局信息。

Compositing Bits

  • 对需要更新的

    RenderObject

    进行 update 操作;

Paint

  • 生成 Layer Tree,生成 Layer Tree 并不能直接使用,还需要 Compositing 合成为一个 Scene 并进行 Rasterize 光栅化处理。层级合并的原因是因为一般 Flutter 的层级很多,直接把每一层传递给 GPU 效率很低,所以会先做 Composite 提高效率。光栅化之后才会交给 Flutter Engine 处理。

Compositing

  • 将 Layout Tree 合成为 Scene,并创建场景当前状态的栅格图像,即进行 Rasterize 光栅化处理,然后提交给 Flutter Engine,最后 Skia 通过 Open GL or Vulkan 接口提交数据给 GPU, GPU经过处理后进行显示。

核心渲染阶段

淘特 Flutter 流式场景的深度优化一、前言二、渲染机制三、性能优化思路四、流式场景五、优化成果六、总结七、参考资料
  • Widget

    我们平时在写的大都是

    Widget

    Widget

    其实可以理解为是一个组件树的数据结构,是 Build 阶段的主要部分。其中 Widget Tree 的深度、

    StatefulWidget

    setState

    合理性、build 函数中是否有不合理逻辑以及使用了调用

    saveLayer

    的相关Widget往往会成为性能问题。
  • Element

    关联

    Widget

    RenderObject

    ,生成

    Widget

    对应的

    Element

    存放上下文信息,Flutter 通过遍历

    Element

    来生成

    RenderObject

    视图树支撑UI结构;
  • RenderObject

    RenderObject 在 Layout 阶段确定布局信息,Paint 阶段生成为对应的 Layer,可见其重要程度。所以 Flutter 中大部分的绘图性能优化发生在这里。RenderObject 树构建的数据会被加入到 Engine 所需的 LayerTree 中。

三、性能优化思路

了解底层渲染机制和核心渲染阶段,可以将优化分为三层:

淘特 Flutter 流式场景的深度优化一、前言二、渲染机制三、性能优化思路四、流式场景五、优化成果六、总结七、参考资料

这里不具体展开讲每一层的优化细节,本文主要从实际的场景来讲述。

四、流式场景

流式组件原理

在原生开发下,通常使用

RecyclerView/UICollectionView

进行列表场景的开发;在Flutter开发下,Flutter Framework 也提供了ListView的组件,它的实质其实是

SliverList

核心源码

我们从

SliverList

的核心源码来进行分析:

class SliverList extends SliverMultiBoxAdaptorWidget {

  @override
  RenderSliverList createRenderObject(BuildContext context) {
    final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement;
    return RenderSliverList(childManager: element);
  }
}

abstract class SliverMultiBoxAdaptorWidget extends SliverWithKeepAliveWidget {

  final SliverChildDelegate delegate;

  @override
  SliverMultiBoxAdaptorElement createElement() => SliverMultiBoxAdaptorElement(this);

  @override
  RenderSliverMultiBoxAdaptor createRenderObject(BuildContext context);
}           

通过查看

SliverList

的源代码可知,

SliverList

是一个

RenderObjectWidget

,结构如下:

淘特 Flutter 流式场景的深度优化一、前言二、渲染机制三、性能优化思路四、流式场景五、优化成果六、总结七、参考资料

我们首先看它的

RenderObject

的核心源码:

class RenderSliverList extends RenderSliverMultiBoxAdaptor {
  
  RenderSliverList({
    @required RenderSliverBoxChildManager childManager,
  }) : super(childManager: childManager);
  
  @override
  void performLayout(){
    ...
    //父节点对子节点的布局限制
    final SliverConstraints constraints = this.constraints;
    final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin;
    final double remainingExtent = constraints.remainingCacheExtent;
    final double targetEndScrollOffset = scrollOffset + remainingExtent;
    final BoxConstraints childConstraints = constraints.asBoxConstraints();
    ...
    insertAndLayoutLeadingChild(childConstraints, parentUsesSize: true);
    ...
    insertAndLayoutChild(childConstraints,after: trailingChildWithLayout,parentUsesSize: true);
    ...
    collectGarbage(leadingGarbage, trailingGarbage);
    ...
  }
}

abstract class RenderSliverMultiBoxAdaptor extends RenderSliver ...{
  @protected
  RenderBox insertAndLayoutChild(BoxConstraints childConstraints, {@required RenderBox after,...}) {
    _createOrObtainChild(index, after: after);
    ...
  }
  
  RenderBox insertAndLayoutLeadingChild(BoxConstraints childConstraints, {@required RenderBox after,...}) {
    _createOrObtainChild(index, after: after);
    ...
  }
  
  @protected
  void collectGarbage(int leadingGarbage, int trailingGarbage) {
    _destroyOrCacheChild(firstChild);
    ...
  }
  
  void _createOrObtainChild(int index, { RenderBox after }) {
    _childManager.createChild(index, after: after);
    ...
  }
  
  void _destroyOrCacheChild(RenderBox child) {
    if (childParentData.keepAlive) {
      //为了更好的性能表现不会进行keepAlive,走else逻辑.
      ...
    } else {
      _childManager.removeChild(child);
      ...
    }
  }
}           

查看

RenderSliverList

的源码发现,对于 child 的创建和移除都是通过其父类

RenderSliverMultiBoxAdaptor

进行。而

RenderSliverMultiBoxAdaptor

是通过

_childManager

SliverMultiBoxAdaptorElement

进行的,整个

SliverList

绘制过程中布局大小由父节点给出了限制。

在流式场景下:

  • 在滑动过程中是通过

    SliverMultiBoxAdaptorElement.createChild

    进行对进入可视区新的 child 的创建;(即业务场景的每一个item卡片)
  • SliverMultiBoxAdaptorElement.removeChild

    进行对不在可视区旧的 child 的移除;

我们来看下

SliverMultiBoxAdaptorElement

class SliverMultiBoxAdaptorElement extends RenderObjectElement implements RenderSliverBoxChildManager {
  final SplayTreeMap<int, Element> _childElements = SplayTreeMap<int, Element>();

  @override
  void createChild(int index, { @required RenderBox after }) {
    ...
    Element newChild = updateChild(_childElements[index], _build(index), index);
    ...
  }
  
  @override
  void removeChild(RenderBox child) {
    ...
    final Element result = updateChild(_childElements[index], null, index);
    ...
  }
  
  @override
  Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    ...
    final Element newChild = super.updateChild(child, newWidget, newSlot);
    ...
  }
}           

SliverMultiBoxAdaptorElement

的源码可以发现,对于 child 的操作其实都是通过父类

Element

updateChild

进行的。

接下来,我们来看下

Element

的核心代码:

abstract class Element extends DiagnosticableTree implements BuildContext {
  @protected
  Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    if (newWidget == null) {
      if (child != null)
        deactivateChild(child);
      return null;
    }
    Element newChild;
    if (child != null) {
      ...
      bool hasSameSuperclass = oldElementClass == newWidgetClass;;
      if (hasSameSuperclass && child.widget == newWidget) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        newChild = child;
      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        child.update(newWidget);
        newChild = child;
      } else {
        deactivateChild(child);
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
      newChild = inflateWidget(newWidget, newSlot);
    }
    ...
    return newChild;
  }
  
  @protected
  Element inflateWidget(Widget newWidget, dynamic newSlot) {
    ...
    final Element newChild = newWidget.createElement();
    newChild.mount(this, newSlot);
    ...
    return newChild;
  }
  
  @protected
  void deactivateChild(Element child) {
    child._parent = null;
    child.detachRenderObject(); 
    owner._inactiveElements.add(child); // this eventually calls child.deactivate() & child.unmount()
    ...
  }
}           

可以看到主要调用

Element

mount

detachRenderObject

,这里我们来看下

RenderObjectElement

的 这两个方法的源码:

abstract class RenderObjectElement extends Element {
  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    ...
    _renderObject = widget.createRenderObject(this);
    attachRenderObject(newSlot);
    ...
  }
  
  @override
  void attachRenderObject(dynamic newSlot) {
    ...
    _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
    _ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
    ...
  }
  
  @override
  void detachRenderObject() {
    if (_ancestorRenderObjectElement != null) {
      _ancestorRenderObjectElement.removeChildRenderObject(renderObject);
      _ancestorRenderObjectElement = null;
    }
    ...
  }
}           

通过查看上面源码的追溯,可知:

  • 在滑动过程中进入可视区新的 child 的创建,是通过创建全新的

    Element

    并 mount 挂载到 Element Tree;然后创建对应的

    RenderObject

    ,调用了

    _ancestorRenderObjectElement?.insertChildRenderObject

  • 在滑动过程中不在可视区旧的 child 的移除,将对应的

    Element

    从 Element Tree unmount 移除挂载;然后调用了

    _ancestorRenderObjectElement.removeChildRenderObject

其实这个

_ancestorRenderObjectElement

就是

SliverMultiBoxAdaptorElement

,我们再来看下

SliverMultiBoxAdaptorElement

:

class SliverMultiBoxAdaptorElement extends RenderObjectElement implements RenderSliverBoxChildManager {
  
  @override
  void insertChildRenderObject(covariant RenderObject child, int slot) {
    ...
    renderObject.insert(child as RenderBox, after: _currentBeforeChild);
    ...
  }
  
  @override
  void removeChildRenderObject(covariant RenderObject child) {
    ...
    renderObject.remove(child as RenderBox);
  }
}           

其实调用的都是

ContainerRenderObjectMixin

的方法,我们再来看下

ContainerRenderObjectMixin

mixin ContainerRenderObjectMixin<ChildType extends RenderObject, ... {
  void insert(ChildType child, { ChildType after }) {
        ...
    adoptChild(child);// attach render object
    _insertIntoChildList(child, after: after);
  }
  
  void remove(ChildType child) {
    _removeFromChildList(child);
    dropChild(child);// detach render object
  }
}           

ContainerRenderObjectMixin

维护了一个双向链表来持有当前 children

RenderObject

,所以在滑动过程中创建和移除都会同步在

ContainerRenderObjectMixin

的双向链表中进行添加和移除。

最后总结下来:

  • Element

    RenderObject

    , 通过调用

    SliverMultiBoxAdaptorElement.insertChildRenderObject

    attach 到 Render Tree,并同步将

    RenderObject

    添加到

    SliverMultiBoxAdaptorElement

    所 mixin 的双链表中;
  • Element

    从 Element Tree unmount 移除挂载;然后通过用

    SliverMultiBoxAdaptorElement.removeChildRenderObject

    将对应的

    RenderObject

    从所 mixin 的双链表中移除并同步将

    RenderObject

    从 Render Tree detach 掉。

渲染原理

通过核心源码的分析,我们可以对流式场景的

Element

做如下分类:

淘特 Flutter 流式场景的深度优化一、前言二、渲染机制三、性能优化思路四、流式场景五、优化成果六、总结七、参考资料

下面我们来看用户向上滑动查看更多商品卡片并触发加载下一页数据进行展示时,整体的渲染流程和机制:

淘特 Flutter 流式场景的深度优化一、前言二、渲染机制三、性能优化思路四、流式场景五、优化成果六、总结七、参考资料
  • 向上滑动时,顶部 0 和 1 的卡片移出 Viewport 区域(Visible Area + Cache Area),我们定义它为进入 Detach Area,进入 Detach Area 后将对应的

    RenderObject

    从 Render Tree detach 掉,并且将对应的

    Element

    从 Element Tree unmount 移除挂载,并同步从双向链表中移除;
  • 通过监听

    ScrollController

    的滑动计算位置来判断是否需要开始加载下一页数据,然后底部 Loading Footer 组件会进入可视区 or 缓存区,需要对

    SliverChildBuilderDelegate

    的 childCount +1,最后一个 child 返回 Loading Footer 组件,同时调用

    setState

    对整个

    SliverList

    刷新。

    update

    会调用

    performRebuild

    进行重构建,中间部分在用户可视区会全部进行 update 操作;然后创建 Loading Footer 组件对应新的

    Element

    RenderObject

    ,并同步添加到双向链表中;
  • 当 loading 结束数据返回后,会再次调用

    setState

    SliverList

    刷新,

    update

    performRebuild

    进行重构建,中间部分在用户可视区会全部进行 update 操作;然后将 Loading Footer 组件将对应的

    RenderObject

    Element

  • 底部新的 item 会进入可视区 or 缓存区,需要创建对应新的

    Element

    RenderObject

优化策略

上面用户向上滑动查看更多商品卡片并触发加载下一页数据进行展示的场景,可以从五个方向进行优化:

  • Load More

    ScrollController

    的滑动不断进行计算,最好无需判断,自动识别到需要加载下一页数据然后发起 loadMore() 回调。新建

    ReuseSliverChildBuilderDelegate

    增加 loadMore 以及和 item Builder 同级的 footerBuilder,并默认包含 Loading Footer 组件,在

    SliverMultiBoxAdaptorElement.createChild(int index,...)

    判断是否需要动态回调 loadMore() 并自动构建 footer 组件。
  • 局部刷新

    参考了闲鱼之前在长列表的流畅度优化[2],在下一页数据回来之后调用

    setState

    SliverList

    刷新,导致中间部分在用户可视区会全部进行 update 操作,实际只需刷新新创建的部分,优化

    SliverMultiBoxAdaptorElement.update(SliverMultiBoxAdaptorWidget newWidget)

    的部分实现局部刷新,如下图:
淘特 Flutter 流式场景的深度优化一、前言二、渲染机制三、性能优化思路四、流式场景五、优化成果六、总结七、参考资料
  • Element & RenderObject 复用

    参考了闲鱼之前在长列表的流畅度优化[2] 和 Google Android RecyclerView ViewHolder 复用设计[3],在有新的 item 创建时,可以做类似 Android

    RecyclerView

    ViewHolder

    对组件进行持有并复用。基于对渲染机制原理分析,在 Flutter 中

    Widget

    其实可以理解为是一个组件树的数据结构,即更多是组件结构的数据表达。我们需要对移除的 item 的

    Element

    RenderObject

    分组件类型进行缓存持有,在创建新的 item 的时候优先从缓存持有中取出进行复用。同时不破坏 Flutter 本身对

    Key

    的设计,当如果 item 有使用

    Key

    的时候,只复用和它

    Key

    相同的

    Element

    RenderObject

    。但在流式场景列表数据都是不同的数据,所以在流式场景中使用了

    Key

    ,也就无法进行任何的复用。如果对

    Element

    RenderObject

    进行复用,item 组件不建议使用

    Key

    我们在对原有流式场景下

    Element

    的分类增加一个缓存态:
淘特 Flutter 流式场景的深度优化一、前言二、渲染机制三、性能优化思路四、流式场景五、优化成果六、总结七、参考资料

如下图:

淘特 Flutter 流式场景的深度优化一、前言二、渲染机制三、性能优化思路四、流式场景五、优化成果六、总结七、参考资料
  • GC 抑制

    Dart 自身有 GC 的机制,类似 Java 的分代回收,可以在滑动的过程中对 GC 进行抑制,定制 GC 回收的算法。针对这项和 Google 的 Flutter 专家讨论,其实 Dart 不像 Java 会存在多线程切换进行垃圾回收的情况,单线程(主isolate)垃圾回收更快更轻量级,同时需要对 Flutter Engine 做深度的改造,考虑收益不大暂不进行。

  • 异步化

    Flutter Engine 限制非 Main Isolate 调用 Platform 相关 Api,将非跟 Platform Thread 交互的逻辑全部放至新的 isolate 中,频繁

    Isolate

    的创建和回收也会对性能有一定的影响,Flutter

    compute<Q, R>(isolates.ComputeCallback<Q, R> callback, Q message, { String debugLabel })

    每次调用会创建新的

    Isolate

    ,执行完任务后会进行回收,实现一个类似线程池的

    Isolate

    来进行处理非视图任务。经过实际测试提升不明显,不展开讲述。

核心技术实现

​我们可以将调用链路的代码做如下分类:

淘特 Flutter 流式场景的深度优化一、前言二、渲染机制三、性能优化思路四、流式场景五、优化成果六、总结七、参考资料

所有渲染核心在继承自

RenderObjectElement

SliverMultiBoxAdaptorElement

中,不破坏原有功能设计以及 Flutter Framework 的结构,新增了

ReuseSliverMultiBoxAdaptorElement

Element

来进行优化策略的实现,并且可以直接搭配原有

SliverList

RenderSliverList

使用或者自定义的流式组件(例如:瀑布流组件)的

RenderObject

使用。

  • 调用链路优化

ReuseSliverMultiBoxAdaptorElement

update

方法做是否为局部刷新的判断,如果不是局部刷新依然走

performRebuild

;如果是局部刷新,只创建新产生的 item。

淘特 Flutter 流式场景的深度优化一、前言二、渲染机制三、性能优化思路四、流式场景五、优化成果六、总结七、参考资料
  • 核心代码
@override
  void update(covariant ReuseSliverMultiBoxAdaptorWidget newWidget) {
    ...
    //是否进行局部刷新
    if(_isPartialRefresh(oldDelegate, newDelegate)) {
        ...
        Widget newWidget = _buildItem(index);
        ...
        _createChild(index, newWidget);
      } else {
         // need to rebuild
         performRebuild();
      }
  }           

    • 创建

ReuseSliverMultiBoxAdaptorElement

createChild

方法读取

_cacheElements

对应组件类型缓存的

Element

进行复用;如果没有同类型可复用的

Element

则创建对应新的

Element

RenderObject

  • 移除

ReuseSliverMultiBoxAdaptorElement

removeChild

方法将移除的

RenderObject

从双链表中移除,不进行

Element

的 deactive 和

RenderObject

的 detach,并将对应的

Element

_slot

更新为null,使下次可以正常复用,然后将对应的

Element

缓存到

_cacheElements

对应组件类型的链表中。

注:不 deactive

Element

其实不进行调用即可实现,但不 detach RenderObject 无法直接做到,需要在 Flutter Framework 层的

object.dart

文件中,新增一个方法

removeOnly

就是只将

RenderObject

从双链表中移除不进行 detach。

淘特 Flutter 流式场景的深度优化一、前言二、渲染机制三、性能优化思路四、流式场景五、优化成果六、总结七、参考资料
//新增的方法,createChild会调用到这个方法
  _createChild(int index, Widget newWidget){
      ...
      Type delegateChildRuntimeType = _getWidgetRuntimeType(newWidget);
      child = _takeChild(delegateChildRuntimeType,index);
      ...
      newChild = updateChild(child, newWidget, index);
      ...
  }           
@override
  void removeChild(RenderBox child) {
     ...
     removeChildRenderObject(child); // call removeOnly
     ...
     removeElement = _childElements.remove(index);
     _performCacheElement(removeElement);
 }           

createChild

时候判断是否是构建 footer 来进行处理。

淘特 Flutter 流式场景的深度优化一、前言二、渲染机制三、性能优化思路四、流式场景五、优化成果六、总结七、参考资料
@override
  void createChild(int index, { @required RenderBox after }) {
      ...
      Widget newWidget;
      if(_isBuildFooter(index)){ // call footerBuilder & call onLoadMore
        newWidget = _buildFooter();
      }else{
        newWidget = _buildItem(index);
      }
      ...
      _createChild(index, newWidget);
      ...
  }           

整体结构设计

  • 将核心的优化能力内聚在

    Element

    层,提供底层能力;
  • ReuseSliverMultiBoxAdaptorWidget

    做为基类默认返回优化后的

    Element

  • 将 loadMore 和 FooterBuilder 的能力统一由继承自

    SliverChildBuilderDelegate

    ReuseSliverChildBuilderDelegate

    对上层暴露;
淘特 Flutter 流式场景的深度优化一、前言二、渲染机制三、性能优化思路四、流式场景五、优化成果六、总结七、参考资料
  • 如有自己单独定制的流式组件

    Widget

    ,直接把继承关系从

    RenderObjectWidget

    换为

    ReuseSliverMultiBoxAdaptorWidget

    即可,例如自定义的单列表组件(ReuseSliverList)、瀑布流组件(ReuseWaterFall)等。
淘特 Flutter 流式场景的深度优化一、前言二、渲染机制三、性能优化思路四、流式场景五、优化成果六、总结七、参考资料

五、优化成果

基于在之前的一系列深度优化以及切换 Flutter Engine 为UC Hummer 之上,单独控制流式场景的优化变量,使用 PerfDog 获取流畅度数据,进行了流畅度测试对比:

淘特 Flutter 流式场景的深度优化一、前言二、渲染机制三、性能优化思路四、流式场景五、优化成果六、总结七、参考资料
淘特 Flutter 流式场景的深度优化一、前言二、渲染机制三、性能优化思路四、流式场景五、优化成果六、总结七、参考资料

可以看到整体性能数据都有优化提升,结合替换 Engine 之前的测试数据平均来看,对帧率有 2-3 帧的提升,卡顿率下降 1.5 个百分点。

六、总结

使用方式

和原生

SliverList

的使用方式一样,

Widget

换成对应可以进行复用的组件 (ReuseSliverList/ReuseWaterFall/ CustomSliverList),delegate 如果需要

footer

loadMore

使用

ReuseSliverChildBuilderDelegate

;如果不需要直接使用原生的

SliverChildBuilderDelegate

即可。

  • 需要分页场景
return ReuseSliverList( // ReuseWaterFall or CustomSliverList
  delegate: ReuseSliverChildBuilderDelegate(
    (BuildContext context, int index) {
      return getItemWidget(index);
    }, 
    //构建footer
    footerBuilder: (BuildContext context) {
      return DetailMiniFootWidget();
    },
    //添加loadMore监听
    addUnderFlowListener: loadMore,
    childCount: dataOfWidgetList.length
  )
);           
  • 无需分页场景
return ReuseSliverList( // ReuseWaterFall or CustomSliverList
  delegate: SliverChildBuilderDelegate(
    (BuildContext context, int index) {
      return getItemWidget(index);
    }, 
    childCount: dataOfWidgetList.length
  )
);           

注意点

使用的时候 item/footer 组件不要加

Key

,否则认为只对同

Key

进行复用。因为复用了

Element

,虽然表达组件树数据结果的

Widget

会每次进行更新,但

StatefulElement

State

是在

Element

创建的时候生成的,同时也会被复用下来,和 Flutter 本身设计保持一致,所以需要在

didUpdateWidget(covariant T oldWidget)

State

缓存的数据重新从

Widget

获取即可。

Reuse Element Lifecycle

将每个 item 的状态进行回调,上层可以做逻辑处理和资源释放等,例如之前在

didUpdateWidget(covariant T oldWidget)

State

Widget

获取可以放置在

onDisappear

里或者自动播放的视频流等;

/// 复用的生命周期
mixin ReuseSliverLifeCycle{

  // 前台可见的
  void onAppear() {}

  // 后台不可见的
  void onDisappear() {}
}           

七、参考资料

[[1]:Google Flutter 团队 Xiao Yu:Flutter Performance Profiling and Theory](

https://files.flutter-io.cn/events/gdd2018/Profiling_your_Flutter_Apps.pdf)

[[2]:闲鱼 云从:他把闲鱼APP长列表流畅度翻了倍](

https://mp.weixin.qq.com/s/dlOQ3Hw_U3CFQM91vcTGWQ)

[[3]:Google Android RecyclerView.ViewHolder:RecyclerView.Adapter#onCreateViewHolder](

https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.Adapter#onCreateViewHolder(android.view.ViewGroup,%20int))