Widget的生命周期
关于生命周期,我之前写过一篇文章总结过:提到生命周期,我们是在说什么?今天这个篇幅是以此文章为基准,再做一些补充。
其实,所谓的生命周期,就是一系列的方法回调,我们可以通过实现这些方法来捕获一个widget从加载到卸载全过程中的各个节点,以在合适的时机做合适的事情。
那么我们可以利用生命周期方法做哪些事情呢?我下面可以稍微罗列一下:
一般而言,Flutter的widget分为StatefulWidget和StatelessWidget,接下来分别作介绍。
StatelessWidget的生命周期
1,初始化构造方法
2,widget的build函数
StatefulWidget的生命周期
1,statefulWidget的构造函数
2,createState
3,对应State的构造函数
4,对应State的初始化函数initState
5,didChangeDependencies
6,state的build函数
上面👆第5步中的didChangeDependencies会触发state的build()函数。
我们知道,在需要修改数据更新UI的时候,只要调用setState然后在其中更改数据,这样UI就可以随之改变了,这是因为setState函数可以触发widget的销毁重建,也就是会触发state的build函数的重新调用。
接下来我们看一下setState的源码:
可以看到,除了断言,这里面实际上就调用了一行代码:
_element!.markNeedsBuild();
这会给element标记为需要重建,然后element对应的widget就会销毁重建。
这里说句题外话,其实这里的_element就是我们在业务代码中常见的context,如何证明这一点呢?我们点击上面的_element,就来到了其定义的地方,然后有如下代码:
可以看到context的getter里面返回的就是_element。
好,现在我们知道了通过setState来根据数据自动调整UI的原理了,因此,原则上我们是可以不调用setState而直接给element调用markNeedsBuild函数来实现UI的更新,即:
在StatefulWidget的build方法中将context转成StatefulElement类型的element,然后直接在对应的数据更新完了之后,手动调用element.markNeedsBuild(),这样就能够实现UI的更新了。但我们开发的时候不会这样去用,因为setState里面做了很多assert断言的容错判断,会更加安全。
7,deactive
当State对象被从视图渲染树中移除的时候,就会调用state的deactive。比如当某个StatefulWidget的可见状态发生了变化,此时该widget对应的state会被暂时从视图渲染树中移除(后面还会用,并未销毁哦),因此就会调用deactive;再比如当视图切换的时候,由于State对象在视图渲染树中的位置发生了变化,因此需要暂时移除之后再重新添加,此时就会触发deactive。
下面👇这张图就展示了当视图切换(push、pop)的时候,各个生命周期函数的调用情况:
可以看到,deactive是可以被调用多次的。
8,dispose
当State对象被永久地从视图树中移除时,Flutter会调用dispose函数。而一旦到这个阶段,组件就要被销毁了,所以我们可以在这里进行最终的资源释放、移除监听、清理环境,等等。
Widget的渲染原理
关于Widget的渲染,我在Widget,构建Flutter界面的基石中有过介绍,本文也是依次为基准,再做一些拓展介绍。
截至目前,我接触到的直接继承自Widget的类有三个,分别是:StatefulWidget、StatelessWidget和RenderObjectWidget:
abstract class StatefulWidget extends Widget
abstract class StatelessWidget extends Widget
abstract class RenderObjectWidget extends Widget
并不是所有的widget都会有对应的一个RenderObject对象(也就是说,并非所有的widget都会被独立渲染),只有直接或者间接继承自RenderObjectWidget的widget才会最后生成一个对应的RenderObject,然后将其加入到渲染树中进行渲染。
但是,所有的widget都会创建一个对应的Element对象。
上面分别列出了StatelessWidget、StatefulWidget和RenderObjectWidget的源码,从源码中也可以看出,三者都有createElement()函数,这也进一步说明了,所有的widget都会创建一个对应的Element对象。
那么createElement()函数啥时候被调用呢?实际上,当创建完了widget之后,Flutter Framework就会去隐式调用createElement()函数来创建对应的Element,创建完了对应的Element后就会将该element加入到Element树当中,一旦将Element对象加入到了Element树当中,Flutter Framework就会去调用对应Elemen中的mount方法。接下来我们就来分别研究一下StatelessWidget、StatefulWidget和RenderObjectWidget的createElement()函数。
StatelessWidget的createElement()
可以看到,StatelessWidget中的createElement()函数返回的是一个StatelessElement类型的对象。并且这里的this代表的是当前的这个StatelessWidget。
当创建了一个StatelessWidget之后,Flutter Framework必然会调用StatelessWidget的createElement创建StatelessElement对象并将其加入到Element树当中,之后Flutter Framework会接着调用ComponentElement的mount函数
接下来看一下StatelessElement的实现:
可以看到,StatelessElement继承自ComponentElement。
接下来看下ComponentElement的源码:
可以看到,在ComponentElement的mount函数中,除去断言之外,只做了一件事情,就是调用_firstBuild();,然后我们一层一层点进去:
void _firstBuild() {
rebuild();
}
此时,再点performRebuild就点不进去了,这个时候将鼠标定位到这里,然后Command + Option + B,如下:
然后点进去:
可以看到,这里面调用了build()函数。
以上分析得出结论如下:
StatelessElement中的mount函数经过一系列的方法跳转,最终会取出对应的StatelessWidget来调用其build函数。
StatefulWidget的createElement()
可以看到,StatefulWidget中的createElement()函数返回的是一个StatefulElement类型的对象。并且这里的this代表的是当前的这个StatefulWidget。
接下来看一下StatefulElement的实现:
可以看到,StatefulElement继承自ComponentElement。
当创建了一个StatefulWidget之后,Flutter Framework必然会调用StatefulWidget的createElement创建StatefulElement对象并将其加入到Element树当中,之后Flutter Framework会接着调用StatefulElement的mount函数
ComponentElement的源码在上面已经展示过了,关于mount函数的分析与上面等同。
但是这里讲一个StatefulElement和StatelessElement不同的地方,如下:
可以看到,在创建StatefulElement的时候,比创建StatelessElement多做了两件事情:
接下来总结一下StatefulWidget的渲染流程:
创建完一个StatefulWidget之后,Flutter Frame会调用StatefulWidget的createElement()函数,在该函数中会创建一个StatefulElement;
在StatefulElement的构建函数中,调用了widget的createState函数来创建State,并且给创建出来的State对象的element和wiget进行赋值传递;
创建完StatefulElement后会将该element插入到element树当中,然后Flutter Framework会调用ComponentElement的mount函数,在mount函数中会调用widget的build函数。
RenderObjectWidget的createElement()
可以看到,RenderObjectWidget中的createElement()函数返回的是一个RenderObjectElement类型的对象。
由于RenderObjectWidget是一个抽象接口类,所以createElement()函数需要在其子类中实现,我们这里以它的一个子类进行演示:
可以看到,在通过createElement创建Element的时候也传入了一个this,这个this就是对应的RenderObjectWidget。
当创建了一个Widget之后,Flutter Framework必然会调用createElement创建Element对象并将其加入到Element树当中,之后Flutter Framework会接着调用Element的mount函数。
接下来看下RenderObjectElement的源码:
除去一堆的断言之后,RenderObjectElement中的mount函数源码如下:
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
可以看到,在RenderObjectElement中的mount函数中,会完成与之关联的RenderObject对象的创建,以及渲染树的插入工作。
需要注意的是,是通过RenderObjectWidget中的createRenderObject函数来创建RenderObject的;而且这里传入的this是RenderObjectElement对象,这样就可以将Element和RenderObject对应起来了。
以上。