Widget 生命周期
生命周期的基本概念
我们使用一个对象的时候,有时会需要知道对象的一个状态,什么时候被创建,什么时候被销毁。我们常用的生命周期方法其实本质上就是回调函数,是 Flutter
封装好的,在 Widget
的不同状态设置对应的回调方法给外部使用。
生命周期的作用
- 初始化数据
- 创建变量、常量
- 发送网络请求
- 监听小部件的事件
- 管理内存
- 销毁数据、销毁监听者、定时器等
Widget 常见的生命周期函数
Widget
生命周期函数我们可以分为两种类型来看,无状态与有状态类型。
无状态 Widget (StatelessWidget)
当无状态 Widget
比渲染的时候会依次调用构造函数与 build
函数。
有状态 Widget (StatefulWidget)
class MyHomePage extends StatefulWidget {
final String? title;
MyHomePage({this.title}) {
print('Widget 构造函数被调用了');
}
@override
_MyHomePageState createState() {
print('createState 函数被调用了');
return _MyHomePageState();
}
}
class _MyHomePageState extends State {
_MyHomePageState() {
print('State 构造函数被调用了');
}
@override
void initState() {
super.initState();
print('State 的 init 函数被调用了');
}
@override
Widget build(BuildContext context) {
print('State build 函数被调用了');
return Center(child: Text(widget.title ?? ''),);
}
@override
void dispose() {
super.dispose();
print('State 的 dispose 函数被调用了');
}
}
有状态的 Widget
被渲染的时候会依次调用 StatefulWidget
的构造函数、createState
函数、State
的构造函数、initState
函数、build
函数。当热重载的时候会调用 StatefulWidget
的构造函数跟 State
的 build
函数。当调用 setState
方法的时候会调用 build
函数。
通过源码可以看到 setState
其实就是 _element
调用了 markNeedsBuild
函数,只是在此之前做了一些错误的判断,这里 _element
就是 context
。
数据共享部件 InheritedWidget
class MyData extends InheritedWidget {
final int data; //需要在子组件中共享的数据
//构造方法
const MyData({required this.data, required Widget child}) : super(child: child);
//定义一个便捷方法,方便子组件中的 widget 获取共享数据
static MyData? ofContext(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType();
}
//该回调决定当前 data 发生变化的时候,是否通知子组件(依赖 data 的子组件)
@override
bool updateShouldNotify(covariant InheritedWidget oldWidget) {
print('调用了 updateShouldNotify 函数');
//如果返回 true,子部件中依赖共享数据的 Widget(build 函数中是否使用共享数据) 的 didChangeDependencies 方法会被调用
return (oldWidget as MyData).data != data;
}
}
class InheritedDemo extends StatefulWidget {
const InheritedDemo({Key? key}) : super(key: key);
@override
_InheritedDemoState createState() => _InheritedDemoState();
}
class _InheritedDemoState extends State {
int _count = 0;
@override
Widget build(BuildContext context) {
return MyData(data: _count, child: Column(
children: [
TextDemo(count: _count),
ElevatedButton(onPressed: () {
_count++;
setState(() {});
}, child: const Icon(Icons.add)),
],
));
}
}
class TextDemo extends StatefulWidget {
final int? count;
TextDemo({this.count});
@override
_TextDemoState createState() => _TextDemoState();
}
class _TextDemoState extends State {
@override
Widget build(BuildContext context) {
print('调用了 build 函数');
return Text((MyData.ofContext(context) as MyData).data.toString());
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print('调用了 didChangeDependencies 函数');
}
}
在我们开发的过程中一定会遇到这种场景,子部件的数据需要依赖父部件的数据,当层级比较多的话层层传递的方式就会比较麻烦,所以 Flutter
提供一个 InheritedWidget
部件,用来解决这种场景。如上案例中,我们定义了一个负责数据共享的类 MyData
继承于 InheritedWidget
,MyData
的构造方法中有两个参数,data
代表需要共享的数据,child
表示依赖于数据共享的 Widget
。并且我们提供了一个 ofContext
方法,供外界获取数据时候使用。使用的话,我们在 _InheritedDemoState
的 build
方法中初始化 MyData
,在需要获取共享数据的子部件中通过 MyData.ofContext(context)
来获取数据,这里需要注意的是,子组件需要通过 InheritedWidget
来获取共享数据的话,其根视图部件必须是继承于 InheritedWidget
类的 MyData
。当我们执行 _count++
的时候会调用 updateShouldNotify
方法,在这里我们可以通过返回值来判断是否调用子组件的 didChangeDependencies
方法,类似于发送通知,返回值为 true
的时候就会调用,反之就不调用,我们可以根据需求在 didChangeDependencies
方法中做一些事情。
Flutter 渲染原理
Widget 树、Element 树与 RenderObjc 树
在 Flutter
中有三个树结构,分别是 Widget
树、Element
树与 RenderObjc
树。Widget
树很直观的就是我们页面所有 Widget
部件的结构关系,但是 Flutter
渲染引擎并不是直接渲染 Widget
树,因为 Widget
是经常发生变化的,直接渲染 Widget
树会非常消耗性能。其实 Flutter
渲染引擎渲染的是 RenderObjc
树,RenderObjc
树中每个节点是一个又一个的 RenderObjc
对象,但并不是所有的 Widget
都会生成 RenderObjc
,只有 RenderObjcWidget
的子类才会生成 RenderObjc
对象,才会被渲染引擎直接渲染。
abstract class RenderObjectWidget extends Widget {
RenderObjectElement createElement();
RenderObject createRenderObject(BuildContext context);
}
在 RenderObjectWidget
类中我们需要关注 createElement
与 createRenderObject
这两个方法,这两个方法都是抽象方法,所有在子类中应该有相应的实现,这里我们先来看 createRenderObject
这个方法。
class Flex extends MultiChildRenderObjectWidget {
@override
RenderFlex createRenderObject(BuildContext context) {
return RenderFlex(
direction: direction,
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
textDirection: getEffectiveTextDirection(context),
verticalDirection: verticalDirection,
textBaseline: textBaseline,
clipBehavior: clipBehavior,
);
}
}
在 RenderObjectWidget
的子类 Flex
中我们可以看到 createRenderObject
其实就是返回一个继承于 RenderObject
的子类对象 RenderFlex
,并加入到 RenderObject
树中。所以只有继承于 RenderObjectWidget
的子类且实现 createRenderObject
方法的部件才能被独立渲染。
Element 树
abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
MultiChildRenderObjectElement createElement() => MultiChildRenderObjectElement(this);
}
class MultiChildRenderObjectElement extends RenderObjectElement {
MultiChildRenderObjectElement(MultiChildRenderObjectWidget widget)
: assert(!debugChildrenHaveDuplicateKeys(widget, widget.children)),
super(widget);
}
在 RenderObjectWidget
类中除了 createRenderObject
方法还有 createElement
方法。通过源码可以看到 RenderObjectWidget
的子类 MultiChildRenderObjectWidget
中实现了 createElement
方法,会创建一个继承于 RenderObjectElement
的子类 MultiChildRenderObjectElement
对象。
所以继承于
RenderObjectWidget
的子类除了创建RenderObject
,还会创建Element
,三个树都有。
下面我们看一下不继承于 RenderObjectWidget
的部件。
abstract class StatelessWidget extends Widget {
StatelessElement createElement() => StatelessElement(this);
}
abstract class StatefulWidget extends Widget {
StatefulElement createElement() => StatefulElement(this);
}
这里可以看到继承于 Widget
的子类都会实现 createElement
方法,都会创建 Element
。所以可以得出结论,所有 Widget
都会创建 Element
对象,Widget
都会有对应的 Element
对象。
通过源码注释可以看到,只要有新的 Element
被添加都会调用 mount
方法。也就是说有新的 Widget
被创建的时候都会调用一次 mount
方法,因为 Widget
跟 Element
是一一对应的关系。
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
assert(() {
_debugDoingBuild = true;
return true;
}());
_renderObject = widget.createRenderObject(this);
}
如果是普通的 Element
调用完 mount
方法之后就结束了,但是继承于 RenderObjectElement
的子类在 mount
方法中会调用 createRenderObject
方法,创建 RenderObject
对象。
StatelessWidget 的 Element
abstract class Widget extends DiagnosticableTree {
Element createElement();
}
以上只附上了关键代码,通过源码我们可以看到 Widget
类都会实现 createElement
函数。这里我们先看下 StatelessWidget
类与 Element
的关系。
abstract class StatelessWidget extends Widget {
StatelessElement createElement() => StatelessElement(this);
}
StatelessWidget
中 createElement
方法会创建一个 StatelessElement
对象并加入到 Elment
树中,并且返回 StatelessElement
对象,创建 StatelessElement
对象的时候 StatelessWidget
自己作为参数传给 StatelessElement
对象。
class StatelessElement extends ComponentElement {
Widget build() => widget.build(this);
}
abstract class ComponentElement extends Element {
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
assert(_child == null);
assert(_lifecycleState == _ElementLifecycle.active);
_firstBuild();
assert(_child != null);
}
abstract class Element extends DiagnosticableTree implements BuildContext {
Element(Widget widget)
: assert(widget != null),
_widget = widget;
通过源码追踪可以看到 StatelessElement
继承于 ComponentElement
,ComponentElement
继承于 Element
,在 Element
的构造方法中外部传入的 widget
会被赋值给 _widget
属性,在 ComponentElement
中会调用 mount
方法, mount
方法中的 _firstBuild
最终会执行 ComponentElement
的 build
方法,并且 StatelessElement
的 build
会执行 widget.build(this)
,也就是调用 Widget
的 build
方法,如下代码所示,并把自己传给外面。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {}
}
所以我们外部 StatelessWidget
子类中的 context
就是 Element
对象。
StatefulWidget 的 Element
abstract class StatefulWidget extends Widget {
@override
StatefulElement createElement() => StatefulElement(this);
}
StatefulWidget
同样会执行 createElement
,但是返回的对象是 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);
}
但是 StatefulElement
多了一步就是执行 createState
函数,并且赋值给 _state
属性,并且把外部传入的 widget
赋值给 state._widget
属性,这也是我们能在 state
中能获取到 widget
的原因。并且会把 this
指针赋值给 state._element
,这里 build
方法中执行的是 state.build(this)
。