我们在开发Flutter的时候经常会有以下疑问:
Flutter中有三棵树: Widget, Element以及RenderObject,它们之间的关系如下图所示

Widget 描述UI数据的组件,作为一个生产者创建Element和RenderObject。相当于是一个配置文件,为构建Element树提供模板
Element UI真正的节点,主要管理Widget和State,通过Widget和State创建Element树,同时也管理RenderObject的构建
RenderObject就是真正渲染到屏幕上的组件。只有当Element是RenderObjectElement的时候才会通过RenderObjectWidget创建RenderObject,因此Element树跟RenderObject树并不是一一对应的
这里另外单独提下BuildOwner,这是一个调度中心。由它发起树构建和更新以及树的销毁。
element树的构建过程先从runApp说起:

owner.buildScope(element!, () {
element!.mount(null, null);
});
所以真正开始构建是从buildScope开始,buildScope传入了两个参数:
@override
void mount(Element? parent, dynamic newSlot) {
assert(parent == null);
super.mount(parent, newSlot);
_rebuild();
}
RenderObjecToWidgetElement由于是最顶层的父节点,因此parent传的是null。
@override
void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot);
...
_renderObject = widget.createRenderObject(this);
...
}
由于RenderObjectToWidgetElement继承的是RootRenderObjectElement,而RootRenderObjectElement又是继承的RenderObjectElement。所以会在mount的时候调用createRenderObject方法,创建对应的RenderObject。
接下来会调用rebuild方法:
void _rebuild() {
try {
//widget.child就是runApp时候传入的我们APP的根布局
_child = updateChild(_child, widget.child, _rootChildSlot);
...
} catch (exception, stack) {
....
}
}
会执行updateChild方法
Element? updateChild(Element? child, Widget? newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null)
//如果当前widget是空的,但是element不是空的,那么就把element设置为inactive状态
//这里仅仅是变成inactive状态,并不会马上执行dispose方法,稍后会讲到什么情况下执行dispose方法
deactivateChild(child);
return null;
}
final Element newChild;
if (child != null) {
//如果element和widget都不为空,那么下面根据不同的条件会做增删改查操作
bool hasSameSuperclass = true;
...
if (hasSameSuperclass && child.widget == newWidget) {
//如果当前element的widget和传入的新widget是同一个
//那么只判断slot是否一致,如果不一致更新下slot信息
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
//如果当前element的widget和传入的新widget不是同一个,但是属于同一个Widget类
if (child.slot != newSlot)
//先判断slot是否一致,不一致的话先更新slot信息
updateSlotForChild(child, newSlot);
//更新element持有的element对象
child.update(newWidget);
assert(child.widget == newWidget);
assert(() {
child.owner!._debugElementWasRebuilt(child);
return true;
}());
newChild = child;
} else {
//如果element所持有的widget和新传入的widget不属于同一个Widget类,那么将当前的Element先deactive掉
//同理这个地方也不会立刻执行dispose方法
deactivateChild(child);
assert(child._parent == null);
//然后会inflateWidget方法,后面再详细讲这个方法,这里会创建一个element的对象
newChild = inflateWidget(newWidget, newSlot);
}
} else {
//如果Widget不为空,但是element是空,那么也要创建一个element的对象
newChild = inflateWidget(newWidget, newSlot);
}
...
return newChild;
}
updateChild是一个比较核心的方法,这里提供一个流程图:

由于是第一次构建,RootRenderObjectElement是没有子Element的,所以这里会直接走inflateWidget的方法。
Element inflateWidget(Widget newWidget, dynamic newSlot) {
assert(newWidget != null);
final Key? key = newWidget.key;
if (key is GlobalKey) {
//这里先从inactiveElement列表中找对应的缓存,如果存在。那么将element重新取出来变成active状态
//(这也就是为什么Element在deactivie掉以后,不一定会指定dispose方法销毁。inactiveElement也是有可能会被再次使用的)
final Element? newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
//更新element的slot信息
...
newChild._activateWithParent(this, newSlot);
final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
assert(newChild == updatedChild);
return updatedChild!;
}
}
//如果没有符合条件的inactiveElement,调用createElement方法创建element
final Element newChild = newWidget.createElement();
assert(() {
_debugCheckForCycles(newChild);
return true;
}());
//将element挂载到element树上
newChild.mount(this, newSlot);
assert(newChild._lifecycleState == _ElementLifecycle.active);
return newChild;
}
接下来我们可以看下Widget的createElement方法和Element的mount方法

由于Widget的createElement方法比较简单,Widget会产生对应的Element(一般情况下Widget和Element的名字是类似的),这里就不详细展开了。

继承Element的类比较多,我们详细展开讲下
mount方法:
@override
void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot);
...
_firstBuild();
...
}
在执行ComponentElement的mount方法的时候会先将Element挂载到树上,然后执行_firstBuild方法,最终会执行performRebuild方法:
void performRebuild() {
...
Widget? built;
try {
assert(() {
_debugDoingBuild = true;
return true;
}());
built = build();
assert(() {
_debugDoingBuild = false;
return true;
}());
debugWidgetBuilderValue(widget, built);
} catch (e, stack) {
...
} finally {
...
}
try {
_child = updateChild(_child, built, slot);
assert(_child != null);
} catch (e, stack) {
...
}
...
}
会先执行build()方法,build方法由ComponentElement子类实现,然后执行updateChild方法(就是Element的updateChild方法,从而进行Element的增删改操作)
当执行到firstBuild的时候,StatefulElement会重写这个方法,先执行state的initState方法,然后执行didChangeDependencies方法。
void _firstBuild() {
assert(state._debugLifecycleState == _StateLifecycle.created);
try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
final dynamic debugCheckForReturnedFuture = state.initState() as dynamic;
...
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
...
state.didChangeDependencies();
...
super._firstBuild();
}
之后执行父类的firstBuild方法,随后就是rebuild->performRebuild->build()。
StatefulElement会通过State的build方法构建Widget, 而state是在StatefulElement的构造方法中初始化的。而StatefulElement是在StatefulWidget的createElement方法中创建的
Widget build() => state.build(this);
StatelessElement比较简单,就直接调用Widget的build方法。同理这个Widget就是StatelessWidget。
Widget build() => widget.build(this);
ProxyElement以及其子类ParentDataElement,InheritedElement会获取对应Widget的child。不过这几个类并不是本篇重点,就不再赘述
Widget build() => widget.child;
void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot);
...
_renderObject = widget.createRenderObject(this);
...
attachRenderObject(newSlot);
_dirty = false;
}
在执行RenderObjectElement的时候,先将Element挂载到Element树上,然后执行RenderObjectWidget的createRenderObject方法创建renderObject。然后将RenderObject附着到最近的RenderObjectElement祖先节点上。至于为什么要用祖先节点来绑定RenderObject之后再做解释。
void attachRenderObject(dynamic newSlot) {
assert(_ancestorRenderObjectElement == null);
_slot = newSlot;
//这里先获取第一个祖先节点的RenderObjectElement
//(因为Element树不一定是RenderObjectElement,而RenderObject必须附着到RenderObjectElement上)
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
//绑定对应的RenderObject,这个方法是个空实现,主要是交给了子RenderObjectElement来做。
_ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
final ParentDataElement<ParentData>? parentDataElement = _findAncestorParentDataElement();
if (parentDataElement != null)
_updateParentData(parentDataElement.widget);
}
这里我们看到RenderObjectElement并没有对子节点有处理,是因为RenderObjectElement子类既存在没有孩子节点的Element也存在多个孩子节点的Element,所以对于子节点的处理交给各自的子类来实现。
这个Element就是作为最顶层Element节点存在,所以没有特殊的处理逻辑(文章最开始讲的RenderObjectToWidgetElement就是继承的RootRenderObjectElement)
顾名思义,叶子节点的Element,那么这个Element就不存在子节点。因此不会有子节点的处理
单孩子节点的Element
mount方法:
void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot);
_child = updateChild(_child, widget.child, null);
}
首先指定父类的mount方法,将Element挂载到Element的树上,然后执行updateChild方法。开始往下遍历增删改孩子节点。
insertRenderObjectChild方法:
void insertRenderObjectChild(RenderObject child, dynamic slot) {
//找到当前Element持有RenderObject对象
final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject as RenderObjectWithChildMixin<RenderObject>;
assert(slot == null);
assert(renderObject.debugValidateChild(child));
//把子RenderObject挂载到RenderObject树上
renderObject.child = child;
assert(renderObject == this.renderObject);
}
这里也可以解释刚才的一个问题:「为什么RenderObject附着到最近的RenderObjectElement祖先节点」, 原因是RenderObjectElement祖先节点会有一个对应的RenderObject,这个RenderObject已经挂在了RenderObject树上面,当前RenderObjectElement的RenderObject需要挂到RenderObject树上就必须先找到最近的RenderObjectElement祖先节点。
多孩子节点的Element
mount方法:
void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot);
final List<Element> children = List<Element>.filled(widget.children.length, _NullElement.instance, growable: false);
Element? previousChild;
for (int i = 0; i < children.length; i += 1) {
//遍历孩子数组,创建对应的Element。
final Element newChild = inflateWidget(widget.children[i], IndexedSlot<Element?>(i, previousChild));
children[i] = newChild;
previousChild = newChild;
}
_children = children;
}
inserRenderObjectChild方法:
void insertRenderObjectChild(RenderObject child, IndexedSlot<Element?> slot) {
//获取当前RenderObjectElement对应的RenderObject
final ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>> renderObject = this.renderObject;
assert(renderObject.debugValidateChild(child));
//根据当前RenderObjectElement的位置信息挂载到对应位置的RenderObject树中
renderObject.insert(child, after: slot.value?.renderObject);
assert(renderObject == this.renderObject);
}
至此,Element树以及RenderObject树的构建过程全部结束。

更新的源头需要从platform_dispatcher说起:当硬件发出VSync信号时,会调用platformDispatcher的onDrawFrame

发现最后还是会执行buildScope方法进行更新。
_dirtyElements[index].rebuild();
发现buildScope在更新的时候会获取_dirtyElements来进行遍历执行rebuild。_dirtyElements列表里面存放着的是Element的对象。
void rebuild() {
...
performRebuild();
...
}
最终还是会执行performRebuild()方法。
ComponentElement会重新build Widget,RenderObjectElement则会执行updateRenderObject更新RenderObject。
在执行完buildScope方法以后,会执行finalizeTree方法。 这个方法会将_inactiveElements的所有element对象都执行一遍unmount方法。StatefulElement会重载这个方法,调用state的dispose方法,并且将state的element置空
最常见的要属setState方法,这个方法是State类的方法,主要配合StatefulWidget进行一个更新
void setState(VoidCallback fn) {
...
final dynamic result = fn() as dynamic;
...
_element!.markNeedsBuild();
}
这个方法传入一个callback,会先执行这个callback,然后执行element的markNeedsBuild()方法
void markNeedsBuild() {
...
if (dirty)
return;
_dirty = true;
owner!.scheduleBuildFor(this);
}
将当前Element的_dirty属性设置为true,然后执行BuildOwner的scheduleBuildFor
void scheduleBuildFor(Element element) {
...
_dirtyElements.add(element);
element._inDirtyList = true;
...
}
最终会将需要更新的element加入到_dirtyElements的列表中
主要是ChangeNotifierProvider、ChangeNotifer、Consumer。
ChangeNotifier就是比较常规的监听器, 实现了Listenable接口,增加了notifyListener的方法。
class ChangeNotifier implements Listenable {
LinkedList<_ListenerEntry>? _listeners = LinkedList<_ListenerEntry>();
...
@override
void addListener(VoidCallback listener) {
assert(_debugAssertNotDisposed());
_listeners!.add(_ListenerEntry(listener));
}
@override
void removeListener(VoidCallback listener) {
assert(_debugAssertNotDisposed());
for (final _ListenerEntry entry in _listeners!) {
if (entry.listener == listener) {
entry.unlink();
return;
}
}
}
@mustCallSuper
void dispose() {
assert(_debugAssertNotDisposed());
_listeners = null;
}
@protected
@visibleForTesting
void notifyListeners() {
...
final List<_ListenerEntry> localListeners = List<_ListenerEntry>.from(_listeners!);
for (final _ListenerEntry entry in localListeners) {
try {
if (entry.list != null)
//包装了对应的Callback。在执行notifyListeners的时候会触发对应的callback方法
entry.listener();
} catch (exception, stack) {
...
}
}
}
}
ChangeNotifierProvider继承于ListenableProvider
class ListenableProvider<T extends Listenable?> extends InheritedProvider<T> {
/// Creates a [Listenable] using [create] and subscribes to it.
///
/// [dispose] can optionally passed to free resources
/// when [ListenableProvider] is removed from the tree.
///
/// [create] must not be `null`.
ListenableProvider({
Key? key,
required Create<T> create,
Dispose<T>? dispose,
bool? lazy,
TransitionBuilder? builder,
Widget? child,
}) : super(
key: key,
startListening: _startListening,
create: create,
dispose: dispose,
lazy: lazy,
builder: builder,
child: child,
);
....
static VoidCallback _startListening(
InheritedContext e,
Listenable? value,
) {
value?.addListener(e.markNeedsNotifyDependents);
return () => value?.removeListener(e.markNeedsNotifyDependents);
}
}
ListenableProvider会将_startListening方法传入到父类里面。这个方法里面是给Listenable(一般情况下都是ChangeNotifier)的对象添加了callback,这个callback会存放到ChangeNotifier的_listeners。所以一旦这个value调用了notifierListeners的方法,就会调用这个callback方法即e.markNeedsNotifyDependents
@override
void markNeedsNotifyDependents() {
if (!_isNotifyDependentsEnabled) {
return;
}
markNeedsBuild();
_shouldNotifyDependents = true;
}
这个方法就会触发markNeedsBuild()方法,当前element就会被加入到dirtyElement数组当中。
再来看下_startListening的链路,ListenableProvider继承于InheritedProvider,InheritedProvider又有对应的delegate,最终_startListening会被传入到_CreateInheritedProvider当中。
class _CreateInheritedProviderState<T>
extends _DelegateState<T, _CreateInheritedProvider<T>> {
VoidCallback? _removeListener;
bool _didInitValue = false;
bool _didSucceedInit = false;
T? _value;
_CreateInheritedProvider<T>? _previousWidget;
@override
T get value {
if (_didInitValue && !_didSucceedInit) {
throw StateError(
'Tried to read a provider that threw during the creation of its value.\n'
'The exception occurred during the creation of type $T.',
);
}
bool? _debugPreviousIsInInheritedProviderCreate;
bool? _debugPreviousIsInInheritedProviderUpdate;
...
if (!_didInitValue) {
_didInitValue = true;
if (delegate.create != null) {
assert(debugSetInheritedLock(true));
try {
...
//create方法创建对应的ChangeNotifier赋值给value
//因此_CreateInheritedProviderState就会持有ChangeNotifier的对象
_value = delegate.create!(element!);
_didSucceedInit = true;
} finally {
...
}
...
}
...
element!._isNotifyDependentsEnabled = false;
//此处会执行startListening方法,将value(即ChangeNotifier)传入
_removeListener ??= delegate.startListening?.call(element!, _value as T);
element!._isNotifyDependentsEnabled = true;
assert(delegate.startListening == null || _removeListener != null);
return _value as T;
}
@override
void dispose() {
super.dispose();
_removeListener?.call();
if (_didInitValue) {
delegate.dispose?.call(element!, _value as T);
}
}
}
发现是在获取value的时候会执行startListening,从而将callback注册到ChangeNotifier当中。
另外我们可以看到只有当_didInitValue为true的时候才会在dispose的时候调用ChangeNotifier的dispose方法,而_didInitValue这个只有在value的get方法调用过后才会置true。
class Consumer<T> extends SingleChildStatelessWidget {
/// {@template provider.consumer.constructor}
/// Consumes a [Provider<T>]
/// {@endtemplate}
Consumer({
Key? key,
required this.builder,
Widget? child,
}) : super(key: key, child: child);
@override
Widget buildWithChild(BuildContext context, Widget? child) {
return builder(
context,
Provider.of<T>(context),
child,
);
}
}
consumer逻辑相对比较简单,继承于SingleChildStatelessWidget。所以在build的时候会执行buildWithChild方法。这里面的重点是Provider.of<T>(context),泛型一般会传入对应的ChangeNotifier。那么再来看下Provider.of<T>(context)的逻辑
static T of<T>(BuildContext context, {bool listen = true}) {
...
//此方法会获取遍历获取对应的element
final inheritedElement = _inheritedElementOf<T>(context);
if (listen) {
// bind context with the element
// We have to use this method instead of dependOnInheritedElement, because
// dependOnInheritedElement does not support relocating using GlobalKey
// if no provider were found previously.
context.dependOnInheritedWidgetOfExactType<_InheritedProviderScope<T?>>();
}
final value = inheritedElement?.value;
if (_isSoundMode) {
if (value is! T) {
throw ProviderNullException(T, context.widget.runtimeType);
}
return value;
}
return value as T;
}
每个InheritedElement都会在_updateInheritance方法执行的时候获取parent的_inheritedWidgets的Map,将parent的map赋值给自己,然后再将自己也存入到map中,这样就可以获取到对应的InheritedElement。也因此在此处会调用startListening方法将callback注册到ChangeNotifier当中。

现在我们可以回答开头的三个问题
在编写Ruby(客户端脚本)时,我看到了三种构建更长字符串的方法,包括行尾,所有这些对我来说“闻起来”有点难看。有没有更干净、更好的方法?变量递增。ifrender_quote?quote="NowthatthereistheTec-9,acrappyspraygunfromSouthMiami."quote+="ThisgunisadvertisedasthemostpopularguninAmericancrime.Doyoubelievethatshit?"quote+="Itactuallysaysthatinthelittlebookthatcomeswithit:themo
我正在尝试在配备ARMv7处理器的SynologyDS215j上安装ruby2.2.4或2.3.0。我用了optware-ng安装gcc、make、openssl、openssl-dev和zlib。我根据README中的说明安装了rbenv(版本1.0.0-19-g29b4da7)和ruby-build插件。.这些是随optware-ng安装的软件包及其版本binutils-2.25.1-1gcc-5.3.0-6gconv-modules-2.21-3glibc-opt-2.21-4libc-dev-2.21-1libgmp-6.0.0a-1libmpc-1.0.2-1libm
关闭。这个问题需要更多focused.它目前不接受答案。想改进这个问题吗?更新问题,使其只关注一个问题editingthispost.关闭8年前。Improvethisquestion我们有以下(以及更多)系统,我们将数据从一个应用推送/拉取到另一个:托管CRM(InsideSales.com)Asterisk电话系统(内部)横幅广告系统(openx,我们托管)潜在客户生成系统(自行开发)电子商务商店(spree,我们托管)工作板(本土)一些工作网站抓取+入站工作提要电子邮件传送系统(如Mailchimp,自主开发)事件管理系统(如eventbrite,自主开发)仪表板系统(大量图表和
在我的mac上安装几个东西时遇到这个问题,我认为这个问题来自将我的豹子升级到雪豹。我认为这个问题也与macports有关。/usr/local/lib/libz.1.dylib,filewasbuiltfori386whichisnotthearchitecturebeinglinked(x86_64)有什么想法吗?更新更具体地说,这发生在安装nokogirigem时日志看起来像:xslt_stylesheet.c:127:warning:passingargument1of‘Nokogiri_wrap_xml_document’withdifferentwidthduetoproto
一、什么是MQTT协议MessageQueuingTelemetryTransport:消息队列遥测传输协议。是一种基于客户端-服务端的发布/订阅模式。与HTTP一样,基于TCP/IP协议之上的通讯协议,提供有序、无损、双向连接,由IBM(蓝色巨人)发布。原理:(1)MQTT协议身份和消息格式有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。MQTT传输的消息分为:主题(Topic)和负载(payload)两部分Topic,可以理解为消息的类型,订阅者订阅(Su
TCL脚本语言简介•TCL(ToolCommandLanguage)是一种解释执行的脚本语言(ScriptingLanguage),它提供了通用的编程能力:支持变量、过程和控制结构;同时TCL还拥有一个功能强大的固有的核心命令集。TCL经常被用于快速原型开发,脚本编程,GUI和测试等方面。•实际上包含了两个部分:一个语言和一个库。首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一些互交程序如文本编辑器、调试器和shell。由于TCL的解释器是用C\C++语言的过程库实现的,因此在某种意义上我们又可以把TCL看作C库,这个库中有丰富的用于扩展TCL命令的C\C++过程和函数,所以,Tcl是
Ruby语言是否可以用于创建全新的移动操作系统或桌面操作系统,即是否可以用于系统编程? 最佳答案 嗯,现在有一些操作系统使用比C更高级的语言。基本上,ruby解释器本身需要用一些低级的东西来编写,并且需要一些引导加载代码将功能齐全的ruby解释器作为独立内核加载到内存中。一旦ruby解释器被引导并以内核模式(或innerrings之一)运行,就没有什么可以阻止您在其上构建整个操作系统。不幸的是,它可能会很慢。每个操作系统功能的垃圾收集可能会相当引人注目。ruby解释器将负责任务调度和网络堆栈等基本事情,使用垃圾收集框架会大大
这个问题在这里已经有了答案:Unabletoinstallgem-Failedtobuildgemnativeextension-cannotloadsuchfile--mkmf(LoadError)(17个答案)关闭9年前。嘿,我正在尝试在一台新的ubuntu机器上安装rails。我安装了ruby和rvm,但出现“无法构建gemnative扩展”错误。这是什么意思?$sudogeminstallrails-v3.2.9(没有sudo表示我没有权限)然后它会输出很多“获取”命令,最终会出现这个错误:Buildingnativeextensions.Thiscouldtakeawhi
我尝试了一些关于rubyonrails中openid利用率的搜索。然而,尽管出现了一组选项,例如omniauth、authlogic等,但这些gem通常用于构建接受openid身份验证的站点。换句话说,它们用于openid消费者设置。我也想构建自己的openid服务器。AssuggestedhereinOpenIdsite我发现了像Masquerade和local-openid这样的东西,不幸的是,它们不是非常活跃的项目,下载量很少。自建openidprovider服务器有没有其他设施可以推荐?非常感谢!!干杯,叶 最佳答案 虽
我正在尝试构建一个纯粹使用Ruby的聊天应用程序。有一个similarquestion较早发布,但我有不同的相关查询。我看过thisexample(与之前发布类似问题的人所提到的相同)。示例中的代码似乎对我不起作用。在终端上运行ruby脚本,并连接到url:http://localhost:1234在我的浏览器中,我无限期地遇到“正在从本地主机传输数据...”消息。此处的1234是所提供示例中使用的端口号。我无法弄清楚我运行失败的原因是什么。可能是我需要在执行脚本时在命令行中指定一些东西,或者我应该通过其他地方(可能是浏览器)开始聊天(输入输出)。我无法弄清楚到底该做什么。你能帮我