Flutter 08 三棵树(Widgets、Elements和RenderObjects)

一、Flutter三棵树背景

1.1 先思考一些问题

1. Widget与Element是什么关系?它们是一一对应的还是怎么理解?

2. createState 方法在什么时候调用?state 里面为啥可以直接获取到 widget 对象?

3. Widget 频繁更改创建是否会影响性能?复用和更新机制是什么样的?

4. Widget、Element、RenderObject 三棵树之间的关系是怎样的?

1.2 Flutter中Dom树

如何理解 DOM 树这个概念

它由页面中每一个控件组成,这些控件所形成的一种天然的嵌套关系使其可以表示为 “树” 结构,可以将 这个概念应用在 Flutter 中。

例如默认的计数器应用的结构如下图:

二、Flutter中的三棵树 

即Widget树、Element树和RenderObject树。

Widget树:控件的配置信息,不涉及渲染,更新代价极低。

RenderObject树:真正的UI渲染树,负责渲染UI,更新代价极大。

Element树:Widget树和RenderObject树之间的粘合剂,负责将Widget树的变更以最低的代价映射到 RenderObject树上。

Widget 树

我们平时用 Widget 使用声明式的形式写出来的界面,可以理解为 Widget 树,这是要介绍的第一棵树。 Widget的功能是“描述一个UI元素的配置数据”,它就是说,Widget其实并不是表示最终绘制在设备屏幕 上的显示元素,而它只是描述显示元素的一个配置数据。

RenderObject 树

Flutter 引擎需要把我们写的 Widget 树的信息都渲染到界面上,这样人眼才能看到,跟渲染有关的当然 有一颗渲染树 RenderObject tree,这是第二颗树,渲染树节点叫做 RenderObject,这个节点里面处理 布局、绘制相关的事情。

这两个树的节点并不是一一对应的关系,有些 Widget是要显示的,有些 Widget ,比如那些继承自 StatelessWidget & StatefulWidget 的 Widget 只是将其他 Widget 做一个组合,这些 Widget 本身并不 需要显示,因此在 RenderObject 树上并没有相对应的节点。

Element 树

Widget 树是非常不稳定的,动不动就执行 build方法,一旦调用 build 方法意味着这个 Widget 依赖的 所有其他 Widget 都会重新创建,如果 Flutter 直接解析 Widget树,将其转化为 RenderObject 树来直 接进行渲染,那么将会是一个非常消耗性能的过程,那对应的肯定有一个东西来消化这些变化中的不 便,来做cache。

因此,这里就有另外一棵树 Element 树。Element 树这一层将 Widget 树的变化(类似 React 虚拟 DOM diff)做了抽象,可以只将真正需要修改的部分同步到真实的 RenderObject 树中,最大程度降低 对真实渲染视图的修改,提高渲染效率,而不是销毁整个渲染视图树重建。

三、Flutter三棵树关系

三棵树架构关系

三棵树架构图:

总结的关系:

widget 树和 Element 树节点是一一对应关系,每一个 Widget 都会有其对应的 Element,但是 RenderObject 树则不然,只有需要渲染的 Widget 才会有对应的节点。

Element 树相当于一个中间层,大管家,它对 Widget 和 RenderObject 都有引用。

当 Widget 不断变化的时候,将新 Widget 拿到 Element 来进行对比,看一下和之前保留的 Widget 类 型和 Key 是否相同,如果都一样,那完全没有必要重新创建 Element 和 RenderObject,只需要更新里 面的一些属性即可,这样可以以最小的开销更新 RenderObject,引擎在解析 RenderObject 的时候,发 现只有属性修改了,那么也可以以最小的开销来做渲染。

简单总结一下:

Widget 树就是配置信息的树,我们平时写代码写的就是这棵树。

RenderObject 树是渲染树,负责计算布局,绘制,Flutter 引擎就是根据这棵树来进行渲染的。

Element 树作为中间者,管理着将 Widget 生成 RenderObject和一些更新操作。

举个通俗例子:

UI 渲染就像盖一栋大楼,Widget 代表图纸,表示我们想造怎样的大楼,RenderObject 是根据图纸干活 的工人,而 Element 是监工,负责协调各方资源,统一调配,外部人员有事需要先找这个监工。 

三者创建关系图:

用文字描述三者创建关系

首先是 Widget 通过调用其 createElement 方法创建出 Element 对象。

Element 继续调用其持有 Widget 对象(Stateless)或 State 对象(Stateful)的 build 方法创建其子 widget 对象。往复循环,继续创建子Element,子 Element 持有父 Element 的引用,因此最终会形成 出一颗 Element 树。

对于有 layout/paint 的能力控件,会创建 RenderObjectElement,在该 Element 的 mount 阶段会创 建其对应的 RenderObject 对象。 

四、运行时三棵树结构

三棵树结构

认识了三棵树之后,那Flutter是如何创建布局的?以及三棵树之间他们是如何协同的呢?

接下来就让我们通过一个简单的例子来剖析下它们内在的协同关系:

class Tree extends StatelessWidget {@overrideWidget build(BuildContext context) {return Container(color: Colors.brown,child: Row(children: [new Image.network("https://p1.ssl.qhmsg.com/dr/220__/t01d5ccfbf9d4500c75.jpg",width: 100,height: 100,),new Text("从网络加载图片",style: TextStyle(fontSize: 16),),],),);}
}

当runApp()被调用时,第一时间会在后台发生以下事件:

1.Flutter会构建包含Widget(Container,Row,Image,Text)的Widgets树;

2.Flutter遍历Widget树,然后根据其中的Widget调用createElement()来创建相应的Element对 象,最后将这些对象组建成Element树;

3.接下来会创建第三个树,这个树中包含了与Widget对应的Element通过createRenderObject() 创建的RenderObject;

具体Flutter经过这三个步骤后的状态:

总结一下三棵树结构:

1. Widget Tree: Widget 是 Flutter 面向开发者的上层接口,我们通过 widget 的层层嵌套,会形成 一颗 Widget 树,一个 Widget 可在多个位置复用。Flutter Framework 层为我们提供了一些常用 的包装或者容器的 Widget,比如 Container,其内部继续嵌套了其他 Widget,如 Padding、Align 等等。所以,开发者编写的 Widget 树和实际生成的 Widget 树都会略有差别。如图中虚线圆形标 注的 ColorBox、RawImage 等。

2. Element Tree :每一个 Widget 都会对应一个 Element,只不过 Element 分类不同。

3. RenderObject Tree:RenderObject 只负责最终的测量、布局和绘制,因此最终的 RenderObject Tree 是 Element Tree 剔除掉哪些包装,最后组织而成的 Tree。

为何搞这多树?

分层:开发只关注widget

1. Framework 将复杂的内部设计、渲染逻辑与开发接口隔离开,应用层只需关注 Widget 开发即 可。

高效:提交绘制效率

1. Tree 最大的共同特点就是快取,因为 Element、RenderObject 销毁重建成本很高,一旦可以复用 ,那么快取可以大幅减少这种开销。

2. 比如:当 Element 不需要重建时,更新 Widget 的引用就可以了;Layer Tree 的设计是将绘制图层 分开,方便提取和合成,合成层中的 transform 和 opacity 效果,都只是几何变换、透明度变换 等,不会触发 layout 和 paint,直接由 GPU 完成即可。

五、三棵树的作用介绍

1. 简而言之是为了性能,为了复用Element从而减少频繁创建和销毁RenderObject。

2. 因为实例化一个RenderObject的成本是很高的,频繁的实例化和销毁RenderObject对性能的影响 比较大,所以当Widget树改变的时候,Flutter使用Element树来比较新的Widget树和原来的 Widget树:

//framework.dart
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {if (newWidget == null) {if (child != null)deactivateChild(child);return null;}Element newChild;if (child != null) {assert(() {final int oldElementClass = Element._debugConcreteSubtype(child);final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);hasSameSuperclass = oldElementClass == newWidgetClass;return true;}());if (hasSameSuperclass && child.widget == newWidget) {if (child.slot != newSlot)updateSlotForChild(child, newSlot);newChild = child;} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {if (child.slot != newSlot)updateSlotForChild(child, newSlot);child.update(newWidget);assert(child.widget == newWidget);assert(() {child.owner._debugElementWasRebuilt(child);return true;}());newChild = child;} else {deactivateChild(child);assert(child._parent == null);newChild = inflateWidget(newWidget, newSlot);}} else {newChild = inflateWidget(newWidget, newSlot);}assert(() {if (child != null)_debugRemoveGlobalKeyReservation(child);final Key key = newWidget?.key;if (key is GlobalKey) {key._debugReserveFor(this, newChild);}return true;}());return newChild;
}//...static bool canUpdate (Widget oldWidget, Widget newWidget) {return oldWidget.runtimeType == newWidget.runtimeType&& oldWidget.key == newWidget.key;
}

1. 如果某一个位置的Widget和新Widget不一致,才需要重新创建Element;

2. 如果某一个位置的Widget和新Widget一致时(两个widget相等或runtimeType与key相等),则只需 要修改RenderObject的配置,不用进行耗费性能的RenderObject的实例化工作了;

3. 因为Widget是非常轻量级的,实例化耗费的性能很少,所以它是描述APP的状态(也就是 configuration)的最好工具;

4. 重量级的RenderObject(创建十分耗费性能)则需要尽可能少的创建,并尽可能的复用; 

更新时三棵树操作

因为Widget是不可变的,当某个Widget的配置改变的时候,整个Widget树都需要被重建。

1. 例如当我们改变一个Text文本的时候,框架就会触发一个重建整个Widget树的动作。

2. 因为有了Element的存在,Flutter会比较新的Widget树中的第一个Widget和之前的 Widget。

3. 接下来比较Widget树中之后Widget和之前Widget,以此类推,直到Widget树比较完成。

@override
Widget build(BuildContext context) {return Container(color: Colors.brown,height: double.infinity,child: Row(children: [new Image.network("https://p1.ssl.qhmsg.com/dr/220__/t01d5ccfbf9d4500c75.jpg",width: 100,height: 100,),new Text("改变UI",style: TextStyle(fontSize: 16),),],),);
}

Flutter遵循一个最基本的原则:

判断新的Widget和老的Widget是否是同一个类型:

如果不是同一个类型,那就把Widget、Element、RenderObject分别从它们的树(包括它们的子树)上 移除,然后创建新的对象;

如果是一个类型,那就仅仅修改RenderObject中的配置,然后继续向下遍历。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/162639.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何释放React Hooks的力量

React是用于构建用户界面的一个流行JavaScript库,多年来已经发生了重大变化和改进。React中最具颠覆性的新特性之一就是引入了Hooks。React Hooks彻底改变了开发者在函数组件中管理状态和生命周期的方式。在这个全面的指南中,将深入研究React Hooks的世界…

Linux友人帐之网络编程基础www服务器

一、概述 1.1www基础 WWW(World Wide Web,万维网)是一种分布式、全球性的信息服务系统,是集成Internet、Web浏览器和Web服务器等技术而形成的一个庞大的、涉及全球的信息网络。 用户在浏览器中输入www.cqvie.edu.cn访问该网站主页…

行政处罚有哪些?

一、行政处罚的定义 行政处罚,是指行政机关依法对违反行政管理秩序的公民、法人或者其他组织,以减损权益或者增加义务的方式予以惩戒的行为。这是首次以立法的形式明确“行政处罚”的定义。 二、行政处罚与行政处分的区别 行政处分是指国家行政机关对其…

快速解决mfc140u.dll丢失问题,找不到mfc140u.dll修复方法分享

在计算机使用过程中,我们可能会遇到各种问题,其中之一就是某些dll文件丢失。最近,我就遇到了一个关于mfc140u.dll丢失的问题。mfc140u.dll是Microsoft Foundation Class(MFC)库中的一个动态链接库文件,它包…

富士康推进印度制造的计划倍速,中国制造iPhone占比下滑较快

日前知名苹果分析师郭明錤指出今年印度制造的iPhone占比达到12%,比预期的7%增加四成以上,比去年的占比更是增加2倍,这主要是因为富士康快速扩张了印度工厂的产能。 郭明錤指出印度制造的iPhone主要是由富士康贡献的,今年印度制造的…

数据结构-顺序表

1.线性表 线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构,也就说是连续的一条直线…

python_寻找N字型下跌

目录 写在前面: 思路拆解: 代码: 验证: 写在前面: 1 由于日线骗线多,本文寻找N字型下跌形态在周线级别操作 2 N字型下跌形态,技术辅助寻找的点: 1)左连阴 连阳 …

GoLong的学习之路(番外)如何使用依赖注入工具:wire

我为什么要直接写番外呢?其原因很简单。项目中会使用,其实在这里大家就可以写一些项目来了。 依赖注入的工具本质思想其实都大差不差。无非控制反转和依赖注入。 文章目录 控制反转为什么需要依赖注入工具 wire的概念提供者(provider&#x…

开放式耳机能保护听力吗?开放式耳机有哪些优缺点?

先说答案,开放式耳机是可以保护听力的! 想要了解开放式耳机是否能保护听力,就要先知道什么是开放式耳机,开放式耳机是一种无需入耳,并且使用时不会堵塞耳道,也不会隔绝外界声音的蓝牙耳机。 一、开放式耳…

Leetcode刷题详解——组合

1. 题目链接:77. 组合 2. 题目描述: 给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1: 输入:n 4, k 2 输出: [[2,4],[3,4],[2,3],[1,2],[1,3],[…

性能测试知多少---了解前端性能

我的上一篇博文中讲到了响应时间,我们在做性能测试时,能过工具可以屏蔽客户端呈现时间,通过局域网的高宽带可以忽略数据传输速度的障碍。这并不是说他们不会对系统造成性能影响。相反,从用户的感受来看,虽然传输速度受…

深度学习_9_图片分类数据集

散装代码: import matplotlib.pyplot as plt import torch import torchvision from torch.utils import data from torchvision import transforms from d2l import torch as d2ld2l.use_svg_display()# 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式…