注:本文代码基于Flutter SDK 3.13.5

一、前言

在上一文解读Flutter源码之StatefulWidget&State的生命周期中,笔者分析了State生命周期的相关方法,但碍于篇幅有限,对于setState方法只是进行了简单描述,还未进行深入分析。

因此,本文将会深入分析setState方法的源码。

二、setState方法的特性

在分析setState源码之前,一起再回顾下setState方法的一些特性:

1、setState方法用于通知框架该对象的内部状态已更改

2、setState方法提供的回调是同步调用,它不能返回future(也就是不能用async),因为如果是异步调用的话,就会不清楚状态何时实际被设置

3、通常,建议setState方法仅用于包装对状态的实际更改,而不是与更改相关的任何计算。例如,这里build方法使用的_counter被递增,然后更改被写入磁盘,但只有_counter++被包装在setState方法中。

1
2
3
4
5
6
7
8
Future<void> _incrementCounter() async {
  setState(() {
    _counter++;
  });
  Directory directory = await getApplicationDocumentsDirectory(); // from path_provider package
  final String dirName = directory.path;
  await File('$dirName/counter.txt').writeAsString('$_counter');
}

4、在框架调用dispose之后调用setState方法是错误的,可以通过检查mounted属性是否为true来判断调用该方法是否合法。

三、分析setState方法的源码

setState方法在Flutter中属于用得比较频繁的一个方法,当页面UI状态需要发生改变时,调用setState方法就可以触发页面UI状态的更新,那么setState方法是如何做到的,一起看下它的源码:

可以看到,在setState方法中,调用了外部传入的回调实例fn,类型为VoidCallback。疑问:为什么要将fn的返回值强转为Object?因为需要在断言中判断fn的返回值类型是否是Future,如果是则抛出异常。

然后执行了_elementmarkNeedsBuild方法,这里的_element指的是当前State实例所持有的StatefulElement引用。当然了,StatefulElementComponentElement内部都没有markNeedsBuild方法,所以执行的是ElementmarkNeedsBuild方法。

先看下markNeedsBuild方法的注释。

1
2
3
4
5
6
7
8
9
/// 将element标记为脏并将其添加到widgets的全局列表中以在下一帧中重建
/// Marks the element as dirty and adds it to the global list of widgets to
/// rebuild in the next frame.
///
/// 由于在一帧中两次构建一个element是低效的,应用程序和widgets应该被构造为仅在帧开始之前的事件处理程序期间而不是在构建本身期间将widgets标记为脏。
/// Since it is inefficient to build an element twice in one frame,
/// applications and widgets should be structured so as to only mark
/// widgets dirty during event handlers before the frame begins, not during
/// the build itself.

再看下markNeedsBuild方法的源码。

markNeedsBuild方法中,可以注意到有一个成员变量dirty,如果dirtytrue则会return返回,之后的逻辑就不走了,先看下成员变量dirty的注释。

1
2
3
4
5
6
7
8
9
/// 如果element已被标记为需要重建,则返回true。
/// Returns true if the element has been marked as needing rebuilding.
///
/// 当element首次创建且调用markNeedsBuild之后,该标志为true。该标志在performRebuild实现中重置为false。
/// The flag is true when the element is first created and after
/// [markNeedsBuild] has been called. The flag is reset to false in the
/// [performRebuild] implementation.
bool get dirty => _dirty;
bool _dirty = true;

从成员变量dirty的注释中可以知道,如果一个element已被标记为需要重建,_dirty就会在markNeedsBuild方法中被标记为true,当执行ElementperformRebuild方法时,_dirty会重置为false

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/// 导致widget自行更新。
/// Cause the widget to update itself.
///
/// 进行适当的检查后由 [rebuild] 调用
/// Called by [rebuild] after the appropriate checks have been made.
///
/// 基本实现仅清除dirty标志。
/// The base implementation only clears the [dirty] flag.
@protected
@mustCallSuper
void performRebuild() {
  _dirty = false;
}

我们知道,如果执行到了performRebuild方法,意味着即将执行Statebuild方法。因此,在执行performRebuild方法之前,连续调用多次setState方法,第二次之后其实都是多余的,因为此时dirtytruereturn返回会忽略掉之后的逻辑,只有dirtyfalse时,调用setState方法才能触发页面UI状态的更新。

OK,继续看下owner!.scheduleBuildFor(this)这行代码,执行的是BuildOwnerscheduleBuildFor方法,传入this,表示当前需要重建的element引用,那么这里的owner是在哪里创建的呢?

1
2
3
4
5
/// 管理该element生命周期的对象
/// The object that manages the lifecycle of this element.
@override
BuildOwner? get owner => _owner;
BuildOwner? _owner;

关于BuildOwner的由来,在之前文章讲runApp分析中有提到,它是在七大binding之一WidgetsBindinginitInstances方法中进行BuildOwner初始化,执行的是BuildOwner构造方法。

然后在WidgetsBindingattachRootWidget方法中,将BuildOwner实例作为参数传入了刚创建的RenderObjectToWidgetAdapter实例的attachToRenderTree方法中。

然后在RenderObjectToWidgetAdapterattachToRenderTree方法中执行了createElement方法,创建了RenderObjectToWidgetElement实例,接着执行了该Element实例的assignOwner方法,传入了BuildOwner引用。

因为RenderObjectToWidgetElement没有assignOwner方法,所以执行的是混入的RootElementMixinassignOwner方法,然后将传入的BuildOwner引用赋值给RootElementMixin的父类Element的成员变量_owner,那么Element就持有了最初创建的BuildOwner引用。

那么为什么Element的子类可以获取到BuildOwner引用?那是因为在Elementmount方法中,对成员变量_owner进行了重新赋值,实际上就是将最初创建的BuildOwner引用一层一层往下传递给Element的子类了,所以Element的子类可以获取到BuildOwner引用。

OK,关于BuildOwner的由来已经知晓了,我们回到owner!.scheduleBuildFor(this)这行代码,这里执行了BuildOwnerscheduleBuildFor方法。

scheduleBuildFor方法中,涉及了四个变量:Element的成员变量_inDirtyListBuildOwner的成员变量_dirtyElementsNeedsResortingBuildOwner的成员变量_scheduledFlushDirtyElementsBuildOwner的成员变量_dirtyElements以及BuildOwner的成员变量onBuildScheduled,下面一一分析这五个变量。

  • Element的成员变量_inDirtyList
1
2
3
4
// 这是否在owner._dirtyElements中。这用于知道当element重新激活时我们是否应该将其添加回列表中。
// Whether this is in owner._dirtyElements. This is used to know whether we
// should be adding the element back into the list when it's reactivated.
bool _inDirtyList = false;

scheduleBuildFor方法中,如果该Element已经在_dirtyElements列表中,那么直接return返回,后续的逻辑不再执行;

scheduleBuildFor方法中,如果该Element不在_dirtyElements列表中,那么将该Element放入到_dirtyElements列表中,然后将_inDirtyList赋值为true

BuildOwnerbuildScope方法中,当处理完_dirtyElements列表中所有的Element后(所有Element均完成重建后),重新将所有Element_inDirtyList赋值为false。

  • BuildOwner的成员变量_dirtyElementsNeedsResorting
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/// 由于在构建过程中更多elements变脏,_dirtyElements是否需要再次排序
/// Whether [_dirtyElements] need to be sorted again as a result of more
/// elements becoming dirty during the build.
///
/// 这对于保留Element._sort定义的排序顺序是必要的
/// This is necessary to preserve the sort order defined by [Element._sort].
///
/// 当buildScope未主动重建widget树时,该字段设置为null。
/// This field is set to null when [buildScope] is not actively rebuilding
/// the widget tree.
bool? _dirtyElementsNeedsResorting;

scheduleBuildFor方法中,如果该Element已经在_dirtyElements列表中,那么_dirtyElementsNeedsResorting赋值为true,表明_dirtyElements中的Element需要再次排序;

其它情况_dirtyElementsNeedsResortingfalse,也就是不需要再次排序;

BuildOwnerbuildScope方法中,当处理完_dirtyElements列表中所有的Element后(所有Element均完成重建后),_dirtyElementsNeedsResorting会赋值为null

  • BuildOwner的成员变量_scheduledFlushDirtyElements

_scheduledFlushDirtyElements用来控制是否触发onBuildScheduled回调,相当于控制了是否执行_dirtyElements列表中所有Element的重建。

BuildOwnerbuildScope方法中,当处理完_dirtyElements列表中所有的Element后(所有Element均完成重建后),_scheduledFlushDirtyElements会赋值为false

  • BuildOwner的成员变量_dirtyElements

_dirtyElements列表用来添加需要重建的Element,后续会循环遍历该列表,执行所有Elementrebuild方法进行重建。

一般情况下,在你的State中点击按钮调用setState方法时,就会把当前State所持有的Element添加进该列表。但是,按钮Widget点击事件后面可能还有它自身的setState,这样就会产生多个需要重建的Element,它们也会被添加进该列表,所以就需要对_dirtyElements进行depth深度排序。

  • BuildOwner的成员变量onBuildScheduled
1
2
3
4
/// 当第一个可构建element被标记为脏时,在每个构建过程中调用
/// Called on each build pass when the first buildable element is marked
/// dirty.
VoidCallback? onBuildScheduled;

onBuildScheduled回调的赋值是在WidgetsBindinginitInstances方法中,执行了WidgetsBinding_handleBuildScheduled方法。

WidgetsBinding_handleBuildScheduled方法中,执行了SchedulerBindingensureVisualUpdate方法。

SchedulerBindingensureVisualUpdate方法中,一开始schedulerPhase的状态为SchedulerPhase.idle,所以会执行scheduleFrame方法安排新帧,从注释可以知道,调用此方法可确保最终调用handleDrawFrame方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/// 如果该对象当前未生成帧,则使用 [scheduleFrame] 安排新帧。
/// Schedules a new frame using [scheduleFrame] if this object is not
/// currently producing a frame.
///
/// 调用此方法可确保最终调用 [handleDrawFrame],除非它已经在进行中。
/// Calling this method ensures that [handleDrawFrame] will eventually be
/// called, unless it's already in progress.
///
/// 如果 [schedulerPhase] 是 [SchedulerPhase.transientCallbacks] 或 [SchedulerPhase.midFrameMicrotasks](因为在这种情况下已经准备好帧)
/// 或 [SchedulerPhase.persistentCallbacks](因为在这种情况下正在主动渲染帧),则这不起作用)。
/// 如果 [schedulerPhase] 是 [SchedulerPhase.idle](在帧之间)或 [SchedulerPhase.postFrameCallbacks](在帧之后),它将调度一个帧。
/// This has no effect if [schedulerPhase] is
/// [SchedulerPhase.transientCallbacks] or [SchedulerPhase.midFrameMicrotasks]
/// (because a frame is already being prepared in that case), or
/// [SchedulerPhase.persistentCallbacks] (because a frame is actively being
/// rendered in that case). It will schedule a frame if the [schedulerPhase]
/// is [SchedulerPhase.idle] (in between frames) or
/// [SchedulerPhase.postFrameCallbacks] (after a frame).
void ensureVisualUpdate() {
  switch (schedulerPhase) {
    case SchedulerPhase.idle:
    case SchedulerPhase.postFrameCallbacks:
      scheduleFrame();
      return;
    case SchedulerPhase.transientCallbacks:
    case SchedulerPhase.midFrameMicrotasks:
    case SchedulerPhase.persistentCallbacks:
      return;
  }
}

看下SchedulerPhase的几种状态。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/// SchedulerBinding在SchedulerBinding.handleBeginFrame期间经历的各个阶段。
/// The various phases that a [SchedulerBinding] goes through during
/// [SchedulerBinding.handleBeginFrame].
///
/// 这是由SchedulerBinding.schedulerPhase公开的
/// This is exposed by [SchedulerBinding.schedulerPhase].
///
/// 该枚举的值按照与阶段发生的顺序相同的顺序排序,因此可以将它们的相对索引值相互比较
/// The values of this enum are ordered in the same order as the phases occur,
/// so their relative index values can be compared to each other.
enum SchedulerPhase {
  /// 没有正在处理的帧。任务(由SchedulerBinding.scheduleTask调度)、微任务(由scheduleMicrotask调度)、 
  /// Timer回调、事件处理程序(例如,来自用户输入)和其它回调(例如,来自Future、 Stream等)可能正在执行。
  /// No frame is being processed. Tasks (scheduled by
  /// [SchedulerBinding.scheduleTask]), microtasks (scheduled by
  /// [scheduleMicrotask]), [Timer] callbacks, event handlers (e.g. from user
  /// input), and other callbacks (e.g. from [Future]s, [Stream]s, and the like)
  /// may be executing.
  idle,

  /// 瞬态回调(由SchedulerBinding.scheduleFrameCallback调度)当前正在执行。
  /// The transient callbacks (scheduled by
  /// [SchedulerBinding.scheduleFrameCallback]) are currently executing.
  ///
  /// 通常,这些回调处理将对象更新为新的动画状态
  /// Typically, these callbacks handle updating objects to new animation
  /// states.
  ///
  /// See [SchedulerBinding.handleBeginFrame].
  transientCallbacks,

  /// 当前正在执行处理瞬态回调期间安排的微任务。
  /// Microtasks scheduled during the processing of transient callbacks are
  /// current executing.
  ///
  /// 例如,这可能包括来自在transientCallbacks阶段解析的future的回调
  /// This may include, for instance, callbacks from futures resolved during the
  /// [transientCallbacks] phase.
  midFrameMicrotasks,

  /// 持久回调(由SchedulerBinding.addPersistentFrameCallback调度)当前正在执行
  /// The persistent callbacks (scheduled by
  /// [SchedulerBinding.addPersistentFrameCallback]) are currently executing.
  ///
  /// 通常,这是构建/布局/绘制管道。
  /// Typically, this is the build/layout/paint pipeline. See
  /// [WidgetsBinding.drawFrame] and [SchedulerBinding.handleDrawFrame].
  persistentCallbacks,

  /// 帧后回调(由SchedulerBinding.addPostFrameCallback调度)当前正在执行
  /// The post-frame callbacks (scheduled by
  /// [SchedulerBinding.addPostFrameCallback]) are currently executing.
  ///
  /// 通常,这些回调处理下一帧的清理和工作安排
  /// Typically, these callbacks handle cleanup and scheduling of work for the
  /// next frame.
  ///
  /// See [SchedulerBinding.handleDrawFrame].
  postFrameCallbacks,
}

继续看下SchedulerBindingscheduleFrame方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
/// 如有必要,通过调用dart:ui.PlatformDispatcher.scheduleFrame来安排新帧。
/// If necessary, schedules a new frame by calling
/// [dart:ui.PlatformDispatcher.scheduleFrame].
///
/// 调用此函数后,引擎将(最终)调用handleBeginFrame 。 (此调用可能会延迟,例如,如果设备的屏幕关闭,则通常会延迟,直到屏幕打开并且应用程序可见。)
/// 在一个帧期间调用此函数会强制调度另一个帧,即使当前帧已尚未完成。
/// After this is called, the engine will (eventually) call
/// [handleBeginFrame]. (This call might be delayed, e.g. if the device's
/// screen is turned off it will typically be delayed until the screen is on
/// and the application is visible.) Calling this during a frame forces
/// another frame to be scheduled, even if the current frame has not yet
/// completed.
///
/// 当操作系统提供的“Vsync”信号触发时,调度帧就会得到服务。 “Vsync”信号或垂直同步信号在历史上与显示器刷新相关,当时硬件在显示器更新之间物理地垂直移动电子束。
/// 现代硬件的操作在某种程度上更加微妙和复杂,但概念性的“Vsync”刷新信号继续用于指示应用程序何时应更新其渲染
/// Scheduled frames are serviced when triggered by a "Vsync" signal provided
/// by the operating system. The "Vsync" signal, or vertical synchronization
/// signal, was historically related to the display refresh, at a time when
/// hardware physically moved a beam of electrons vertically between updates
/// of the display. The operation of contemporary hardware is somewhat more
/// subtle and complicated, but the conceptual "Vsync" refresh signal continue
/// to be used to indicate when applications should update their rendering.

SchedulerBindingscheduleFrame方法中,做了两件事情:

  • 调用SchedulerBindingensureFrameCallbacksRegistered方法

注册了PlatformDispatcher.onBeginFramePlatformDispatcher.onDrawFrame两个回调。

Vsync信号到来时,onBeginFrameonDrawFrame都会从Engine层回调,其中onBeginFrame主要用来处理动画相关,onDrawFrame主要用来处理页面构建、布局、绘制相关。

本文只讲onDrawFrame回调后的逻辑。

1
2
3
4
5
6
7
8
/// 确保PlatformDispatcher.onBeginFrame和PlatformDispatcher.onDrawFrame的回调已注册。
/// Ensures callbacks for [PlatformDispatcher.onBeginFrame] and
/// [PlatformDispatcher.onDrawFrame] are registered.
@protected
void ensureFrameCallbacksRegistered() {
  platformDispatcher.onBeginFrame ??= _handleBeginFrame;
  platformDispatcher.onDrawFrame ??= _handleDrawFrame;
}
  • 执行platformDispatcher.scheduleFrame()
1
2
3
4
5
6
7
/// 请求在下一个适当的机会调用onBeginFrame和onDrawFrame回调。
/// Requests that, at the next appropriate opportunity, the [onBeginFrame] and
/// [onDrawFrame] callbacks be invoked.
void scheduleFrame() => _scheduleFrame();

@Native<Void Function()>(symbol: 'PlatformConfigurationNativeApi::ScheduleFrame')
external static void _scheduleFrame();

可以看到,platformDispatcher.scheduleFrame() 调用的是_scheduleFrame方法,它已经是底层方法了,这里就不深入底层实现了,不过可以猜测,在Android中应该是调用了Choreographer注册了Vysnc信号监听,然后再回调给Flutter,具体留给你们去验证了。

等到Engine层处理完成,将会回调Framework层的_drawFrame方法,在_drawFrame方法中执行了PlatformDispatcher_drawFrame方法。

1
2
3
4
5
// /FlutterSDK/flutter/bin/cache/pkg/sky_engine/lib/ui/hooks.dart
@pragma('vm:entry-point')
void _drawFrame() {
  PlatformDispatcher.instance._drawFrame();
}

PlatformDispatcher_drawFrame方法中,执行了_invoke方法,第一个参数传入的是onDrawFrame回调,这个就是之前讲过的SchedulerBindingensureFrameCallbacksRegistered方法中注册的回调。

1
2
3
4
// Called from the engine, via hooks.dart
void _drawFrame() {
  _invoke(onDrawFrame, _onDrawFrameZone);
}

来看下_invoke方法,执行了callback(),也就是调用了onDrawFrame的回调引用,那么就会触发SchedulerBinding_handleDrawFrame方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// /FlutterSDK/flutter/bin/cache/pkg/sky_engine/lib/ui/hooks.dart
/// Invokes [callback] inside the given [zone].
void _invoke(void Function()? callback, Zone zone) {
  if (callback == null) {
    return;
  }
  if (identical(zone, Zone.current)) {
    callback();
  } else {
    zone.runGuarded(callback);
  }
}

继续看SchedulerBinding_handleDrawFrame方法,执行了handleDrawFrame方法。

先看下handleDrawFrame方法的注释。

1
2
3
4
5
6
7
8
/// 由引擎调用以产生新的帧
/// Called by the engine to produce a new frame.
///
/// 该方法在handleBeginFrame之后立即调用。它调用addPersistentFrameCallback注册的所有回调(通常驱动渲染管道),然后调用addPostFrameCallback注册的回调
/// This method is called immediately after [handleBeginFrame]. It calls all
/// the callbacks registered by [addPersistentFrameCallback], which typically
/// drive the rendering pipeline, and then calls the callbacks registered by 
/// [addPostFrameCallback].

再看下handleDrawFrame方法的源码,它会遍历_persistentCallbacks这个List,然后执行_invokeFrameCallback方法。

_invokeFrameCallback方法中,实际上是直接调用callback,也就是_persistentCallbacks中的FrameCallback

那么这些FrameCallback是在哪里被添加进_persistentCallbacks这个List

实际上是在RendererBindinginitInstances方法中执行了addPersistentFrameCallback方法添加进来,具体实现是_handlePersistentFrameCallback方法。

RendererBinding_handlePersistentFrameCallback方法中,执行了drawFrame方法。

1
2
3
4
void _handlePersistentFrameCallback(Duration timeStamp) {
  drawFrame();
  _scheduleMouseTrackerUpdate();
}

RendererBindingdrawFrame方法被子类WidgetsBinding实现,在WidgetsBindingdrawFrame方法中,执行了buildOwner!.buildScope(rootElement!);

BuildOwnerbuildScope方法中,对_dirtyElements进行了depth深度排序,遍历过程中从depth深度值为最小的Element开始处理,也就是优先处理越接近根布局的Element,然后执行Elementrebuild方法完成Element的重建。

Elementrebuild方法中,后续执行了performRebuild方法,这个方法我们太熟悉了,之后的过程就不讲解了,最终会触发build方法的执行,完成相关Widget的重建。

WidgetsBindingdrawFrame方法中,执行了buildOwner!.buildScope(rootElement!)之后,就会执行super.drawFrame(),也就是调用父类RendererBindingdrawFrame方法。该方法意味着在重新构建渲染树之后就是真正的渲染开始了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/// 泵送渲染管道以生成帧
/// Pump the rendering pipeline to generate a frame.
///
/// 该方法由handleDrawFrame调用,当需要布局和绘制框架时,该方法本身由引擎自动调用。
/// This method is called by [handleDrawFrame], which itself is called
/// automatically by the engine when it is time to lay out and paint a frame.
///
/// 每个帧由以下阶段组成:
/// Each frame consists of the following phases:
///
/// 1.动画阶段:在PlatformDispatcher.onBeginFrame中注册的handleBeginFrame方法按注册顺序调用在scheduleFrameCallback中注册的所有瞬态帧回调。
/// 这包括驱动AnimationController对象的所有Ticker实例,这意味着所有活动的Animation对象此时都会勾选。
/// 1. The animation phase: The [handleBeginFrame] method, which is registered
/// with [PlatformDispatcher.onBeginFrame], invokes all the transient frame
/// callbacks registered with [scheduleFrameCallback], in registration order.
/// This includes all the [Ticker] instances that are driving
/// [AnimationController] objects, which means all of the active [Animation]
/// objects tick at this point.
///
/// 2. 微任务:[handleBeginFrame] 返回后,由瞬态帧回调调度的任何微任务都会开始运行。
/// 这通常包括来自完成此帧的 [Ticker] 和 [AnimationController] 的 future 回调。
/// 2. Microtasks: After [handleBeginFrame] returns, any microtasks that got
/// scheduled by transient frame callbacks get to run. This typically includes
/// callbacks for futures from [Ticker]s and [AnimationController]s that
/// completed this frame.
///
/// 在 [handleBeginFrame] 之后,调用在 [dart:ui.PlatformDispatcher.onDrawFrame] 中注册的 [handleDrawFrame],
/// 它会调用所有持久帧回调,其中最值得注意的是这个方法 [drawFrame],其执行过程如下如下:
/// After [handleBeginFrame], [handleDrawFrame], which is registered with
/// [dart:ui.PlatformDispatcher.onDrawFrame], is called, which invokes all the
/// persistent frame callbacks, of which the most notable is this method,
/// [drawFrame], which proceeds as follows:
///
/// 3. 布局阶段:系统中所有脏的[RenderObject]都被布局
/// 3. The layout phase: All the dirty [RenderObject]s in the system are laid
/// out (see [RenderObject.performLayout]). See [RenderObject.markNeedsLayout]
/// for further details on marking an object dirty for layout.
///
/// 4. 合成位阶段:更新任何脏 [RenderObject] 对象上的合成位。
/// 4. The compositing bits phase: The compositing bits on any dirty
/// [RenderObject] objects are updated. See
/// [RenderObject.markNeedsCompositingBitsUpdate].
///
/// 5. 绘制阶段:系统中所有脏的[RenderObject]都被重新绘制(参见[RenderObject.paint])。这会生成[图层]树。
/// 5. The paint phase: All the dirty [RenderObject]s in the system are
/// repainted (see [RenderObject.paint]). This generates the [Layer] tree. See
/// [RenderObject.markNeedsPaint] for further details on marking an object
/// dirty for paint.
///
/// 6. 合成阶段:将图层树变成【场景】并发送到GPU。
/// 6. The compositing phase: The layer tree is turned into a [Scene] and
/// sent to the GPU.
///
/// 7. 语义阶段:系统中所有脏的[RenderObject]都更新了它们的语义。这会生成 [SemanticsNode] 树。
/// 7. The semantics phase: All the dirty [RenderObject]s in the system have
/// their semantics updated. This generates the [SemanticsNode] tree. See
/// [RenderObject.markNeedsSemanticsUpdate] for further details on marking an
/// object dirty for semantics.
///
/// For more details on steps 3-7, see [PipelineOwner].
///
/// 8. 最终确定阶段:[drawFrame] 返回后,[handleDrawFrame] 调用帧后回调(在 [addPostFrameCallback] 中注册)。
/// 8. The finalization phase: After [drawFrame] returns, [handleDrawFrame]
/// then invokes post-frame callbacks (registered with [addPostFrameCallback]).
///
/// 某些绑定(例如,[WidgetsBinding])会向此列表添加额外的步骤(例如,请参阅[WidgetsBinding.drawFrame])。
/// Some bindings (for example, the [WidgetsBinding]) add extra steps to this
/// list (for example, see [WidgetsBinding.drawFrame]).
//
// When editing the above, also update widgets/binding.dart's copy.
@protected
void drawFrame() {
  pipelineOwner.flushLayout();
  pipelineOwner.flushCompositingBits();
  pipelineOwner.flushPaint();
  if (sendFramesToEngine) {
    renderView.compositeFrame(); // this sends the bits to the GPU
    pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
    _firstFrameSent = true;
  }
}