当前位置: 首页 > news >正文

Flutter启动流程浅析

一,Mixins

1,定义:Mixins 是一种在多个类层次结构中重用类代码的方法。

个人理解:就是一个类,这个类有一些方法,其他类可以在不继承这个类的情况下使用这个类的方法。

2,几个关键词

(1)mixin:一般使用mixin关键字定义可以混合的类;

(2)with:使用混合时用with关键字;

(1)on:添加限定条件,如下,意思是这个类只能被on的类或者子类混合

3,现在有这个情况:

class A{A(){print('A constructor');}log() => print('A log');
}
mixin AA on A{log() {print('AA log');}
}
mixin AB on A{@overridelog() {super.log();print('AB log');}
}
//C是A的子类,所以可以混合AA和AB
class C extends A with AA,AB{@overridelog() {super.log();print('C log');}
}

类C继承类A,并且混合了AA和AB,这几个类都有log方法,现在执行:

C().log(),会输出什么?

答案是:

I/flutter (16574): A constructor
I/flutter (16574): AA log
I/flutter (16574): AB log
I/flutter (16574): C log

因为混合类时,进行混合的多个类是线性的,所以他们的共有方法不会冲突,会优先调用后面混合的类的方法,所以混合的顺序决定了混合时相同的方法的处理逻辑。

像这个例子,执行的方法肯定是C.log,但因为super.log(),所以会调用AB.log,AA.log,因为AA.log没有super,所以没有调用A.log。

mixin的类不能有构造函数,不能继承其他类,因为mixin机制语义上是向一个类混入其他类的方法或者成员变量,使得我们可以在混合类中访问到混入类的方法或者属性。而混入其他类的构造函数实际上是没有意义的,因为不会有人手动去调用这个混入类的构造函数。

二,runAPP()

从这儿开始执行:

runApp(const MyApp());void runApp(Widget app) {//1,初始化//2,绑定根节点//3,绘制热身帧WidgetsFlutterBinding.ensureInitialized()..scheduleAttachRootWidget(app)..scheduleWarmUpFrame();
}

2.1,初始化

2.1.1,初始化函数

看下初始化函数,这儿是WidgetsFlutterBinding的单例过程,因为要确保flutter完成初始化并且只完成一次,这儿调用了自己的构造器

static WidgetsBinding ensureInitialized() {if (WidgetsBinding._instance == null)WidgetsFlutterBinding();return WidgetsBinding.instance;
}

对于WidgetsFlutterBinding这个类,继承了BindingBase并且混入了7个类:

class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding{

因为WidgetsFlutterBinding继承BindingBase,在调用构造方法前会先调用BindingBase的构造方法,所以看下父类BindingBase的构造方法:

BindingBase() {developer.Timeline.startSync('Framework initialization');//注意:这个方法,其他七个混入的类都有//绑定初始化initInstances();//在绑定初始化完成后进行服务初始化initServiceExtensions();developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});developer.Timeline.finishSync();
}

因为initInstances()方法,其他几个混入的类都有,所以WidgetsBinding 会覆盖前面混入的 initInstances(),所以 WidgetsBinding 的 initInstances() 会首先被调用,而 WidgetsBinding 还有其他几个混入的类的 initInstances 函数中都执行了

super.initInstances();

所以根据mixin的特性 initInstances 的执行顺序依次是:BindingBase -> GestureBinding -> SchedulerBinding -> ServicesBinding -> PaintingBinding -> SemanticsBinding -> RendererBinding -> WidgetsBinding,从而依次完成各个 Binding 的初始化相关工作。

2.1.2,七个Bindding

1,GestureBinding:手势事件绑定,主要处理触屏幕指针事件的分发以及事件最终回调处理。

2,SchedulerBinding:绘制调度绑定,和绘制渲染流程有关。

3,ServicesBinding:

(1)platform与flutter层通信相关服务的初始化,接收MethodChannelSystemChannels传递过来的消息;

(2)注册监听了flutter app的生命周期变化事件,根据生命周期状态决定是否允许发起绘制任务

4,PaintingBinding:和图片缓存有关。

5,SemanticsBinding:语义相关的初始化,主要就是描述应用程序中的UI信息,用于读屏使用。

6,RendererBinding:

(1)初始化了渲染管道PipelineOwner和RenderView,这就是我们屏幕真实显示的那个View,Flutter是单页面的UI框架,renderView就是这个时间点被初始化出来的。

(2)注意初始化方法中把renderView挂载到pipelineOwner.rootNode上,RenderView是RenderObject的子类,renderView其实是Render tree 的根节点,后续会讲到。

7,WidgetsBinding:

(1)初始化了一个BuildOwner对象,它主要是执行widget tree的build任务;

(2)执行了一些window的回调。

2.2,绑定根节点

2.2.1,接下来看绑定根节点的方法scheduleAttachRootWidget()

void scheduleAttachRootWidget(Widget rootWidget) {Timer.run(() {//传入了rootWidget,这个rootWidget就是我们构建的MyApp(),这个方法主要是绑定根节点attachRootWidget(rootWidget);});
}
///    创建树的根节点
///    这个方法主要是将根widget和根element和根renderObject关联起来
///    并将唯一的BuildOwner对象引用作为根对象的持有对象,通过继承关系层层传递
void attachRootWidget(Widget rootWidget) {final bool isBootstrapFrame = renderViewElement == null;_readyToProduceFrames = true;//1,初始化了一个RenderObjectToWidgetAdapter对象//RenderObjectToWidgetAdapter继承RenderObjectWidget,所以本质是一个widget,可以说创建了widget树的根节点//调用attachToRenderTree_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(container: renderView,//之前介绍的在RendererBinding初始化的对象,也是renderObject的根节点debugShortDescription: '[root]',child: rootWidget,//根widget,就是runAPP传入的MyApp()).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);if (isBootstrapFrame) {SchedulerBinding.instance.ensureVisualUpdate();}
}RenderView get renderView => _pipelineOwner.rootNode! as RenderView;
/// 这儿创建了element树的根节点,并返回了element
/// 也就是_renderViewElement其实就是element树的根
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {if (element == null) {owner.lockState(() {//创建element树的根节点 RenderObjectToWidgetElementelement = createElement();assert(element != null);//绑定BuildOwner,通过 BuildOwner 构建需要构建的 elementelement!.assignOwner(owner);});//BuildOwner构建elementowner.buildScope(element!, () {element!.mount(null, null);});} else {element._newWidget = this;element.markNeedsBuild();}return element!;
}

现在为止,出现了三棵树的根:

widget树:RenderObjectToWidgetAdapter对象;

element树:RenderObjectToWidgetElement对象;

renderObject树:RenderView对象。

接下来看mount方法,作用就是将element添加到树种的parent节点上。

//1,RenderObjectToWidgetElement:
@override
void mount(Element? parent, Object? newSlot) {assert(parent == null);//先看下mount方法都做了什么super.mount(parent, newSlot);//在这儿触发了_rebuild方法,该方法调用了updateChild方法_rebuild();assert(_child != null);
}//2, RenderObjectElement://创建了renderObject@overridevoid mount(Element? parent, Object? newSlot) {super.mount(parent, newSlot);_renderObject = (widget as RenderObjectWidget).createRenderObject(this);attachRenderObject(newSlot);_dirty = false;}//因为widget是RenderObjectToWidgetAdapter类型,其createRenderObject返回的了container//这个container就是我们之前传的renderViewRenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;//将renderObject挂载到RenderObject Tree上void attachRenderObject(Object? newSlot) {_slot = newSlot;_ancestorRenderObjectElement = _findAncestorRenderObjectElement();_ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);final ParentDataElement<ParentData>? parentDataElement = _findAncestorParentDataElement();if (parentDataElement != null)_updateParentData(parentDataElement.widget as ParentDataWidget<ParentData>);}//3, Element://首先执行的mount,主要是设置parent,slot等的值void mount(Element? parent, Object? newSlot) {_parent = parent;//element树上的父节点_slot = newSlot;//子element在父节点上的位置_lifecycleState = _ElementLifecycle.active;_depth = _parent != null ? _parent!.depth + 1 : 1;//element树的深度if (parent != null) {_owner = parent.owner;}final Key? key = widget.key;if (key is GlobalKey) {owner!._registerGlobalKey(key, this);}_updateInheritance();attachNotificationTree();}

以上mount执行顺序为Element.mount->RenderObjectElement.mount->RenderObjectToWidgetElement.mount,执行完毕后在Element.mount

函数这里会触发_rebuild();在_rebuild()里面我们看到了Element.updateChild()。

  void _rebuild() {try {//_child为null,第二个入参就是MyApp()_child = updateChild(_child, (widget as RenderObjectToWidgetAdapter<T>).child, _rootChildSlot);} catch (exception, stack) {...}}

Element.updateChild()其实是element很重要的一个方法,作为widget和renderobject的桥梁,这个方法会根据不同的条件去创建、更新、删除element。

这里可见就是build子widget,这里就是build MyApp()

  //这里传的child是null
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {final Element newChild;if (child != null) {//.......} else {newChild = inflateWidget(newWidget, newSlot);}return newChild;}

updateChild里有调用inflateWidget方法,inflateWidget这个函数

  Element inflateWidget(Widget newWidget, Object? newSlot) {...try {...//这儿就是为widget创建对应的element对象,所以widget和element关联起来了final Element newChild = newWidget.createElement();//又调用了mount方法,将子element挂载到当前element结点上newChild.mount(this, newSlot);return newChild;} finally {...}}

在这个函数里面就会触发 createElement去创建element ,element又会去调用对应类的mount函数。

经过一系列的流程之后,又会回到inflateWidget这个函数中,再次触发新的mount函数,形成一个层层调用,不断创建parentElement到childElement的过程,这个过程完成了element tree的构建。

2.2.2,三棵树简介

WIdget树:存放渲染内容

element树:分离 WidgetTree 和真正的渲染对象的中间层, WidgetTree 用来描述对应的Element 属性,同时持有Widget和RenderObject,存放上下文信息,通过它来遍历视图树,支撑UI结构。

RenderObject树:用于应用界面的布局和绘制,负责真正的渲染,保存了元素的大小,布局等信息,实例化一个 RenderObject 是非常耗能的。

总结:

当应用启动时Flutter会遍历并创建所有的 Widget 形成 Widget Tree,通过调用 Widget 上的 createElement() 方法创建每个 Element 对象,形成 Element Tree。最后调用 Element 的 createRenderObject() 方法创建每个渲染对象,形成一个 Render Tree。

为什么要这样?

widget的重建开销非常小,所以可以随意的重建,因为它不一会导致页面重绘,并且它也不一定会常常变化。 而renderObject如果频繁创建和销毁成本就很高了,对性能的影响比较大,因此它会缓存所有页面元素,只是当这些元素有变化时才去重绘页面。

而判断页面有无变化就依靠element了,每次widget变化时element会比较前后两个widget,只有当某一个位置的Widget和新Widget不一致,才会重新创建Element和widget;其他时候则只会修改renderObject的配置而不会进行耗费性能的RenderObject的实例化工作了。

2.3,绘制热身帧

void scheduleWarmUpFrame() {if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)return;_warmUpFrame = true;final TimelineTask timelineTask = TimelineTask()..start('Warm-up frame');final bool hadScheduledFrame = _hasScheduledFrame;Timer.run(() {assert(_warmUpFrame);//1,handleBeginFrame(null);});Timer.run(() {assert(_warmUpFrame);//2,handleDrawFrame();resetEpoch();_warmUpFrame = false;if (hadScheduledFrame)scheduleFrame();});lockEvents(() async {await endOfFrame;timelineTask.finish();});
}

scheduleWarmUpFrame绘制的是根节点RenderObject对应的TransformLayer对象,调用handleBeginFrame和handleDrawFrame方法,通过pipelineOwner去执行layoutpaint等一些列操作。

并且scheduleWarmUpFrame是立即去绘制的,因为启动的显示要越快越好。

后面的lockEvents也是为了等待预约帧绘制完成后再去执行其他的任务。

相关文章:

Flutter启动流程浅析

一&#xff0c;Mixins1&#xff0c;定义&#xff1a;Mixins 是一种在多个类层次结构中重用类代码的方法。个人理解&#xff1a;就是一个类&#xff0c;这个类有一些方法&#xff0c;其他类可以在不继承这个类的情况下使用这个类的方法。2&#xff0c;几个关键词&#xff08;1&a…...

004:NumPy的应⽤-2

数组的运算 使⽤NumPy 最为⽅便的是当需要对数组元素进⾏运算时&#xff0c;不⽤编写循环代码遍历每个元素&#xff0c;所有的运算都会⾃动的⽮量化&#xff08;使⽤⾼效的、提前编译的底层代码来对数据序列进⾏数学操作&#xff09;。简单的说就是&#xff0c;NumPy 中的数学运…...

一文了解JAVA中同步、异步、阻塞和非阻塞

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;JAVA中同步、异步、阻塞和非阻塞 ✅创作者&#xff1a;林在闪闪发光 ⏰预计时间&#xff1a;30分钟 &#x1f389;个人主页&#xff1a;林在闪闪发光的个人主页 &#x1f341;林在闪闪发光的个人社区&#xff0c;欢迎你的加…...

查询股票交易日接口可以用C++实现查询当日成交吗?

用查询股票交易日接口可以自行查询各大交易网站或交易所的股票历史数据及行情数据&#xff0c;也可以用它 查询当日成交数据&#xff01; 接下来小编就来分享一下用C实现查询当日成交代码&#xff1a; std::cout << " 查询当日成交: category 3 \n"; categ…...

java中常见的json库以及对应的用法

一、常见的json库 1、Jackson: Jackson是一个高性能、灵活性强的JSON库&#xff0c;提供了丰富的API&#xff0c;支持JSON和XML的数据解析和生成。它支持对Java对象进行序列化和反序列化&#xff0c;可以处理复杂的JSON格式数据。 导入的依赖 https://mvnrepository.com/ &…...

德赛西威NAV75*-SV731*导航升级(凯立德J30)实战

一、前言&#xff1a;升级导航德赛西威&#xff08;2015年买的&#xff09;地图几年没升级过了&#xff08;之前自己折腾了一个&#xff09;之前的启动是DSA2013&#xff08;电子G已经无法升级数据文件了&#xff0c;本次只升级地图J30图资-凯立德&#xff09;主程序版本&#…...

[USACO2023-JAN-Bronze] T1 LEADERS 题解

一、题目描述Farmer John 有 N 头牛 (2≤N≤10^5)。 每头牛有对应的品种&#xff1a;Guernsey or Holstein. 按照惯例&#xff0c;这些牛站成一排&#xff0c;编号从1到N。在某一天&#xff0c;每头牛写了一个数字, 第i头牛写的数字Ei明确地表示了一个范围&#xff0c;表示范围…...

第二章:unity性能优化之drawcall优化-1

目录 前言&#xff1a; 一、什么是drawcall 二、如何合批 1、什么是合批&#xff1f; 2、静态批处理 1、什么是静态批处理&#xff1a; 2、静态合批的规则 3、动态批处理 4、GPU Instancing 1、GPU instancing的定义 2、编写支持GPU instancing Shader步骤 5、…...

【2341. 数组能形成多少数对】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给你一个下标从 0 开始的整数数组 nums 。在一步操作中&#xff0c;你可以执行以下步骤&#xff1a; 从 nums 选出 两个 相等的 整数从 nums 中移除这两个整数&#xff0c;形成一个 数对 请你在 nu…...

[TPAMI‘21] Heatmap Regression via Randomized Rounding

paper: https://arxiv.org/pdf/2009.00225.pdf code: https://github.com/baoshengyu/H3R 总结&#xff1a;本文提出一套编解码方法&#xff1a; 编码&#xff1a;random-round整数化 激活点响应值表征小数部分&#xff0c;使得GT可以通过编码后的heatmap解码得到&#xff1b…...

pytorch下tensorboard使用[远程服务器]

** 1、安装tensorboard ** pip install tensorboard可以不安装tensorflow&#xff0c;后续会有提示&#xff1a; TensorFlow installation not found - running with reduced feature set. 但是没有影响。 2、创建环境&#xff0c;导出数据 这一步由代码中的writer完成。 …...

CentOS下安装Nginx的详细步骤

1.安装依赖&#xff1a;yum -y install gcc gcc-c make libtool zlib zlib-devel openssl openssl-devel pcre pcre-devel 2.下载Nginx安装包&#xff1a;wget -c https://nginx.org/download/nginx-1.18.0.tar.gz 3.解压,进入解压目录&#xff1a; tar -zxvf nginx-1.18.0.…...

CSS编码规范

本篇文章是基于王叨叨大佬师父维护的文档梳理的&#xff0c;有兴趣可以去看一下原文CSS编码规范。 其实不管是HTML也好&#xff0c;还是CSS也好&#xff0c;有些规范其实是共通的。 1. 命名 class的命名应该偏向语义化&#xff0c;不是为了样式而去命名&#xff0c;而是通过…...

Linux下makefile 编译项目

文章目录1、规划makefile编写2、makefile文件2.1、根目录下common.mk2.2、config.mk2.3、根目录makefile2.4、其他目录下1、规划makefile编写 a、根目录下放三个文件&#xff1a; 1、makefile&#xff1a;是咱们编译项目的入口脚本&#xff0c;编译项目从这里开始&#xff0c;…...

Linux磁盘查看,使用(分区、格式化、挂载)

目录 0、观察磁盘分区状态&#xff1a;lsblk、blkid、parted 0.1 lsblk列出系统上的所有磁盘列表 0.2 blkid列出设备的UUID等参数 0.3 parted列出磁盘的分区表类型与分区信息 1、磁盘分区&#xff1a;gdisk、fdisk 1.1 fdisk 2、磁盘格式化&#xff08;创建文件系统…...

走进WebGL

什么是 WebGL&#xff1f; WebGL 是一种跨平台、免版税的 API&#xff0c;用于在 Web 浏览器中创建 3D 图形。基于 OpenGL ES 2.0&#xff0c;WebGL 使用 OpenGL 着色语言 GLSL&#xff0c;并提供熟悉的标准 OpenGL API。因为它在 HTML5 Canvas 元素中运行&#xff0c;所以 We…...

Unity 中 Awake 和 Start 时机与 GameObject的关系

Awake和Start很相似&#xff0c;都是在脚本的初始阶段执行 但是有两点重要不同&#xff1a; Awake先执行Awake即便在脚本 disabled &#xff08;即enabled false&#xff09;时&#xff0c;也会执行&#xff0c;但是Start就不会执行了 对一个物体&#xff1a; 当初始没有激…...

1月份 GameFi 行业报告

Jan. 2023&#xff0c; DanielData Source&#xff1a; January Monthly GameFi Report在经历了艰难的一年之后&#xff0c;1 月是对加密货币市场最有利的月份。虽然可以说的大部分内容适用于其他看涨周期&#xff0c;但有几个统计数据令 1 月在区块链领域非常有趣。例如&#…...

JVM - 调优

目录 调什么,如何调 内存方面 线程方面 如何调优 调优的目标,策略和冷思考 JVM调优的目标 常见调优策略 JVM调优冷思考 调优经验与内存泄漏分析 JVM调优经验 内存泄露 调什么,如何调 内存方面 JVM需要的内存总大小各块内存分配&#xff0c;新生代、老年代、存活区选…...

flask配置https协议

感谢https://blog.csdn.net/qq_33934427/article/details/127456673&#xff0c;文中多有参考再实践一、要用https协议需要有ca证书&#xff0c;在windows10先下载windows版本openssl&#xff0c;地址如下https://share.weiyun.com/vfjVrMAb我是64位的选择下载完毕安装后配置环…...

浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)

✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义&#xff08;Task Definition&…...

进程地址空间(比特课总结)

一、进程地址空间 1. 环境变量 1 &#xff09;⽤户级环境变量与系统级环境变量 全局属性&#xff1a;环境变量具有全局属性&#xff0c;会被⼦进程继承。例如当bash启动⼦进程时&#xff0c;环 境变量会⾃动传递给⼦进程。 本地变量限制&#xff1a;本地变量只在当前进程(ba…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来

一、破局&#xff1a;PCB行业的时代之问 在数字经济蓬勃发展的浪潮中&#xff0c;PCB&#xff08;印制电路板&#xff09;作为 “电子产品之母”&#xff0c;其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透&#xff0c;PCB行业面临着前所未有的挑战与机遇。产品迭代…...

OkHttp 中实现断点续传 demo

在 OkHttp 中实现断点续传主要通过以下步骤完成&#xff0c;核心是利用 HTTP 协议的 Range 请求头指定下载范围&#xff1a; 实现原理 Range 请求头&#xff1a;向服务器请求文件的特定字节范围&#xff08;如 Range: bytes1024-&#xff09; 本地文件记录&#xff1a;保存已…...

Caliper 配置文件解析:config.yaml

Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

全志A40i android7.1 调试信息打印串口由uart0改为uart3

一&#xff0c;概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本&#xff1a;2014.07&#xff1b; Kernel版本&#xff1a;Linux-3.10&#xff1b; 二&#xff0c;Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01)&#xff0c;并让boo…...

Python ROS2【机器人中间件框架】 简介

销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

JVM 内存结构 详解

内存结构 运行时数据区&#xff1a; Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器&#xff1a; ​ 线程私有&#xff0c;程序控制流的指示器&#xff0c;分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 ​ 每个线程都有一个程序计数…...

给网站添加live2d看板娘

给网站添加live2d看板娘 参考文献&#xff1a; stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下&#xff0c;文章也主…...