Flutter筆記——幀繪製系列之一(原始碼學習)

悟篤篤發表於2020-01-23

Flutter系列學習筆記

前言

前兩篇文章Flutter筆記——runApp發生了什麼(原始碼學習)Flutter筆記——State.setState發生了什麼學習了Flutter中runApp()、修改UI元素State.setState()過程。
這篇文章主要學習的是Flutter中實際渲染UI的過程。

1 BaseBinding

BaseBinding系列是FlutterFramework的核心類,學習Flutter的UI渲染過程會涉及到WidgetsBindingRenderBindingSchedulerBinding等。由於Dart的mixIn菱形繼承語法,該部分比較難搞明白,只能從區域性入手,抽絲剝繭般的去學習理解整體流程。

1.1 handleDrawFrame

在我的Flutter筆記——runApp發生了什麼(原始碼學習)文章中,瞭解到WidgetsFlutterBinding.scheduleWarmUpFrame()函式用於排程展示一個預熱幀。而WidgetsFlutterBinding.scheduleAttachRootWidget(Widget rootWidget)函式使用Timer包裹,作為一個非同步執行函式,在它執行完畢之時最終會呼叫WidgetsBinding.handleDrawFrame()函式繪製幀。
那麼handleDrawFrame()函式到底發生了什麼?

mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  void handleDrawFrame() {
    assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
    Timeline.finishSync(); // end the "Animate" phase
    try {
      // PERSISTENT FRAME CALLBACKS
      _schedulerPhase = SchedulerPhase.persistentCallbacks;
      for (FrameCallback callback in _persistentCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);

      // POST-FRAME CALLBACKS
      _schedulerPhase = SchedulerPhase.postFrameCallbacks;
      final List<FrameCallback> localPostFrameCallbacks =
          List<FrameCallback>.from(_postFrameCallbacks);
      _postFrameCallbacks.clear();
      for (FrameCallback callback in localPostFrameCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);
    } finally {
      _schedulerPhase = SchedulerPhase.idle;
      Timeline.finishSync(); // end the Frame
      assert(() {
        if (debugPrintEndFrameBanner)
          debugPrint('▀' * _debugBanner.length);
        _debugBanner = null;
        return true;
      }());
      _currentFrameTimeStamp = null;
    }
  }
}

複製程式碼

首先學習WidgetsBinding類,見註釋

Scheduler for running the following:
- Transient callbacks, triggered by the system's [Window.onBeginFrame] callback, for synchronizing the application's behavior to the system's display. For example, [Ticker]s and [AnimationController]s trigger from these.
- Persistent callbacks, triggered by the system's [Window.onDrawFrame] callback, for updating the system's display after transient callbacks have executed. For example, the rendering layer uses this to drive its rendering pipeline.
- Post-frame callbacks, which are run after persistent callbacks, just before returning from the [Window.onDrawFrame] callback.
- Non-rendering tasks, to be run between frames. These are given a priority and are executed in priority order according to a [schedulingStrategy]

簡單理解下,該類主要作用就是排程幀渲染任務,當然也可以執行非渲染任務。主要是瞬間渲染、持久渲染與渲染回撥任務等,例如持久的幀渲染監聽註冊WidgetsBinding.instance.addPersistentFrameCallback(callback)就是該類的作用了。
回到handleDrawFrame()函式,這裡面迴圈執行SchedulerBinding._persistentCallbacksSchedulerBinding._postFrameCallbacks的註冊回撥之外,好像沒做其他事情哦?那麼線索斷了嗎?

事情並不簡單.jpg

1.2 initInstances

這裡吐槽下mixIn菱形繼承,這個語法特性真的香嗎?

這裡把眼光回到BaseBinding系列的初始化函式中,我們可以在RendererBinding.initInstances()函式中,找到SchedulerBinding.addPersistentFrameCallback(FrameCallback callback)函式的呼叫,這意味著在RendererBinding.initInstances()初始化階段,已經註冊了一個關鍵函式,噔噔瞪,見下面原始碼

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    _pipelineOwner = PipelineOwner(
      onNeedVisualUpdate: ensureVisualUpdate,
      onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
      onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
    );
    window
      ..onMetricsChanged = handleMetricsChanged
      ..onTextScaleFactorChanged = handleTextScaleFactorChanged
      ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
      ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
      ..onSemanticsAction = _handleSemanticsAction;
    initRenderView();
    _handleSemanticsEnabledChanged();
    assert(renderView != null);
    //重點
    addPersistentFrameCallback(_handlePersistentFrameCallback);
    initMouseTracker();
  }
  //重點
  void _handlePersistentFrameCallback(Duration timeStamp) {
    drawFrame();
  }
}
複製程式碼

我們可以看到,在SchedulerBinding._persistentCallbacks已經註冊了drawFrame函式回撥,到了這裡handleDrawFrame渲染幀的線索又接上了,接著往下看。

1.3 drawFrame

drawFrame()函式有2處實現(有一處Test環境,忽略),並且都被WidgetsFlutterBinding繼承,這個mixIn真的香嗎?

香嗎.jpg

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  void drawFrame() {
    assert(renderView != null);
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    pipelineOwner.flushPaint();
    renderView.compositeFrame(); // this sends the bits to the GPU
    pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
  }
}
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  @override
  void drawFrame() {
    assert(!debugBuildingDirtyElements);
    assert(() {
      debugBuildingDirtyElements = true;
      return true;
    }());

    if (_needToReportFirstFrame && _reportFirstFrame) {
      assert(!_firstFrameCompleter.isCompleted);

      TimingsCallback firstFrameCallback;
      firstFrameCallback = (List<FrameTiming> timings) {
        if (!kReleaseMode) {
          developer.Timeline.instantSync('Rasterized first useful frame');
          developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
        }
        SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback);
        _firstFrameCompleter.complete();
      };
      SchedulerBinding.instance.addTimingsCallback(firstFrameCallback);
    }

    try {
      if (renderViewElement != null)
        buildOwner.buildScope(renderViewElement);
      super.drawFrame();
      buildOwner.finalizeTree();
    } finally {
      assert(() {
        debugBuildingDirtyElements = false;
        return true;
      }());
    }
    if (!kReleaseMode) {
      if (_needToReportFirstFrame && _reportFirstFrame) {
        developer.Timeline.instantSync('Widgets built first useful frame');
      }
    }
    _needToReportFirstFrame = false;
  }
}
複製程式碼

在1.2中,我們知道drawFrame在每個handleDrawFrame函式中都會被呼叫,我們的WidgetsFlutterBinding繼承自RendererBindingWidgetsBinding,見下圖的順序看看drawFrame到底發生了什麼,再進行原始碼追蹤

drawFrame.png

過程比較複雜,原始碼學習按照序列圖中的順序來

  1. WidgetsBinding.drawFrame()該函式在每一次handleDrawFrame都會被呼叫,並且還會呼叫super.drawFrame函式
    ///虛擬碼
    mixin WidgetsBinding ...{
      ///忽略斷言和除錯部分程式碼
      @override
      void drawFrame() {
        try {
          ///如果renderViewElement不為空,呼叫BuildOwner.buildScope函式,生成WidgetTree更新域
          if (renderViewElement != null){
            buildOwner.buildScope(renderViewElement);
          }
          //呼叫RenderBinding.drawFrame函式
          super.drawFrame();
          //
          buildOwner.finalizeTree();
        } finally {
          assert(() {
            debugBuildingDirtyElements = false;
            return true;
          }());
        }
        if (!kReleaseMode) {
          if (_needToReportFirstFrame && _reportFirstFrame) {
            developer.Timeline.instantSync('Widgets built first useful frame');
          }
        }
        _needToReportFirstFrame = false;
      }
    }
    複製程式碼
  2. buildOwner.buildScope(renderViewElement):這裡的renderViewElement是一個RenderObjectToWidgetElement<RenderBox>物件,在runApp(Widget app)函式中被初始化,不瞭解的請看我的這篇文章Flutter筆記——runApp發生了什麼(原始碼學習)
    buildOwner.buildScope(renderViewElement)函式的作用是建立WidgetTree構建的域。
      ///刪除斷言和callback相關程式碼
      void buildScope(Element context, [ VoidCallback callback ]) {
        Timeline.startSync('Build', arguments: timelineWhitelistArguments);
        try{
          _dirtyElements.sort(Element._sort);
          _dirtyElementsNeedsResorting = false;
          int dirtyCount = _dirtyElements.length;
          int index = 0;
          while (index < dirtyCount) {
            try {
              _dirtyElements[index].rebuild();
            } catch (e, stack) {
              _debugReportException(
                ErrorDescription('while rebuilding dirty elements'),
                e,
                stack,
                informationCollector: () sync* {
                  yield DiagnosticsDebugCreator(DebugCreator(_dirtyElements[index]));
                  yield _dirtyElements[index].describeElement('The element being rebuilt at the time was index $index of $dirtyCount');
                },
              );
            }
            index += 1;
            if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {
              _dirtyElements.sort(Element._sort);
              _dirtyElementsNeedsResorting = false;
              dirtyCount = _dirtyElements.length;
              while (index > 0 && _dirtyElements[index - 1].dirty) {
                index -= 1;
              }
            }
          }
        } finally {
          for (Element element in _dirtyElements) {
            element._inDirtyList = false;
          }
          _dirtyElements.clear();
          _scheduledFlushDirtyElements = false;
          _dirtyElementsNeedsResorting = null;
          Timeline.finishSync();
        }
      }
    複製程式碼
  3. _dirtyElements.sort(Element._sort):排列Element,根據Element中的depth值,depth值是當期Element所在樹的層次整數。每個Elementdepth值都大於ParentElementdepth
      static int _sort(Element a, Element b) {
        if (a.depth < b.depth)
          return -1;
        if (b.depth < a.depth)
          return 1;
        if (b.dirty && !a.dirty)
          return -1;
        if (a.dirty && !b.dirty)
          return 1;
        return 0;
      }
    複製程式碼
  4. _dirtyElements[index].rebuild():遍歷_dirtyElements容器中的元素,呼叫它們的rebuild()函式。
  5. element.rebuild():這裡以ComponentElement作為示例,rebuild()函式原始碼如下
      void rebuild() {
        ///刪除很多斷言和其他程式碼
        performRebuild();
      }
    複製程式碼
  6. ComponentElement.performRebuild():在這裡我們可以看到performRebuild()函式會呼叫Element中的build()函式,這對於我們應該是最熟悉的Flutter程式碼之一了。這裡面的built = build()有幾個繼承,StatefulWidget通過createState()函式生成State,再通過Statebuild():Widget函式生成Widget。
      @override
      void performRebuild() {
        ///刪除很多斷言和其他程式碼
        Widget built;
        try {
          built = build();
          debugWidgetBuilderValue(widget, built);
        } catch (e, stack) {
          built = ErrorWidget.builder(
            _debugReportException(
              ErrorDescription('building $this'),
              e,
              stack,
              informationCollector: () sync* {
                yield DiagnosticsDebugCreator(DebugCreator(this));
              },
            ),
          );
        } finally {
          _dirty = false;
        }
        try {
          _child = updateChild(_child, built, slot);
          assert(_child != null);
        } catch (e, stack) {
          built = ErrorWidget.builder(
            _debugReportException(
              ErrorDescription('building $this'),
              e,
              stack,
              informationCollector: () sync* {
                yield DiagnosticsDebugCreator(DebugCreator(this));
              },
            ),
          );
          _child = updateChild(null, built, slot);
        }
      }
    複製程式碼
  7. updateChild(Element child, Widget newWidget, dynamic newSlot):更新Element中的Widget物件,這裡面有三個引數,第一個是之前的Widget物件,也就是類物件child。第二個是新生成的newWidget物件,由build()函式生成,第三個newSlot是父Element給與子Element的位置引數,如果slot位置發生了變化,即使childnewWidget相同,也會重新渲染。
      @protected
      Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
        if (newWidget == null) {
          if (child != null)
            deactivateChild(child);
          return null;
        }
        if (child != null) {
          if (child.widget == newWidget) {
            if (child.slot != newSlot)
              updateSlotForChild(child, newSlot);
            return child;
          }
          if (Widget.canUpdate(child.widget, newWidget)) {
            if (child.slot != newSlot)
              updateSlotForChild(child, newSlot);
            child.update(newWidget);
            assert(child.widget == newWidget);
            assert(() {
              child.owner._debugElementWasRebuilt(child);
              return true;
            }());
            return child;
          }
          deactivateChild(child);
          assert(child._parent == null);
        }
        return inflateWidget(newWidget, newSlot);
      }
    複製程式碼
  8. Element inflateWidget(Widget newWidget, dynamic newSlot)根據給定的WidgetnewSlot生成一個Element,該方法通常由updateChild()函式直接呼叫。如果該Widget生成Element已經存在或者存在相同的GlobalKey將會複用。該函式還會呼叫Widget.canUpdate(Widget oldWidget, Widget newWidget)來比較Widget物件是否相同。 該部分原始碼較長,在之後文章看是否記錄學習,這裡知道其作用即可。
    • 如果newWidget的key是GlobalKey,並且通過Element _retakeInactiveElement(GlobalKey key, Widget newWidget)能拿回來一個Element,那麼在更新狀態與slot、配置之後便返回一個Element
    • 不能從key中拿回已有的Element,會呼叫Element newChild = newWidget.createElement()生成一個新的newChild,並掛載它newChild.mount(this, newSlot)並返回。
  9. super.drawFrame():也就是RenderBinding.drawFrame()函式,該函式涉及知識點較多,下篇文章學習。它主要涉及到了RenderObjectRectPipelineOwner等知識點。
  10. buildOwner.finalizeTree():呼叫該函式來完成元素構建。

2 小結

  1. 本篇文章從預熱幀WidgetsFlutterBinding.scheduleWarmUpFrame()函式入手,找到FlutterFramework渲染幀的過程函式handleDrawFrame(),再通過BaseBinding系列找到drawFrame()的持久監聽與回撥來學習幀繪製的部分內容。
  2. 本文從Elementcreateupdate中,也找到了State.setState時,有些UI元素沒有重繪的根本原因,也瞭解了key的作用。
  3. BaseBinding中的WidgetsBindingRenderBindingSchedulerBinding等子類是FlutterFramework幀渲染的核心類。本文從drawFrame入手學習了部分內容,另外BuildOwner全域性管理類也要著重瞭解。
  4. 本文篇章有限,還有許多內容沒有學習到,等下篇文章再著重學習RenderBinding.drawFrame()的作用,之後再做一個階段性總結。

謝謝閱讀,如有錯誤勞煩指出糾正,十分感謝,新春快樂哦!

相關文章