Element
在前面的描述中可以知道Widget和Element是属于响应式层面的概念,而RenderObject是属于渲染层面的概念.Widget是配置对象,RenderObject是被渲染引擎渲染的对象,Element是Widget和RenderObject之间的桥梁.
先说结论,在Flutter中一共存在两棵树,有几个Owner就有几棵树.有PipelineOwner和BuildOwner这两个Owner,就有RenderTree和ElementTree这两棵树,Widget只是这两棵树使用的配置对象.要理解这三个概念的关系需要从Element开始,因为Element是渲染逻辑的入口,Rendering Pipeline就是通过遍历dirty elements来完成的.
定义
官方对Element的解释是An instantiation of a [Widget] at a particular location in the tree,按字面意思是说它是树上的某个widget的实例,这种描述其实并不准确,因为Element毕竟不是Widget类的实例,之所以这么说是因为Element是用Widget中定义的createElement创建的.一种类型的widget对应一个相应的element.
abstract class Element implements BuildContext {
Element _parent;
Widget get widget => _widget;
Widget _widget;
BuildOwner get owner => _owner;
BuildOwner _owner;
RenderObject get renderObject
Element updateChild(Element child, Widget newWidget, dynamic newSlot)
void mount(Element parent, dynamic newSlot)
void markNeedsBuild()
void rebuild()
void unmount()
}
参考Element定义的源码可以注意到几个重要的属性和方法
-
_parent
- 有_parent就说明了Element确实是一棵树,代表了父节点的引用
-
widget
- 每个Element都会持有它的配置对象Widget
-
owner
- 每个Element都会持有一个BuildOwner,这个是在WidgetBinding中初始化的
-
renderObject
- 所有的Element都会有renderObject属性,但只有element的类型为RenderObjectElement时,其值才不会为null
-
updateChild
- 新widget更新子element的方法,更新规则如下
根据原来child的有无和传入新widget的有无,共分了四种情况,这个方法会在rebuild中被调用
-
mount
- 新创建的element第一次被挂载到父节点的时候会被调用
-
markNeedsBuild
- 标记element为脏数据,并把它放进BuildOwner维护的一个全局的脏列表_dirtyElements里,调用前面owner属性的scheduleBuildFor方法就可以完成
-
rebuild
- 第一次加载和widget改变都会调用这个方法重新构建element.在一次RenderPipeline中由BuildOwner遍历脏列表时在buildScope方法中发起调用
-
unmount
- 卸载element
通过以上属性和方法可以感知到Element好像是有一个生命周期的,事实上这个生命周期确实是存在的,但这个生命周期一般只会被Framework使用到
enum _ElementLifecycle {
initial, //初始化
active, //活跃态,在屏幕上可见
inactive,//非活跃态,在屏幕上不可见
defunct, //废止态,再也不见
}
Element类是个抽象类,是不能直接被使用的,所以在Flutter中定义了两种继承自Element的抽象子类ComponentElement和RenderObjectElement.
ComponentElement
这种类型的Element是一个起到包装作用的父Element容器,主要通过封装一些逻辑来管理其子Element.
abstract class ComponentElement extends Element {
Element _child;
Widget build();
}
通过源码可以发现,ComponentElement主要多了一个_child属性和一个build方法
-
_child
这个属性表明在ComponentElement下面存在一个Element类型的子节点,这个子节点就是通过build返回的widget创建的.
具体的创建过程定义在了inflateWidget中:
Element inflateWidget(Widget newWidget, dynamic newSlot) { final Element newChild = newWidget.createElement(); newChild.mount(this, newSlot); return newChild; }由此也可以得出一个结论,Element树其实是一棵组件树,也就是ComponentElement树.
-
build
这个build返回一个新widget,这个需要被所有子类重写.
ComponentElement也是一个抽象类,同样不能被直接使用,所以又定义了三种继承自ComponentElement的子类StatelessElement、StatefulElement、ProxyElement.
StatelessElement
class StatelessElement extends ComponentElement {
@override
Widget build() => widget.build(this);
}
StatelessElement是以StatelessWidget为配置对象的Element,它会通过Widget.createElement()方法被创建.
这个Element主要重写了build方法,这个build方法调用了对应widget的build方法,在调用的时候会把element本身当作参数传进方法中,所以在widget的build方法中看到的BuildContext形参就是这个element实例.
build会在element实例中的performRebuild方法中被调用,StatelessElement拿到build返回的widget后会对子节点进行创建、更新等操作.
void performRebuild() {
Widget built;
built = build();
_child = updateChild(_child, built, slot);
}
StatefulElement
class StatefulElement extends ComponentElement {
StatefulElement(StatefulWidget widget): _state = widget.createState(),super(widget) {
_state._element = this;
_state._widget = widget;
}
@override
Widget build() => _state.build(this);
State<StatefulWidget> get state => _state;
State<StatefulWidget> _state;
void _firstBuild() {
final dynamic debugCheckForReturnedFuture = _state.initState() as dynamic;
_state.didChangeDependencies();
super._firstBuild();
}
@override
void reassemble() {
state.reassemble();
super.reassemble();
}
@override
void performRebuild() {
if (_didChangeDependencies) {
_state.didChangeDependencies();
_didChangeDependencies = false;
}
super.performRebuild();
}
@override
void update(StatefulWidget newWidget) {
super.update(newWidget);
final StatefulWidget oldWidget = _state._widget;
_dirty = true;
_state._widget = widget as StatefulWidget;
try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic;
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
rebuild();
}
@override
void activate() {
super.activate();
markNeedsBuild();
}
@override
void deactivate() {
_state.deactivate();
super.deactivate();
}
@override
void unmount() {
super.unmount();
_state.dispose();
_state._element = null;
_state = null;
}
@override
InheritedWidget inheritFromElement(Element ancestor, { Object aspect }) {
return dependOnInheritedElement(ancestor, aspect: aspect);
}
@override
InheritedWidget dependOnInheritedElement(Element ancestor, { Object aspect }) {
return super.dependOnInheritedElement(ancestor as InheritedElement, aspect: aspect);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_didChangeDependencies = true;
}
}
StatefulElement是以StatefulWidget为配置对象的Element,它会通过Widget.createElement()方法被创建.
在源码中可以发现在StatefulElement内部重写了build方法,这个build方法调用的是_state.build而不是widget.build,这也是为什么StatefulWidget的build要在State中重写而不在StatefulWidget中重写.
同时在element的内部还调用了widget.createState、_state.initState()、_state.reassemble()、_state.didChangeDependencies()、_state.didUpdateWidget(oldWidget)、_state.deactivate()、_state.dispose()等方法,这些不正是state的几个生命周期嘛,在这里可以清楚的看到各个生命周期执行的时机.
-
widget.createState
创建state实例的阶段,在调用widget.createElement对element进行初始化的时候会调用一次
-
_state.initState()
初始化state阶段,这个方法在_firstBuild中被触发,也就是element还没开始挂载到树上.
-
_state.didChangeDependencies()
state的依赖完成改变阶段,这个方法会在_firstBuild和performRebuild中触发:
- 在_firstBuild中会紧随initState方法之后调用一次
- 当重绘时在performRebuild方法中会有一次调用,会在didUpdateWidget调用之后发生
-
_state.reassemble()
这是个会在开发阶段触发的一个热重载阶段,又Flutter的底层foundation层通过BuildOwner触发,这个阶段会发生在重新build之前
-
_state.didUpdateWidget(oldWidget)
更新完widget后触发,这个函数会把old widget作为参数传进来.这个阶段的触发在_parent.build调用之后
-
_state.deactivate()
state的非活跃阶段,这个方法会在_parent.updateChild中触发,但触发条件要分两种情况:
- 移除一个element,也就是新widget为null,旧widget不为null,会调用deactivate方法,后续的didUpdateWidget阶段不会存在.
- 改变一个element,也就是新widget与就widget不是同一个类型的widget或者key值不一样,也会调用deactivate方法
-
_state.dispose()
- state的释放阶段,处于非活跃状态的element在一次rendering pipeline结尾如果仍然没有被重新放回到树中,则在Finalization中会卸载有所有的element,在卸载之前会触发state的dispose方法.
现在可以透过Element来总结一下State的生命周期
ProxyElement
ProxyElement以ProxyWidget为配置对象
abstract class ProxyElement extends ComponentElement {
@override
Widget build() => widget.child;
@override
void update(ProxyWidget newWidget) {
final ProxyWidget oldWidget = widget;
super.update(newWidget);
updated(oldWidget);
_dirty = true;
rebuild();
}
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
@protected
void notifyClients(covariant ProxyWidget oldWidget);
}
-
build
既然是个ComponentElement,重写build那就是必须的.但是这里重写的方式和前两个不一样,并不是调用widget的build方法,而是返回widget的child属性.也就是在编写ProxyWidget的时候不需要重写build函数而是为其child属性赋值就可以了,采用赋值的方式返回widget也就暗示了这个child属性最好不要改变.
-
update
ProxyElement还重写了update函数,这个是很值得玩味的.可以先看看重写之前的update函数做了些什么
void update(covariant Widget newWidget) { _widget = newWidget; }可以看到原来的update就做了一件事情,用新的的widget给_widget属性赋值.而这里多做了两件事,额外调用了updated和rebuild方法.
在前面分析中可以得知一个element在updateChild阶段会有四种情况,只有当新旧weidget的类型和key值相同的情况下才会走update这条线路.
//in Element >> updateChild if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) { if (child.slot != newSlot) updateSlotForChild(child, newSlot); child.update(newWidget); newChild = child; }而这里重写update的方式也就暗示了在ProxyElement中执行updateChild时,一定要走update(widget.canUpdate为true)这条线路,否则就没什么意义了,和前两种ELement就没啥区别.要保证在updateChild时执行update就需要新旧widget的runtimeType和key值不变,也就是它的widget.child属性的类型和key不要改变.
所以ProxyElement适合放在最顶层,它的widget.child最好放一些Scaffold这样不会被改变类型的脚手架.
-
updated
单独出这么一个函数来调用notifyClients是为了方便子类可以覆写它,可以加个判断用来决定是否要调用notifyClients.
-
notifyClients
这是一个需要子类覆写的方法,通过名字可以看出它是一个实现了类似于消息通知的方法.
从这个方法可以看出ProxyElement存在的意义和为什么叫ProxyElement了,这个Element就是一个代理、一个不赚差价的中间商,它本身并不生产widget,它只是child属性搬运工.它的子widget并不是使用它的widget.build方法创造出来的,而是在被实例化的时候传入的child属性,所有的子节点都可以订阅或修改消息,它负责分发消息给对应的子节点.
因为ProxyElement是个抽象类,是不能被实例化的,所以会使用到它的两个子类:
-
ParentDataElement
和渲染对象有关,一般不会直接使用到,暂略.
-
InheritedElement
这个Element可以说是非常有用,可以用它来做全局的状态管理
现在可以从mount开始分析一下订阅的消息机制是如何工作的
void mount(Element parent, dynamic newSlot) { _updateInheritance(); }可以看到在mount中调用了_updateInheritance方法,而这个_updateInheritance方法可以在子类InheritedElement中找到
Map<Type, InheritedElement> _inheritedWidgets; void _updateInheritance() { final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets; if (incomingWidgets != null) _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets); else _inheritedWidgets = HashMap<Type, InheritedElement>(); _inheritedWidgets[widget.runtimeType] = this; }这里做了一件事情就是为_inheritedWidgets这个Map变量赋值,如果在父节点中存在_inheritedWidgets则会合并到这里,如果父节点没有则为_inheritedWidgets赋初始值,键值为widget的runtimeType,value为这个element.
经过mount一波猛如虎的操作这个InheritedElement就会被挂载到Element Tree中,此时并没有发生什么,接下来就是子节点要在这个代理中注册想要对消息.这个注册方法需要在InheritedElement的widget中手动定义,官方简易的注册写法是这样的
static InheritedWidget_Name of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<InheritedWidget_Name>(); }这个of方法要在子Widget的build中调用来获取InheritedWidget_Name实例,可以通过这个实例拿到存储在InheritedWidget_Name中的状态供子widget使用,但在这个获取实例的过程中会悄悄注册消息订阅事件.
@override T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) { final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T]; if (ancestor != null) { return dependOnInheritedElement(ancestor, aspect: aspect) as T; } _hadUnsatisfiedDependencies = true; return null; } Set<InheritedElement> _dependencies; @override InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) { _dependencies ??= HashSet<InheritedElement>(); _dependencies.add(ancestor); ancestor.updateDependencies(this, aspect); return ancestor.widget; }在dependOnInheritedWidgetOfExactType中可以看到子节点拿到了它的代理ancestor并调用了dependOnInheritedElement,这个dependOnInheritedElement调用了代理element的updateDependencies方法并把子element作为参数传入,最后返回代理的widget(这里面存储着子widget需要的data).
@protected void updateDependencies(Element dependent, Object aspect) { setDependencies(dependent, null); } final Map<Element, Object> _dependents = HashMap<Element, Object>(); void setDependencies(Element dependent, Object value) { _dependents[dependent] = value; }又可以看到在updateDependencies中调用了setDependencies,这个setDependencies就是为代理中的_dependents赋值,_dependents的key值是子element实例.
经过以上步骤就把子Element的实例注册在了InheritedElement中的_dependents属性的keys中.当触发渲染流水线重绘走到element的update函数时,就会调用前面说的在ProxyElement中定义的updated方法,这个方法会被InheritedElement重写
@override void updated(InheritedWidget oldWidget) { if (widget.updateShouldNotify(oldWidget)) super.updated(oldWidget); }重写的目的就是加了一层判断,需要开发者根据新旧widget自行判断是否要通知子节点触发didChangeDependencies事件,这个判断是在InheritedWidget中被重写的.如果返回true则会触发notifyClients通知注册的子element调用didChangeDependencies
@override void notifyClients(InheritedWidget oldWidget) { for (final Element dependent in _dependents.keys) { notifyDependent(oldWidget, dependent); } } @protected void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) { dependent.didChangeDependencies(); }
以上就是ComponentElement的三种类型,ComponentElement存在的意义就是创建子节点,如果Flutter只使用ComponentElement那么在屏幕中是什么也不会看到的,会在内存中建立一个无穷尽的Element树,直到内存溢出或报错,因为每个ComponentElement一定会有build方法返回一个不能为空的子节点widget.所以这个ComponentElement树的每个分支一定要有个尽头,这个尽头就是RenderObjectElement.
RenderObjectElement
RenderObjectElement是比较复杂的,首先它也具有ComponentElement的功能也是可以产生树结构的,只是它的子节点不但可以是child还可以是children,而ComonentElement只有child.其次,RenderObjectElement是一个有布局、有样式、有画面的Element,因为每个RenderObjectElement都有一个属性来持有RenderObject,这个RenderObject就是最终要被渲染的东西,而且由于ComponentElement的存在,会导致Element树和Render树上的节点并不是一一对应的.所以RenderObjectElement中存在渲染逻辑不但要完成树的构建还要把RenderObject的变化同步到Render Tree中.