原理Flutter内部结构--FLutter是如何工作的?

Posted 牧羊人.阿标

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了原理Flutter内部结构--FLutter是如何工作的?相关的知识,希望对你有一定的参考价值。


Flutter 是如何工作的?

什么是Widgets, Elements, BuildContext, RenderObject, Bindings…

阅读难度:初级

介绍

刚开始接触 Flutter 的时候,互联网上很难找到相关的资料,尽管也有不少相关的文章,但几乎没有关于 Flutter 内部实现的。

Widgets, Elements, BuildContext 究竟是什么?为什么 Flutter 执行效率那么高?执行的时候,为什么有时候会出现不符合预期的结果?经常提到的树,究竟是什么?

当你在开发一个 flutter app 时,你大部分情况下是在跟 Widgets 打交道。但你有没有想过,这些功能是如何实现的?系统是怎么知道什么时候需要刷新,哪些部分需要刷新的呢?

第一部分: 背景

文章的第一部分会介绍一些重要的概念,这些概念将会有助于理解整篇文章。


从设备说起

首先,我们从有关设备的基础知识开始。

当一款app在你的设备上运行的时候,你是透过屏幕,才能看到这款app的具体内容。

更准确地说,你所看到的是一系列的像素点组成的2D图像。当你用手指触摸屏幕时,这个设备也仅仅是识别你手指在屏幕上的位置而已。

大部分情况下,app页面更新的原因在于出现了以下四种交互场景:

  • 与设备屏幕的交互(e.g. 手指在屏幕上的点击操作)
  • 与网络的交互(e.g. 从服务器请求数据)
  • 与时间相关的交互(e.g. 动画)
  • 与其他外部的传感器的交互

图像是由硬件(显示器)渲染到屏幕上的,这块显示器会以一定的频率(通常是每秒60次)来刷新显示的内容。这个刷新的频率也被称为**“刷新率”,用符号来表示的话,就是Hz**(Hertz)。

显示器GPUGraphics Processing Unit)获取数据,然后将这些数据显示在屏幕上。GPU 是一个专业的集成电路,为了把一些数据(polygons和textures)变成图像,它做了许多优化。GPU 每秒能够生成 图像(帧缓冲) 并发送图像到显示器的数量被称为帧率,通常我们用 fps 作为帧率的单位(e.g. 每秒60帧,称为60fps)。

你可能会问我,为什么这篇文章要从2D图像、GPU、显示器的概念说起?这些与 Flutter 的 Widget 有什么关系?

简单地说, Flutter app 的一个主要目的就是生成2D图像,并且让这个2D图像可以交互。我认为,如果我们能从视觉层面上看到它,那么在学习 Flutter 的实际工作流程的过程中,将会更容易理解。

并且在 Flutter 中,几乎所有内容的实现都是基于屏幕刷新的。

代码与物理设备的接口

每一个对 Flutter 的感兴趣的人,一定都见过这张描述 Flutter 架构的图。

当我们用 Dart 编写 Flutter 应用时,我们处在 Flutter框架 这个层级中(绿色部分)。

Flutter框架 通过 WindowFlutter Engine(蓝色部分)交互。Window 这个抽象层暴露了一系列API,来让 Flutter框架 间接地与设备进行交互。

也是通过这个抽象层,当以下情况发生时,Flutter Engine 会通知 Flutter框架

  • 当设备发生了一些事件时,比如方向改变、设置改变、内存问题,app运行状态改变等。
  • 当屏幕上发生了一些事件时,比如手势
  • 当平台的 channel 发送一些数据时
  • 最主要的,当 Flutter Engine 准备渲染新的一帧

Flutter框架 是由 Flutter Engine 框架中的 rendering 模块驱动的

这可能很难让人相信,但事实确实是这样。

Flutter框架中的代码,只有在 Flutter Engine 框架中的 rendering 模块的驱动下,才会执行,这包括下面四种情况:

  • Gesture(触摸屏幕相关的事件)
  • Platform messages,(设备发出的消息,比如 GPS )
  • Device messages(设备状态变化的消息 e.g. 设备方向、app进入后台、内存警告、设备设置等)
  • Future 或者 http请求

如果 Flutter Engine 框架中的 rendering 模块没有发起请求的话,
Flutter框架 将不会执行任何与UI相关的代码。

(事实上,在没有 Flutter Engine 驱动的情况下,Flutter框架 自身是可以改变UI的,但这是非常不建议的

你可能会问,当 手势 引起UI变化,或者当使用一个 timer 来执行一个引起UI变化的任务(比如动画),在这种情况下,界面是如何刷新的呢?

如果你希望UI能够发生变化,或者你想添加一个计时器来执行一些代码,
你需要告诉 Flutter Engine,有内容需要刷新。

一般而言,在下一次刷新的时候,Flutter Engine 会要求 Flutter框架 去执行一些代码,这些代码最终会为 Flutter Engine 生成一个新的 scene 来渲染。

因此,目前最大的问题就是 Flutter Engine 是如何获取到新的 scene 来提供给 rendering模块 来渲染的?

先看下面的动图,来了解 Flutter 内部的运行机制,

先简单说明一下(随后会有详细解释):

  • 一些外部事件(手势、http请求等等)或者 future,会执行改变UI的任务。在这种情况下,一条消息(Schedule Frame)就会被发送给 Flutter Engine,来通知它需要刷新。
  • Flutter Engine 准备渲染之前,它会发出一个 Begin Frame 请求
  • 这个 Begin Frame 请求会被 Flutter框架 拦截,然后执行 Tickers 相关的任务(比如动画)
  • 这些任务可能会发起重新渲染的请求(比如一个动画还没有完成,它需要在下一个阶段的 Begin Frame 时继续执行动画代码)
  • 接着,Flutter Engine 发起 Draw Frame 请求
  • Draw Frame 会被 Flutter框架 拦截,Flutter框架 会找出所有与结构、尺寸相关的更新布局的任务,然后执行。
  • 等到所有这些任务都执行完, Flutter框架 会继续处理与绘制相关的更新布局的任务
  • 如果UI有更新的话, Flutter框架 会发送新的需要被渲染的 SceneFlutter EngineFlutter Engine 会完成屏幕的更新
  • 然后, Flutter框架 会执行渲染结束之后的所有任务,以及一些与渲染无关的子任务。
  • 然后这个不断重复这个流程

RenderView 和 RenderObject

在深入介绍整个工作流程之前,我们需要先了解 Rendering Tree 的概念。

如同之前所说,一切数据最终会成为像素点,显示在屏幕上。 Flutter框架 将我们用来构建app的 Widgets 转换成视觉相关的内容,最终将会被渲染到屏幕上。

这些将被渲染到屏幕上的视觉相关的内容,相对应的类,叫做 RenderObject,它被用于:

  • 根据尺寸、位置、几何形状(统称为渲染内容)来定义一些区域
  • 在屏幕上定义能够识别手势的区域

这一系列的 RenderObject 形成一棵树,这棵树被叫做 Render树 ;在树的最顶端(=root),我们会找到 RenderView.

RenderView 表示 Render树 的整个渲染平面,并且它自己也是一个特别的 RenderObject.

我们可以用下图来描述:

关于Widgets 和 RenderObjects 的关系,我们在后面的内容中会提到。

现在,让我们再深入学习一下。


先说重要的 - bindings的初始化

当你启动一个 Flutter应用 时,系统会调用 main() 方法,最终会调用 RunApp(Widget app) 这个方法。

在执行 runApp() 方法时, Flutter初始化了 Flutter框架Flutter Engine 之间的接口。 这个接口就叫做 bindings.


Bindings - 介绍

bindings 就是一些 Flutter EngineFlutter框架 之间的胶水代码。在这两个Flutter模块(Flutter Engine 和 Flutter框架)之间,只有通过 bindings 才能交换数据。
(有一个例外,那就是 RenderView,我们后面会谈到)

每一个 Binding 负责处理一组特别的任务、行为和事件,并按活动域重新分组。

在写这篇文章的时候,Flutter框架 包括8个binding.

我们会在本文中讨论下面4个:

  • SchedulerBinding
  • GestureBinding
  • RendererBinding
  • WidgetsBinding

为了方便理解,我们也列举出剩下的4个(但我们将不会在本文中详细介绍):

  • ServicesBinding: 负责处理来自 平台 通道的数据
  • PaintingBinding: 负责处理图片缓存
  • SemanticsBinding: 保留供以后实现与语义相关的所有内容
  • TestWidgetsFlutterBinding: 供widgets测试库使用

我也会提到 WidgetsFlutterBinding, 但它不是一个真正意义上的binding,它更像是一个“ binding初始器 ”。

下图显示了我稍后将要介绍的 bindings 和 Flutter Engine 之间的关系,

让我们来看一下这些“主要”的 binding.


SchedulerBinding

这个 binding 有两个主要的责任:

  • 第一,告诉 Flutter Engine: “ 嗨!下次你不忙的时候,记得把我唤醒,以便我能工作一会儿,并告诉你需要渲染的内容,或者是否需要你一会儿再唤醒我
  • 第二,监听并响应上一条提到的*“唤醒事件”*(见下文)

什么时候 SchedulerBinding 会请求 唤醒事件

  • Ticker 执行改变UI任务时,

    举个例子,假设你启动了一个动画,这个动画被 Ticker 驱动,Ticker 每隔一段时间会执行 回调 。为了执行这个 回调 ,我们需要告诉 Flutter Engine 在下一次刷新时,唤醒我们(通过调用 Begin Frame),这将会执行 ticker回调函数 来完成这个任务。在执行完这个任务之后,如果 ticker 还需要接着执行动画任务,它将会调用 SchedulerBinding 来把接下来的任务安排在下一帧。

  • 当layout发生变化时。

    举个例子,当你在处理一个改变UI的事件时(比如,更改颜色、滚动、从屏幕上新增/删除内容),我们需要一些必要的步骤来让内容最终渲染到屏幕上。在这种情况下,当这些变化发生时, Flutter框架 将会通过调用 SchedulerBinding 来让 Flutter Engine 执行下一帧刷新的任务。(一会儿会看到它是如何工作的)


GestureBinding

这个 binding 监听 Flutter Engine 中的 手势 事件。

特别地,它负责接收与 手势 相关的数据,并且决定屏幕的哪个区域接收这个数据,然后通知相应的区域。


RendererBinding

这个 bindingFlutter EngineRender Tree 之间的胶水代码。它有两个不同的责任:

  • 第一,监听 Engine 发出的用户通过改变设备设置而引起UI或者 semantics 变化的事件
  • 第二,为 Engine 提供渲染到屏幕上的数据

为了将数据渲染到屏幕上,这个 Binding 要负责驱动 PipelineOwner,并且初始化 RenderView.

这个 PipelineOwner 是一种 协调器 ,它知道哪个 RenderObject 需要重新布局,并且进行调度来完成这些任务。


WidgetsBinding

这个 binding 监听设备设置中影响本地化(locale)和 semantic 的事件。

旁注

我猜测以后与 semantic 相关的事件,都会被迁移到 SemanticsBinding 中,
但在写这篇文章的时候,相关代码还没有被迁移。

除此之外, WidgetsBinding 也是 WidgetFlutter Engine 之间的胶水代码,它有两个主要的职责:

  • 第一个是负责 Widget 结构的构建
  • 第二个是触发渲染流程

Widgets 结构的构建是由 BuildOwner 来完成的。

BuildOwner 会记录哪些 Widget 需要重建,以及处理其他的引起 widget 结构变化的任务。


第二部分:从 Widget 到像素

既然我们已经介绍了内部机制的基础知识了,那么现在该讨论 Widget 了。

所有 Flutter 的文档都会告诉你 一切都是 Widget

当然,这种说法没错,但为了更精确一点,请让我重新描述一下:

从开发者的角度而言,用户界面中,有关布局和交互的部分,
都是通过 Widgets 来完成的。

为什么要这么描述呢?因为 Widget 在屏幕上定义尺寸、内容、布局、交互的同时,它能做的还更多。那么到底 Widget 是什么呢?


不可变的配置

当你在读 Flutter 的源码的时候,你会注意到对 Widget 类的定义:

@immutable
abstract class Widget extends DiagnosticableTree 
  const Widget( this.key );

  final Key key;

  ...

这是什么意思呢?

这个注释 “@immutable” 非常重要,它告诉我们任何 Widget 类中的变量都必须是 final 的,换句话说:“ 只会在初始化时,被赋值一次 ”。那么,一旦初始化, Widget 将不能再改变它的 内部变量

正是因为 Widget 是不可变的,所以它更像是一种不可变的配置文件

Widgets 的层级结构

当你用 Flutter 进行开发时,你会用 Widget 来编写界面,看起来像这样:

Widget build(BuildContext context)
    return SafeArea(
        child: Scaffold(
            appBar: AppBar(
                title: Text('My title'),
            ),
            body: Container(
                child: Center(
                    child: Text('Centered Text'),
                ),
            ),
        ),
    );

这个例子中用了7个 Widget,他们共同形成了一个层级结构。

如果简单列举的话,这个层级结构如下图所示:

如图所示,这看起来就像是一棵树,而 SafeArea 就是这棵树的根节点。


是树也是森林

你一定知道, Widget 它自己可能包含多个 Widget 。比如,上面的代码,我可以重构成这样:

Widget build(BuildContext context)
    return MyOwnWidget();

这里的 “MyOwnWidget” 包括 SafeAre, Scaffold 等等,但举这个例子,最重要的是要说明:

Widget 既可以叶节点、节点,而且它本身也可以是树,甚至是由多棵树组成的森林…


Element 的概念

为什么我们要在这里提到 Element ?

正如我们后面会看到的那样,为了能够在设备上渲染出相应图像的像素, Flutter 需要知道组成屏幕的各个部分的所有细节,所以为了知道所有的数据,它会 inflate 所有的 Widget

举个例子,想象一下俄罗斯套娃:最初,你仅仅看到一个套娃,实际上它包含了另一个套娃,而这个套娃又包含了另一个套娃,就这样一直重复下去…

Flutter inflate 所有的 widget 的过程,就跟我们拿出俄罗斯套娃中所有的娃娃的过程类似。

下图展示了之前代码中 Widget inflate 之后的层级结构(的其中一部分)。其中黄色高亮了之前代码中提到过的 Widget ,以便你能够从这棵 widget树 中认出它们来。

重要声明

我们说 “Widget树” ,仅仅是方便理解,
因为开发者就是用 Widget 进行开发的,但在 Flutter 中, Widget树 是不存在的!

实际上,我们真正描述的是:“Element树”

现在是时候来介绍下 Element 了…

每一个 widget 对应一个 element 。
Element 之间相互关联,形成一棵树。
因此,一个 element 就是树上的一个引用。

首先,我们想象一下,一个 element 就是一个有 parentchild(可能没有) 的节点。通过 parent 的关系,这些节点联接在了一起,我们就获得了一个树形结构。

如上图所示, Element持有 一个 Widget ,也 可能 会持有一个 RenderObject

更确切地说…Element 持有的这个 Widget 能够创建出跟自己类型相对应的 element 实例 !

让我们再回顾一下…

  • 没有Widget树,只有Element树
  • Element 是由 Widget 创建的
  • Element 持有创建它自己的 Widget
  • Element 通过 parent 关系关联在一起
  • Element 可能有 child 或者 children
  • Element 也可能持有 RenderObject

Element 定义了各个界面是如何相互关联的

为了更好地理解 element 的概念,我们一起来看看下图:

如你所见, element树 才是 WidgetRenderObject 之间的实际联系。

但是,为什么要用 Widget 来创建 Element 呢?


Widget 的3个主要的类型

Flutter 中,Widget 被分成了3大类,我这样称呼这三种类型(仅仅是我个人的划分方式):

  • 代理类

    这类 Widget 的主要作用是持有一些信息,这些信息能够被它的 child 访问。典型的例子就是 InheritedWidgetLayoutId.

  • 渲染类

    这类 Widget 在下面三个方面,直接或间接地参与布局:

    • 尺寸
    • 位置
    • 布局、渲染

    例如 Row, Column, Stack, Padding, align, Opacity, RawImage…

  • 组件类

    这类 Widget 不直接提供尺寸、位置、外观这类最终信息,而是提供可以获取最终信息的数据(或者叫线索)。这类 Widget 一般被称作 组件

    比如:RaisedButton, Scaffold, Text, GestureDetector, Container…

这个 PDF 按类型列出了大部分 Widget .

为什么分类如此重要?因为不同类型的 Widget 都会有一个对应类型的 Element

Element 类型

下图是不同类型 element

如图所示,Element 主要分为两大类:

  • ComponentElement

    这类 element 不会直接参与UI渲染。

  • RenderObjectElement

    这类 element 直接参与UI渲染。

到目前为止,出现了很多概念,我们为什么要介绍这些概念?它们又是如何关联在一起的呢?


Widgets 和 Element 是如何合作的?

在 Flutter 中,
整个机制的运行都是依赖于 invalidate element 或者 renderObject 。

有两种方法可以 invalidate element

  • 通过调用 setState ,可以 invalidate StatefulElement (注意这里我故意没有提到 SatefulWidget
  • proxyElement (例如 InheritedWidget )通过通知,来 invalidate 所有依赖它的 element

invalidate element 的作用就是使该 element 被标记为 dirty

有时候 element 的结构没有发生变化,那么只需要 invalidate renderObject 即可,比如:

  • UI的尺寸、位置、形状的变化
  • 背景颜色、字体改变等需要重绘的情况

结果就是,相应的 renderObject 会被 rebuild 或者 repaint.

不管是 invalidate element 还是 renderObject , 都会调用 SchedulerBinding 去让 Flutter Engine 在下一帧刷新的时候,执行刷新UI的代码,这就是整个渲染流程开始的地方。

onDrawFrame()

在文章开头,我们提到过 SchedulerBinding 有两个主要的责任,其中一个是处理来自 Flutter Engine 的关于 rebuild 的请求。现在我们来详细介绍这个过程…

下面的时序图展示了,当 Flutter Engine 执行 SchedulerBindingonDrawFrame() 方法时,具体发生了什么:

第一步: elements

WidgetBinding 会被调用,我们首先来看与 element 有关的流程。

我们知道 BuildOwner 是负责处理 element树 的,实际上 WidgetBinding 就是调用 buildOwnerbuildScope 方法。

这个方法会遍历 invalidated elements (也就是状态为 dirty 的 element ),然后调用 element 中的 rebuild() 方法。

rebuild() 实际上做了以下几件事情:

  1. rebuild() 中会执行 element 持有的 widget 的 build() 方法,这也是 rebuild() 中最耗时的操作。 这个 build() 会返回一个新的 widget .
  2. 如果 element 没有 child ,那么这个新的 widget 会 inflate (见下文),否则
  3. 将新的 widget 与 element 的 child 持有的 widget 进行对比,
    • 如果能够互换(widget类型和key都相同),那么就用新的 widget 替换旧的 widget ,child element 不变。
    • 如果不能互换,那么 child element 会被丢弃,新的 widget 会 inflate.
  4. widget inflate 时,会生成新的 element ,这个 element 会作为 child 被 mount 到当前 element 上。(mount就是将自己作为 child 加入到 element树 中)

整个流程如下图所示,

关于 Widget inflate 的解释

当 widget inflate 时,widget 会被要求创建一个 element ,这个 element 的类型与创建它的 widget 类型有关。

因此,

  • InheritedWidget 会生成 InheritedElement
  • StatefulWidget 会生成 StatefulElement
  • StatelessWidget 会生成 StatelessElement
  • InheritedModel 会生成 InheritedModelElement
  • InheritedNotifier 会生成 InheritedNotifierElement
  • LeafRenderObjectWidget 会生成 LeafRenderObjectElement
  • SingleChildRenderObjectWidget 会生成 SingleChildRenderObjectElement
  • MultiChildRenderObjectWidget 会生成 MultiChildRenderObjectElement
  • ParentDataWidget 会生成 ParentDataElement

不同的 element 会有不同的行为,比如

  • StatefulElement 会在初始化的时候调用 widget.createState() 来创建一个 State ,这个 state 会被 elemnt 持有。
  • RenderObjectElement 被 mount 时,会创建一个 RenderObject ,这个 renderObject 会被添加到 render tree ,也会被 element 持有。

第二步: renderObjects

当把所有的 dirty element 都处理完了之后, element tree 也已经构建完毕,接下来就是 render 流程了。

因为是 RendererBinding 负责处理 rendering tree ,所以在 WidgetsBinding 完成 build 的任务之后,它会调用 RendererBindingdrawFrame 方法来完成 render 工作。

RendererBinding 中执行 drawFrame() 的时序图如下图所示:

在整个过程中,会按照下面的流程执行:

  • 每一个 dirty 状态的 renderObject 会执行它们的 layout 方法,这个方法会重新计算尺寸和形状;
  • 每一个 needs paint 状态的 renderObject 都会使用 renderObjectlayer 进行重绘;
  • 最终生成的 scene 会被发送给 Flutter EngineFlutter Engine 随后会将 scene 渲染到屏幕上;
  • Semantic 也会被更新,然后交给 Flutter Engine 渲染;

在整个流程之后,屏幕就会被刷新了。


第三部分:手势处理

GestureBinding 负责处理手势相关的逻辑。

Flutter Engine 会通过 window.onPointerDataPacket 发送关于手势的信息,而 GestureBinding 会拦截这个消息,然后执行以下流程:

  1. 转换 Flutter Engine 发送过来的坐标信息,来适配当前设备的分辨率,
  2. 根据转换后的坐标,从 renderView 中获取所有在这个坐标区域内的 RenderObject
  3. 遍历这些 renderObject ,把手势事件依次派发给它们,
  4. renderObject 接受到事件之后,就会自己处理它。

由此我们可以看出, renderObject 有多么重要。

第四部分:动画

本文最后一部分着重介绍以下 动画 的概念,特别是 Ticker 的概念。

当你初始化一个动画的时候,你一般会用 AnimationController 这个类,或者是相关的 Widget 或 conponent 。

在 Flutter 中,所有与动画相关的内容,都离不开 Ticker

Ticker 其实只做了一件事情,当 Ticker 处于激活状态时:“它会叫 ScheduleBinding 注册一个回调,并叫 Flutter Engine 在下一帧唤醒自己 ”。

Flutter Engine 准备好了之后,它会通过 onBeginFrame 唤醒 SchedulerBinding

SchedulerBinding 会拦截这个请求,然后遍历并调用 ticker 的所有回调。

每一个 ticker 的 tick 都会被被相应的控制器拦截,并且执行各自相关的代码。如果动画已经完成,那么 tikcer 的状态会被更改为 “disabled” ,否则, ticker 会再次调用 SchedulerBinding 来安排另一个回调。


总览

我们现在已经知道 Flutter 的内部运行机制了,下图是整个流程:


BuildContext

如果你回头看展示不同类型的 element 的图片时,你很可能注意到 base Element 的定义:

abstract class Element extends DiagnosticableTree implements BuildContext 
    ...

这里,我们看到了著名的 BuildContext

那么到底什么是 BuildContext 呢?

BuildContext 就是一个接口,它定义了一些 getter方法element 会实现这个接口。

BuildContext 主要是在 StatelessWidgetStatefulWidgetbuild() 方法中使用,除此之外,还有 StatefulWidgetState 中,也在使用。

BuildContext 在下列情况下,就是 Element 本身:

  • Widget rebuild 的时候(build 或者 builder 方法中的 context)
  • StatefulWidget 中的 State 引用的 context

这也就是说,大部分开发者每天都在跟 element 打交道,但自己却不知道。


如何使用 BuildContext ?

BuildContext相当于就是 widget 所对应的 element ,而且也代表着 widget 在树中的位置,因此在以下面几种场景中,常常用到 BuildContext

  • 获取 widget 对应的 RenderObject
  • 获取 RenderObject 的尺寸
  • 遍历 element树。实际上,一般 of( 比如 Theme.of(context) ) 方法的实现,就是依赖于 BuildContext

一个不恰当的例子

现在我们知道 BuildContext 其实就是 element 了,为了给你展示它的另一种使用方法,这里举一个不太恰当的例子…

接下来的例子,让 StatelessWidget 能够更新它自己,它看起来就像是没有 setState() 方法的 StatefulWidget ,我们通过 BuildContext 来实现这个功能。

警告

千万不要这样编写代码!

它唯一的目的就是让我们知道, StatelessWidget 也能够 更新它自己。

如果你需要更改状态,请使用 StatefulWidget

void main()
    runApp(MaterialApp(home: TestPage(),));


class TestPage extends StatelessWidget 
    // final because a Widget is immutable (remember?)
    final bag = "first": true;

    @override
    Widget build(BuildContext context)
        return Scaffold(
            appBar: AppBar(title: Text('Stateless ??')),
            body: Container(
                child: Center(
                    child: GestureDetector(
                        child: Container(
                            width: 50.0,
                            height: 50.0,
                            color: bag["first"] ? Colors.red : Colors.blue,
                        ),
                        onTap: ()
                            bag["first"] = !bag["first"];
                            //
                            // This is the trick
                            //
                            (context as Element).markNeedsBuild();
                        
                    ),
                ),
            ),
        );
    

事实上,当你在 StatefulWidget 中调用 setState() 时,实际上执行的也是 _element.markNeedsBuild()


总结

好了,终于又完成了一篇文章。

我认为,Flutter 的架构是很有趣的,它被设计得高效、可扩展,并且对未来的扩展开放。

而且,像 Widget, Element, BuildContext, RenderObject 的概念,并不总是很好理解。

希望本文对你有用。

敬请期待新的文章,同时,祝你编码愉快。

以上是关于原理Flutter内部结构--FLutter是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章

原理Flutter内部结构--FLutter是如何工作的?

Flutter Provider:如何监听类字段内部的类字段变化?

Flutter 快速解析 TextField 的内部原理 | 开发者说·DTalk

Flutter 快速解析 TextField 的内部原理 | 开发者说·DTalk

深入理解flutter的编译原理与优化

Flutter 内幕:Flutter 在内部是如何工作的?