Flutter 内幕:Flutter 在内部是如何工作的?
Widgets
、Elements
、BuildContext
到底是什么东西?为什么 Flutter 可以运行那么快?为什么有时候运行的效果并不符合我们的预期?什么是所谓视图树?——本文将一一为你解答。
Widgets
、Elements
、BuildContext
、RenderObject
这些都是些什么东西?
Widgets
、Elements
、BuildContext
到底是什么东西?为什么 Flutter 可以运行那么快?为什么有时候运行的效果并不符合我们的预期?什么是所谓视图树?
Widgets
,用它来展示 UI 并处理屏幕交互。但你是否考虑过,整个系统是如何工作的,是如何知道要更新哪些 UI 呢?
- 屏幕事件 (点击屏幕 )
- 网络事件 (与服务器通信)
- 时间事件 (动画)
- 其它传感器事件
- 设备级别的属性更改 (设备方向改变,设置修改,内存问题,APP状态修改等)
- 屏幕级别的更改(手势)
- 平台渠道发送的数据
- 在 Flutter 引擎层空闲下来,可以渲染新的帧的时候,会发送通知给 Flutter 框架层。
- 手势 (屏幕上的事件)
- 平台消息(如 GPS)
- 硬件消息(如旋转屏幕, 应用压后台,内存不足等)
- 异步消息( Future API 或者 HTTP 响应)
注:一般情况下,如果Flutter渲染引擎没有发出通知, Flutter 框架是不能更新任何UI的。有些时候,在没有 Flutter 渲染引擎通知的情况下,也可以让 Flutter 框架更新UI,但是并不建议这么做。
如果你想更新UI, 或者说你想在后台执行代码逻辑并更新 UI,你需要告诉 Flutter 引擎,这里有一些更改需要被渲染到屏幕上。通常情况下,在屏幕下一次刷新的时候, Flutter引擎会通知 Flutter框架,让它来提供新场景的图像来进行渲染显示。
- 像手势、http 网络请求和异步事件,它们都会触发一个异步任务,当它们引起 UI 的更新。它们会发送一个消息(Schedule Frame)给 Flutter引擎,告诉 Flutter引擎,有新的UI需要被渲染。
- 当 Flutter引擎准备好,可以更新UI的时候,它会发送 Begin Frame 通知到Flutter框架。
- Flutter 框架运行着的异步任务,如动画,它们会拦截掉 Begin Frame 通知。
- 这些异步任务会根据自身状态进行判断是否需要继续发送请求给 Flutter 引擎,用来触发后续的UI渲染(例:当一个动画没有完成的时候,为了让动画可以继续执行,它会发送一个通知到 Flutter 引擎,然后会等待接收另一个 Begin Frame 的通知)。
- 紧接着,Flutter 引擎会发出一个 Draw Frame 的通知到 Flutter 框架层。
- Flutter 框架会拦截 Draw Frame 通知,并根据任务进行布局调整和UI大小计算。
- 完成这些任务后,它将继续执行与更新布局有关的绘画任务。
- 如果有什么要画在屏幕上,它会发送一个全新的场景数据到 Flutter 引擎,让Flutter引擎来更新到屏幕上。
- 最后,Flutter 框架执行完所有的任务并且在屏幕中渲染完成。
- 紧接着会继续一遍又一遍的执行上述流程。
RenderObject
,它被用来表示:
- 定义屏幕中的区域,包括 大小,位置, 几何结构。也可称其为"渲染内容"。
- 识别可能受到手势影响的屏幕区域。
RenderObject
共同组成了一棵树,称之为视图树。在视图树的最上面,也就是其跟节点,就是RenderView。RenderView
代表了整个输出的视图树,它也是一种特殊的 Renderobject
, 如图所示:
Widgets
与 RenderObjects
之前的关系。但在这之前,我们需要更深入的了解 RenderObjects
。
main()
方法,它会调用 runApp(Widget app)
。
- SchedulerBinding
- GestureBinding
- RendererBinding
- WidgetsBinding
- ServicesBinding :处理不同平台发过来的消息
- PaintingBinding:处理图片缓存
- SemanticsBinding:保留到以后实现所有与语义相关的内容
- TestWidgetsFlutterBinding:组件测试使用的
- 第一个是告诉 Flutter 引擎:“我现在已经准备好了,在你不忙的时候,把我唤醒,告诉我要渲染的内容,我会开始工作。”
第二个是监听并响应一些事件,如唤醒事件。
当SchedulerBinding接受到唤醒事件 的时候,要做些什么呢?
需要 Ticker 来控制的时候
举个例子,假设您有一个动画,并且已经开始执行了。这个动画是由Ticker进行控制的,它需要以固定时间间隔触发回调。要让这样的回调运行,我们需要告诉Flutter 引擎在下次刷新时唤醒我们(发送Begin Frame),触发回调,执行动画任务。在该动画任务结束时,动画还需要继续,它将再次调用SchedulerBinding来调度另一帧。
更改布局
当你响应导致视觉变化的事件(例如,更新屏幕的一部分的颜色,滚动,向屏幕中添加/从屏幕中删除某些内容)时,我们需要采取必要的步骤来保证它可以正常的显示在屏幕上。在这种情况下,Flutter框架将调用SchedulerBinding来告诉Flutter Engine去调度另一帧。
GestureBinding
这个 binding 处理手势事件,并与 Flutter 引擎进行通信。它负责接受与手指有关的数据,并确定屏幕的哪一部分受到手势的影响。然后,它会通知这些部分,来响应事件。
RendererBinding
它是 Flutter 引擎与视图树之前的桥梁,它有两个不同的功能:
- 第一个是监听Flutter引擎发出的消息,当设备设置发生更改,它会告知用户受到影响的视觉效果/语义。
第二个是为 Flutter 引擎提供要显示在屏幕上的数据。
PipelineOwner是一种协调器,它知道哪个RenderObject需要做一些与布局有关的事情并协调这些动作。
- 第一个是负责处理Widgets结构变更的过程;
第二个是触发渲染事件。
一些小组件的结构更改是 BuildOwner 来完成的,它跟踪需要重建的小部件,并处理应用于整个小部件结构的其他任务。
第二部分:Widgets 转换成像素
基础的内部原理讲解完成,下面我们来看看 Widgets。
在所有有关 Flutter 的文档中,你能看到这样子的描述:在Fluter中,所有的对象都是 Widgets 。这样子说虽然没错,但要说得更精确一些,我觉得应该是:
从开发人员的角度来看,在布局和交互方面,与用户界面相关的所有内容均通过Widgets完成。
2abstract class Widget extends DiagnosticableTree {
3 const Widget({ this.key });
4
5 final Key key;
6
7 ...
8}
2 return SafeArea(
3 child: Scaffold(
4 appBar: AppBar(
5 title: Text('My title'),
6 ),
7 body: Container(
8 child: Center(
9 child: Text('Centered Text'),
10 ),
11 ),
12 ),
13 );
14}
2 return MyOwnWidget();
3}
MyOwnWidget
会自己去渲染 SafeArea, Scaffold 等 Widget。这个例子,要表达的意思是:
Widget 可能是一个页子节点,也可能是一颗树。
注:在这里我们使用了 "Widget 树",这只是为了更好的理解逻辑,在 Flutter中并没有这个概念。
每个小部件都对应一个元素。元素彼此链接并形成一棵树。因此,元素是树中某个节点的引用。
- 没有 Widgets 树,但是有元素树;
- Widgets 创建 Elements;
- Element 指向创建它的 Widget;
- Elements 使用父子关系进行关联;
- Elements 有一个或多个子节点;
- Elements 也可以指向一个 RenderObject。
Elements 定义了视图部分的链接关系。
代理类
InheritedWidget
和 LayoutId
这些 Widgets 不会展现出任何的用户页面,但是它们会用来为其它的Widgets提供数据。
渲染类
大小尺寸
UI位置
布局/渲染方式
Row
,Column
,Stack
、Padding
、Align
、Opacity
、RawImage
等。
组件类
RaisedButton
, Scaffold
, Text
, GestureDetector
, Container。
Widgets 分类
- 组件,此分类不直接用于任何视觉渲染的部分。
- 渲染,此分类是直接用于屏幕渲染。
在Flutter中,整个系统都依赖 Widget / RenderObject 的状态。
- 使用 setState 方法,这个方法可以用于所有的 StatefulElement (注意,我这里面说的不是 StatefulWidget)。
- 使用通知,基于 ProxyElement来实现状态更新(如:InheritedWidget)。
- 大小、位置等修改;
- 需要重绘,如背景颜色修改、字体样式修改。
第1步,元素
WidgetsBinding被执行的时候, Flutter引擎首先考虑的是元素的变化。因为由建造者自己管理元素树,所以绑定控件的时候,会调用建造者的 buildScope 方法。这个方法中,会将要更改的元素存起来,稍后触发他们进行重建。
rebuild()
主要的原则如下:
大部分时候,触发元素的重建,会调用控件的
build()
方法(Widget build(BuildContext context) {….}),这个方法会返回一个新的控件。
如果元素没有子节点,这个元素就被创建完成,反之,会先创建子节点。
将新控件与元素引用的子控件进行比较:
如果可以被替换, 则更新,并保留子控件;
如果不可以被替换,子控件会被移除,并创建一个新的。
widget.createState()
方法,创建并关联对应的状态。RenderObjectElement 在元素被加载的时候,会创建一个 RenderObject, 并且会将这个对象加入到渲染树。
第2步,渲染对象
drawFrame()
的整个调用流程:
为每一个标记为脏的渲染对象计算新的布局(计算大小和几何形状);
使用渲染层,将所有需要重绘的对象重画出来;
将生成的场景数据发送给 Flutter 引擎,然后在屏幕中显示出来;
最后,Semantics 被发送更新到 Flutter 引擎。
window.onPointerDataPacket
方法发送出手势相关的事件, GestureBinding 会拦截并处理:
Flutter 引擎将屏幕位置转换成对应的坐标;
拿到坐标上所有渲染出来的View对应的 RenderObject;
然后遍历所有的RenderObject ,并把对应事件分发给他们;
RenderObject 会等待它能处理的时间并处理它。
dart abstract class Element extends DiagnosticableTree implements BuildContext { ... }
除了一下的两种情况下,其他时候 BuildContext 是没有任何用处的:
控件被重建的时候; 在StatefulWidget链接到你引用的上下文变量的状态。
获得对应于控件的渲染对象的基准;
获取RenderObject的大小;
访问树——这是实际使用的所有小部件通常实施该方法的(例如MediaQuery.of(Context),Theme.of(Context))。
小例子
我们知道 BulidContext 也是一个元素,我给你展示一种有关 BuildContext 的使用方法。下面的代码可以使 StatelessWidget 更新,但是并不使用 setState 方法,而是使用 BuildContext:
1 void main(){2 runApp(MaterialApp(home: TestPage(),));3 }45 class TestPage extends StatelessWidget {6 // final because a Widget is immutable (remember?)7 final bag = {"first": true};89 @override
10 Widget build(BuildContext context){
11 return Scaffold(
12 appBar: AppBar(title: Text('Stateless ??')),
13 body: Container(
14 child: Center(
15 child: GestureDetector(
16 child: Container(
17 width: 50.0,
18 height: 50.0,
19 color: bag["first"] ? Colors.red : Colors.blue,
20 ),
21 onTap: (){
22 bag["first"] = !bag["first"];
23 //
24 // This is the trick
25 //
26 (context as Element).markNeedsBuild();
27 }
28 ),
29 ),
30 ),
31 );
32 }
33 }
与执行 setState 方法相同,其核心都是执行 _element.markNeedsBuild()
方法。
结语
我认为了解Flutter的架构是很有趣的,所有东西都被设计为高效,可扩展且对将来的扩展开放。而且,诸如Widget,Element,BuildContext,RenderObject之类的关键概念并不总是显而易见。
我希望本文对你有用。
这几个Python技能实战,能让你少些1000行代码!
https://edu.csdn.net/topic/python115?utm_source=csdn_bw
热 文 推 荐
☞
点击阅读原文,参与中国开发者现状调查问卷!
Flutter 内幕:Flutter 在内部是如何工作的?相关推荐
- Flutter 笔记 | Flutter 核心原理(一)架构和生命周期
Flutter 架构 简单来讲,Flutter 从上到下可以分为三层:框架层.引擎层和嵌入层,下面我们分别介绍: 1. 框架层 Flutter Framework,即框架层.这是一个纯 Dart实现的 ...
- Flutter 动态化 | Flutter + Dart 三端一体化动态化平台实践
导读 FairPushy 是基于Flutter+Dart三端一体化打造的动态更新平台主要由Web + Server + Native全部使用Flutter+Dart编写,为Flutter动态化场景提供 ...
- Flutter 笔记 | Flutter 文件IO、网络请求、JSON、日期与国际化
文件IO操作 Dart的 IO 库包含了文件读写的相关类,它属于 Dart 语法标准的一部分,所以通过 Dart IO 库,无论是 Dart VM 下的脚本还是 Flutter,都是通过 Dart I ...
- 【Flutter】Flutter 调试 ( 调试控制相关功能 | 断点管理 | 代码运行控制 )
文章目录 一.调试控制相关功能 二.断点管理 三.代码运行控制 四.相关资源 一.调试控制相关功能 " Return 'main.dart' " 重新运行项目 ; " S ...
- 【Flutter】Flutter 拍照示例 ( 浮动按钮及点击事件 | 底部显示按钮组件 | 手势检测器组件 | 拍照并获取当前拍摄照片 | 从相册中选择图片 )
文章目录 一.浮动按钮及点击事件 二.底部显示按钮组件 三.手势检测器组件 四.image_picker 完整代码示例 五.相关资源 一.浮动按钮及点击事件 一般使用 Scaffold 组件作为界面的 ...
- 【老孟Flutter】Flutter 2的新功能
老孟导读:昨天期待已久的 Flutter 2.0 终于发布了, Flutter Web和Null安全性趋于稳定,Flutter桌面安全性逐渐转向Beta版! 原文链接:https://medium.c ...
- 【Flutter】Flutter 照片墙 ( Center 组件 | Wrap 组件 | ClipRRect 组件 | Stack 组件 | Positioned 组件 | 按钮组合组件 )
文章目录 一.Flutter 组件回顾 二.Center 组件 三.Wrap 组件 四.ClipRRect 组件 五.Stack 组件与 Positioned 组件 六.按钮组件组合 七.完整代码示例 ...
- Flutter开发Flutter与原生OC、Java的交互通信-2(48)
我们上一篇主要讲了Flutter与原生OC.Java的交互通信的机制:平台通道 只实现了Flutter 主动调用OC.Java的方向的通信.并没有实现OC.Java端主动调用Flutter的实现.这里 ...
- 【Flutter】Flutter 混合开发 ( Flutter 与 Native 通信 | 完整代码示例 )
文章目录 前言 一.Android 端完整代码示例 二.Flutter 端完整代码示例 三.相关资源 前言 前置博客 : [Flutter]Flutter 混合开发 ( Flutter 与 Nativ ...
最新文章
- Web前端开发标准规范
- GDCM:衍生系列DeriveSeries的测试程序
- 【牛客 - 369C】小A与欧拉路(bfs树的直径)
- C#高性能大容量SOCKET并发(八):通讯协议
- 查看路由器ADSL密码
- 2021.12.13(第一周) 实习周记lzhuan
- RGBA 图片格式转换 RGB 无损
- 是时候激活你的批判性思维了
- 程序员修炼之道 读书笔记2
- .log 合并或 .txt 合并
- MQ 消息队列问题整理
- 一禅小和尚的人生哲学
- 怎样设置用键盘开机?
- 消费者太穷不愿买手机?苹果的份额创新高,撕下国产手机遮羞布
- css情景动画,css3中的动画属性animation应用场景及编写代码教程
- 终极单词index 排序 G-H
- appcomat_v7报错解决方案
- 真正的标准化机房长啥样?
- 一年级课程表(3月28日-4月1日)
- mysql联合主键,也就是两个数据字段一起做主键的情况
热门文章
- [Linux 性能检测工具]IOSTAT
- 为什么要使用面向对象编程
- Hibernate常见错误
- python提高——多继承、静态方法、类方法、property属性、魔法属性
- pointnet2(pointnet++)源码复现
- leetcode python3 简单题232. Implement Queue using Stacks
- 数据密集型应用系统设计--数据复制
- 中国女子高尔夫球场市场趋势报告、技术动态创新及市场预测
- 中国水性植绒胶行业市场供需与战略研究报告
- 2021年中国云无线接入网(C-RAN)市场趋势报告、技术动态创新及2027年市场预测