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层通信相关服务的初始化,接收MethodChannel和SystemChannels传递过来的消息;
(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去执行layout,paint等一些列操作。
并且scheduleWarmUpFrame是立即去绘制的,因为启动的显示要越快越好。
后面的lockEvents也是为了等待预约帧绘制完成后再去执行其他的任务。
相关文章:
Flutter启动流程浅析
一,Mixins1,定义:Mixins 是一种在多个类层次结构中重用类代码的方法。个人理解:就是一个类,这个类有一些方法,其他类可以在不继承这个类的情况下使用这个类的方法。2,几个关键词(1&a…...
004:NumPy的应⽤-2
数组的运算 使⽤NumPy 最为⽅便的是当需要对数组元素进⾏运算时,不⽤编写循环代码遍历每个元素,所有的运算都会⾃动的⽮量化(使⽤⾼效的、提前编译的底层代码来对数据序列进⾏数学操作)。简单的说就是,NumPy 中的数学运…...
一文了解JAVA中同步、异步、阻塞和非阻塞
🏆今日学习目标: 🍀JAVA中同步、异步、阻塞和非阻塞 ✅创作者:林在闪闪发光 ⏰预计时间:30分钟 🎉个人主页:林在闪闪发光的个人主页 🍁林在闪闪发光的个人社区,欢迎你的加…...
查询股票交易日接口可以用C++实现查询当日成交吗?
用查询股票交易日接口可以自行查询各大交易网站或交易所的股票历史数据及行情数据,也可以用它 查询当日成交数据! 接下来小编就来分享一下用C实现查询当日成交代码: std::cout << " 查询当日成交: category 3 \n"; categ…...
java中常见的json库以及对应的用法
一、常见的json库 1、Jackson: Jackson是一个高性能、灵活性强的JSON库,提供了丰富的API,支持JSON和XML的数据解析和生成。它支持对Java对象进行序列化和反序列化,可以处理复杂的JSON格式数据。 导入的依赖 https://mvnrepository.com/ &…...
德赛西威NAV75*-SV731*导航升级(凯立德J30)实战
一、前言:升级导航德赛西威(2015年买的)地图几年没升级过了(之前自己折腾了一个)之前的启动是DSA2013(电子G已经无法升级数据文件了,本次只升级地图J30图资-凯立德)主程序版本&#…...
[USACO2023-JAN-Bronze] T1 LEADERS 题解
一、题目描述Farmer John 有 N 头牛 (2≤N≤10^5)。 每头牛有对应的品种:Guernsey or Holstein. 按照惯例,这些牛站成一排,编号从1到N。在某一天,每头牛写了一个数字, 第i头牛写的数字Ei明确地表示了一个范围,表示范围…...
第二章:unity性能优化之drawcall优化-1
目录 前言: 一、什么是drawcall 二、如何合批 1、什么是合批? 2、静态批处理 1、什么是静态批处理: 2、静态合批的规则 3、动态批处理 4、GPU Instancing 1、GPU instancing的定义 2、编写支持GPU instancing Shader步骤 5、…...
【2341. 数组能形成多少数对】
来源:力扣(LeetCode) 描述: 给你一个下标从 0 开始的整数数组 nums 。在一步操作中,你可以执行以下步骤: 从 nums 选出 两个 相等的 整数从 nums 中移除这两个整数,形成一个 数对 请你在 nu…...
[TPAMI‘21] Heatmap Regression via Randomized Rounding
paper: https://arxiv.org/pdf/2009.00225.pdf code: https://github.com/baoshengyu/H3R 总结:本文提出一套编解码方法: 编码:random-round整数化 激活点响应值表征小数部分,使得GT可以通过编码后的heatmap解码得到;…...
pytorch下tensorboard使用[远程服务器]
** 1、安装tensorboard ** pip install tensorboard可以不安装tensorflow,后续会有提示: TensorFlow installation not found - running with reduced feature set. 但是没有影响。 2、创建环境,导出数据 这一步由代码中的writer完成。 …...
CentOS下安装Nginx的详细步骤
1.安装依赖:yum -y install gcc gcc-c make libtool zlib zlib-devel openssl openssl-devel pcre pcre-devel 2.下载Nginx安装包:wget -c https://nginx.org/download/nginx-1.18.0.tar.gz 3.解压,进入解压目录: tar -zxvf nginx-1.18.0.…...
CSS编码规范
本篇文章是基于王叨叨大佬师父维护的文档梳理的,有兴趣可以去看一下原文CSS编码规范。 其实不管是HTML也好,还是CSS也好,有些规范其实是共通的。 1. 命名 class的命名应该偏向语义化,不是为了样式而去命名,而是通过…...
Linux下makefile 编译项目
文章目录1、规划makefile编写2、makefile文件2.1、根目录下common.mk2.2、config.mk2.3、根目录makefile2.4、其他目录下1、规划makefile编写 a、根目录下放三个文件: 1、makefile:是咱们编译项目的入口脚本,编译项目从这里开始,…...
Linux磁盘查看,使用(分区、格式化、挂载)
目录 0、观察磁盘分区状态:lsblk、blkid、parted 0.1 lsblk列出系统上的所有磁盘列表 0.2 blkid列出设备的UUID等参数 0.3 parted列出磁盘的分区表类型与分区信息 1、磁盘分区:gdisk、fdisk 1.1 fdisk 2、磁盘格式化(创建文件系统…...
走进WebGL
什么是 WebGL? WebGL 是一种跨平台、免版税的 API,用于在 Web 浏览器中创建 3D 图形。基于 OpenGL ES 2.0,WebGL 使用 OpenGL 着色语言 GLSL,并提供熟悉的标准 OpenGL API。因为它在 HTML5 Canvas 元素中运行,所以 We…...
Unity 中 Awake 和 Start 时机与 GameObject的关系
Awake和Start很相似,都是在脚本的初始阶段执行 但是有两点重要不同: Awake先执行Awake即便在脚本 disabled (即enabled false)时,也会执行,但是Start就不会执行了 对一个物体: 当初始没有激…...
1月份 GameFi 行业报告
Jan. 2023, DanielData Source: January Monthly GameFi Report在经历了艰难的一年之后,1 月是对加密货币市场最有利的月份。虽然可以说的大部分内容适用于其他看涨周期,但有几个统计数据令 1 月在区块链领域非常有趣。例如&#…...
JVM - 调优
目录 调什么,如何调 内存方面 线程方面 如何调优 调优的目标,策略和冷思考 JVM调优的目标 常见调优策略 JVM调优冷思考 调优经验与内存泄漏分析 JVM调优经验 内存泄露 调什么,如何调 内存方面 JVM需要的内存总大小各块内存分配,新生代、老年代、存活区选…...
flask配置https协议
感谢https://blog.csdn.net/qq_33934427/article/details/127456673,文中多有参考再实践一、要用https协议需要有ca证书,在windows10先下载windows版本openssl,地址如下https://share.weiyun.com/vfjVrMAb我是64位的选择下载完毕安装后配置环…...
Springboot 我随手封装了一个万能的导出excel工具,传什么都能导出
前言 如题,这个小玩意,就是不限制你查的是哪张表,用的是什么类。 我直接一把梭,嘎嘎给你一顿导出。 我知道,这是很多人都想过的, 至少我就收到很多人问过我这个类似的问题。 我也跟他们说了,但…...
【Linux详解】——进程控制(创建、终止、等待、替换)
📖 前言:本期介绍进程控制(创建、终止、等待、替换)。 目录🕒 1. 进程创建🕘 1.1 fork函数初识🕘 1.2 fork的返回值问题🕘 1.3 写时拷贝🕘 1.4 创建多个进程🕒…...
HummerRisk V0.9.1:操作审计增加百度云,增加主机检测规则及多处优化
HummerRisk V0.9.0发布:增加RBAC 资源拓扑图,首页新增检查的统计数据,云检测、漏洞、主机等模块增加规则,对象存储增加京东云,操作审计增加金山云,镜像仓库新增设置别名。 感谢社区中小伙伴们的反馈&#…...
Rust入门(十六):手写web服务器和线程池
这一章将实现一个手写的 web server 和 多线程的服务器,用到之前学到的所有特性 简单的web server 作为一个 web 服务器,我们首先要能接收到请求,目前市面上的 web 服务大多数都是基于 HTTP 和 HTTPS 协议的,而他们有是基于 TCP…...
数据结构——第二章 线性表(1)——顺序结构
线性表1. 线性表1.1 线性表的定义1.1.1 访问型操作1.1.2 加工型操作1.2 线性表的顺序存储结构1.2.1 定义顺序表数据类型方法11.2.2 定义顺序表数据类型方法21.3 顺序表的基本操作实现1.3.1 顺序表的初始化操作1.3.2 顺序表的插入操作1.3.3 顺序表的删除操作1.3.4 顺序表的更新操…...
YOLO 格式数据集制作
目录 1. YOLO简介 2.分割数据集准备 3.代码展示 整理不易,欢迎一键三连!!! 1. YOLO简介 YOLO(You Only Look Once)是一种流行的目标检测和图像分割模型,由华盛顿大学的 Joseph Redmon 和 Al…...
基于linux内核的驱动开发
1 字符设备驱动框架 1.1字符设备 定义:只能以一个字节一个字节的方式读写的设备,不能随机的读取设备中中的某一段数据,读取数据需要按照先后顺序。(字符设备是面向字节流的) 常见的字…...
找不到工作的测试员一大把,大厂却招不到优秀软件测试员?高薪难寻测试工程师。
测试工程师招了快一个月了,实在招不到合适的,已经在被解雇的边缘了。。。” 初级测试工程师非常多,但真正掌握测试思维、能力强的优秀测试太少了! 据我所知, 当下的测试人员不少状态都是这样的: 在工作中…...
buuctf Basic
buuctf Basic 1.Linux Labs 根据提示我们可以知道需要远程连接linux服务器,这里使用xshell进行如下配置 输入ssh的用户名root,密码123456 连接成功 构造命令 ls …/ 查看文件 查看flag cat …/flag.txt 为flag{8fee8783-1ed5-4b67-90eb-a1d603a0208…...
赛狐ERP|亚马逊产品缺货怎么办?该如何补救?
由于物流时效的延长,运输成本的增加,亚马逊的仓储限制等各种原因,断货问题很常成为亚马逊卖家的普遍困扰。那么亚马逊产品缺货应该怎么办!1、提高产品价格:除了卖自己的Listing此外,提高产品价格也是一种保…...
wordpress插件的使用/谷歌推广费用
一、硬件材料 1*Arduino UNO R3开发板 1*超声波HCSR04传感器模块 1*HC-05蓝牙模块 1*5V电池 二、硬件接线图 CSDN 赤鱼科技...
抚州网站网站建设/友链网站
我自己遇到了这个错误,在调查时,我来了一个mailing list post with this info:If you link a shared object containing IE-model access relocs,the objectwill have the DF_STATIC_TLS flag set. By the spec,this means that dlopenmight refuse to load it.查看…...
互动广告平台/seo项目培训
在修改mysql密码时,修改不生效 后来才发现 在mysql5.7以下版本就修改authentication_string update mysql.user set authentication_stringpassword(123456) where userroot and hostlocalhost;在mysql5.7以上版本就修改password update mysql.user set passwordpas…...
卡片式主题wordpress/目前推广平台都有哪些
阿里云函数 实现企业微信消息 回调地址验证 getRawBody(req, async function (err, body) {// resp.send(Hello)// 企业微信消息回调地验证if (req.method GET) {var sVerifyMsgSig req.queries.msg_signature;var sVerifyTimeStamp req.queries.timestamp;var sVerifyNonce…...
做龙之向导网站有用吗/域名注册 万网
前言 指令集架构(Instruction Set Architecture,缩写为ISA),是一组指令的集合,指令是指处理器进行操作的最小单元(譬如加减乘除操作或者读/写存储器数据)。指令集架构,有时简称为“架…...
复制别人网站做第一站/游戏代理是怎么赚钱的如何代理游戏
题目链接 虽然是看的别的人思路,但是做出来还是挺高兴的。 首先求环上最大字段和,而且不能是含有全部元素。本来我的想法是n个元素变为2*n个元素那样做的,这样并不好弄。实际可以求出最小值,总和-最小,就可以求出&…...