天天看點

淘特 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))