Flutter是一个由Google开发的开源移动应用程序开发框架。它使用Dart语言开发,可以一次编码生成iOS和Android两个平台的应用。
下面我会为您详细解析在Flutter中页面(Widget
)的生命周期:
在Flutter中,每一个组件即为一个Widget,每个Widget都会经历创建、更新和销毁三个主要阶段。根据它们的特点,Widget分为两种:StatelessWidget
和StatefulWidget
。
-
StatelessWidget
是一个不可变的Widget,它描述了在给定配置下应该如何渲染。当调用build()
时,会根据现有的配置信息和上下文信息来创建widget树。它只有一个生命周期函数,就是build()
。 -
StatefulWidget
是可以改变状态的Widget,它至少由两个类组成:StatefulWidget
和State
。生命周期方法主要在State
对象中。以下是StatefulWidget
的主要生命周期方法:-
createState()
: 当我们构建一个新的StatefulWidget时,这个方法会被立即调用。这个方法必须被重写。 -
initState()
: 这是第一个在State对象生命周期中调用的方法,它在createState()
之后立即被调用,这是一个初始化阶段,您可以在此期间执行您需要在Widget创建时执行的任何数据初始化。 -
didChangeDependencies()
: 当State对象的依赖关系改变时调用。例如,之前调用过InheritedWidget
,那么这个方法会在第一次构建后和每次改变InheritedWidget时都会被调用。 -
build()
: 这是一个必须实现的方法,用于构建Widget子树。build()
方法会在initState()
和didUpdateWidget()
之后立即被调用,同时也会在每次调用setState()
后调用。 -
didUpdateWidget(OldWidget oldWidget)
: 当父Widget发生改变并重构当前的StatefulWidget时,它将调用此方法。 -
dispose()
: 当State对象从树中被永久移除时,dispose()会被调用。通常会在这个阶段取消网络请求,或者停止运行的动画。一旦执行了此方法,你就不能再次使用此State对象了。
-
在理解了以上生命周期之后,我们就可以更好地在Flutter应用开发中控制Widget的行为,提升应用的性能和用户体验。
每个生命周期方法在 Flutter 的 StatefulWidget
组件中都有特定的使用场景,以下是这些方法通常的使用情况:
-
createState()
: 该方法是在我们构建一个新的 StatefulWidget 时被调用,一般我们会在这个方法中创建一个新的 State 对象。该方法对于 StatefulWidget 的生命周期是必须的。 -
initState()
: 该方法在 Flutter 构建 Widget 时会被调用,这是一个初始化阶段,您可以在此期间执行一些初始工作,例如数据初始化、订阅子项等。例如,如果您的 Widget 需要读取用户的本地数据,或者需要在加载时订阅一个流,这些都可以在initState()
中进行。 -
didChangeDependencies()
: 这个函数在initState()
之后和build()
之前被调用,它会在依赖的 State 对象改变位置时被 Flutter 框架调用。如果您的 Widget 依赖于 InheritedWidget,那么当 InheritedWidget 发生改变时,didChangeDependencies()
会被调用。这个方法常用于进行一些耗时操作,例如网络请求。 -
build()
: 这是一个必须实现的方法,用于描述该 Widget 的部分 UI 外观。当您调用setState()
更新 Widget 状态时,Flutter 会重新运行这个build()
方法来根据最新状态构建界面。这个方法会频繁地调用,所以在这个方法中应避免做任何耗时操作。 -
didUpdateWidget(OldWidget oldWidget)
: 当 Widget 的配置改变时就会调用此方法(例如,在父 Widget 重建并创建新的 Widget,或者当前 Widget 的依赖 Widget 有所改变时),这是一个比较好的地方去做一些相应的逻辑,比如可能需要根据新的配置信息来更新显示内容。 -
dispose()
: 当 Widget 被从渲染树中永久移除时,会调用该方法。这是取消监听、动画、或者其他可能产生内存泄漏的操作的地方。在这个方法中,你应该释放所有资源,如:关闭网络连接、数据库访问等。
希望这个解答对你有帮助!
当调用setState方法会重新调用Build进行渲染
setState方法内部主要是利用_element(本质是就是context对象)调用markNeedsBuild
/// Notify the framework that the internal state of this object has changed. /// /// Whenever you change the internal state of a [State] object, make the /// change in a function that you pass to [setState]: /// /// ```dart /// setState(() { _myState = newValue; }); /// ``` /// /// The provided callback is immediately called synchronously. It must not /// return a future (the callback cannot be `async`), since then it would be /// unclear when the state was actually being set. /// /// Calling [setState] notifies the framework that the internal state of this /// object has changed in a way that might impact the user interface in this /// subtree, which causes the framework to schedule a [build] for this [State] /// object. /// /// If you just change the state directly without calling [setState], the /// framework might not schedule a [build] and the user interface for this /// subtree might not be updated to reflect the new state. /// /// Generally it is recommended that the [setState] method only be used to /// wrap the actual changes to the state, not any computation that might be /// associated with the change. For example, here a value used by the [build] /// function is incremented, and then the change is written to disk, but only /// the increment is wrapped in the [setState]: /// /// ```dart /// Future<void> _incrementCounter() async { /// setState(() { /// _counter++; /// }); /// Directory directory = await getApplicationDocumentsDirectory(); // from path_provider package /// final String dirName = directory.path; /// await File('$dirName/counter.txt').writeAsString('$_counter'); /// } /// ``` /// /// It is an error to call this method after the framework calls [dispose]. /// You can determine whether it is legal to call this method by checking /// whether the [mounted] property is true. @protected void setState(VoidCallback fn) { assert(() { if (_debugLifecycleState == _StateLifecycle.defunct) { throw FlutterError.fromParts(<DiagnosticsNode>[ ErrorSummary('setState() called after dispose(): $this'), ErrorDescription( 'This error happens if you call setState() on a State object for a widget that ' 'no longer appears in the widget tree (e.g., whose parent widget no longer ' 'includes the widget in its build). This error can occur when code calls ' 'setState() from a timer or an animation callback.', ), ErrorHint( 'The preferred solution is ' 'to cancel the timer or stop listening to the animation in the dispose() ' 'callback. Another solution is to check the "mounted" property of this ' 'object before calling setState() to ensure the object is still in the ' 'tree.', ), ErrorHint( 'This error might indicate a memory leak if setState() is being called ' 'because another object is retaining a reference to this State object ' 'after it has been removed from the tree. To avoid memory leaks, ' 'consider breaking the reference to this object during dispose().', ), ]); } if (_debugLifecycleState == _StateLifecycle.created && !mounted) { throw FlutterError.fromParts(<DiagnosticsNode>[ ErrorSummary('setState() called in constructor: $this'), ErrorHint( 'This happens when you call setState() on a State object for a widget that ' "hasn't been inserted into the widget tree yet. It is not necessary to call " 'setState() in the constructor, since the state is already assumed to be dirty ' 'when it is initially created.', ), ]); } return true; }()); final Object? result = fn() as dynamic; assert(() { if (result is Future) { throw FlutterError.fromParts(<DiagnosticsNode>[ ErrorSummary('setState() callback argument returned a Future.'), ErrorDescription( 'The setState() method on $this was called with a closure or method that ' 'returned a Future. Maybe it is marked as "async".', ), ErrorHint( 'Instead of performing asynchronous work inside a call to setState(), first ' 'execute the work (without updating the widget state), and then synchronously ' 'update the state inside a call to setState().', ), ]); } // We ignore other types of return values so that you can do things like: // setState(() => x = 3); return true; }()); _element!.markNeedsBuild(); }
以下是 setState
方法源代码的主要内容和解析:
void setState(VoidCallback fn) { assert(() { // ... (省略部分代码) return true; }()); final Object? result = fn() as dynamic; assert(() { if (result is Future) { // ... (省略部分代码) throw FlutterError.fromParts(<DiagnosticsNode>[ // ... (省略部分代码) ]); } return true; }()); _element!.markNeedsBuild(); }
-
setState
方法接受一个回调函数fn
作为参数。这个回调函数包含了需要改变 state 的所有操作。 -
一系列的
assert
语句用于在 debug 模式下进行一些错误检查。例如,setState
不应该在dispose
方法之后被调用,也不应该在构造函数中被调用。而且,传入setState
的回调函数不应该是异步的,也就是不能返回一个Future
对象。如果违反了这些规则,就会抛出一个异常。 -
fn() as dynamic
这一行是调用传入的回调函数fn
,并将结果转换为动态类型dynamic
。 -
最后,
_element!.markNeedsBuild()
这一行告诉 Flutter 的渲染引擎,这个 widget 需要被重新构建(即,重新调用build
方法)。这是因为 state 已经改变,所以可能需要重新渲染 UI 来反映这些改变。
总的来说,setState
的作用就是改变 state,并通知 Flutter 这个 widget 需要被重新构建。这是 Flutter 实现响应式 UI 的重要机制。
_element 就是 Context
/// The location in the tree where this widget builds. /// /// The framework associates [State] objects with a [BuildContext] after /// creating them with [StatefulWidget.createState] and before calling /// [initState]. The association is permanent: the [State] object will never /// change its [BuildContext]. However, the [BuildContext] itself can be moved /// around the tree. /// /// After calling [dispose], the framework severs the [State] object's /// connection with the [BuildContext]. BuildContext get context { assert(() { if (_element == null) { throw FlutterError( 'This widget has been unmounted, so the State no longer has a context (and should be considered defunct). \n' 'Consider canceling any active work during "dispose" or using the "mounted" getter to determine if the State is still active.', ); } return true; }()); return _element!; } StatefulElement? _element;
上述代码片段是获取State对象的context
的部分实现,context
在Flutter中扮演着非常重要的角色,是和Widget、Element、State等对象交互的一个主要途径。
这段代码中定义了一个context
的 getter,返回的是与当前State对象关联的BuildContext
。注意这里是一个assert
的使用,它会在debug模式下进行一些错误检查。如果当前的State对象已经被卸载(也就是说_element
为null),那么尝试获取context
就会抛出一个异常。这是因为一旦State对象被卸载,它就不再有与之关联的BuildContext
,此时应当视为失效(defunct)。
而StatefulElement? _element;
是一个私有变量,它是BuildContext
的一个具体实现,对应着当前State对象在Widget树中的位置。当我们通过context
这个getter获取BuildContext
时,实际上就是返回的这个_element
。
在State对象的生命周期中,与其关联的BuildContext
(也就是_element
)在createState
方法之后、initState
方法之前被框架赋值,之后这个关联就是永久的,也就是说在State对象的生命周期中,它的BuildContext
不会改变。但是,BuildContext
自身是可以在Widget树中移动的。
在dispose
方法被调用之后,框架会切断State对象和BuildContext
的连接,这样就无法再通过context
获取到BuildContext
了。
总的来说,context
是Flutter中一个非常重要的概念,它提供了访问和操作Widget树的各种能力。
/// Marks the element as dirty and adds it to the global list of widgets to /// rebuild in the next frame. /// /// Since it is inefficient to build an element twice in one frame, /// applications and widgets should be structured so as to only mark /// widgets dirty during event handlers before the frame begins, not during /// the build itself. void markNeedsBuild() { assert(_lifecycleState != _ElementLifecycle.defunct); if (_lifecycleState != _ElementLifecycle.active) { return; } assert(owner != null); assert(_lifecycleState == _ElementLifecycle.active); assert(() { if (owner!._debugBuilding) { assert(owner!._debugCurrentBuildTarget != null); assert(owner!._debugStateLocked); if (_debugIsInScope(owner!._debugCurrentBuildTarget!)) { return true; } final List<DiagnosticsNode> information = <DiagnosticsNode>[ ErrorSummary('setState() or markNeedsBuild() called during build.'), ErrorDescription( 'This ${widget.runtimeType} widget cannot be marked as needing to build because the framework ' 'is already in the process of building widgets. A widget can be marked as ' 'needing to be built during the build phase only if one of its ancestors ' 'is currently building. This exception is allowed because the framework ' 'builds parent widgets before children, which means a dirty descendant ' 'will always be built. Otherwise, the framework might not visit this ' 'widget during this build phase.', ), describeElement('The widget on which setState() or markNeedsBuild() was called was'), ]; if (owner!._debugCurrentBuildTarget != null) { information.add(owner!._debugCurrentBuildTarget!.describeWidget('The widget which was currently being built when the offending call was made was')); } throw FlutterError.fromParts(information); } else if (owner!._debugStateLocked) { throw FlutterError.fromParts(<DiagnosticsNode>[ ErrorSummary('setState() or markNeedsBuild() called when widget tree was locked.'), ErrorDescription( 'This ${widget.runtimeType} widget cannot be marked as needing to build ' 'because the framework is locked.', ), describeElement('The widget on which setState() or markNeedsBuild() was called was'), ]); } return true; }()); if (dirty) { return; } _dirty = true; owner!.scheduleBuildFor(this); }
这段代码是 markNeedsBuild
方法的实现,它的作用是将当前的 Element 标记为“dirty”(需要重建),并将它添加到下一帧需要重建的全局 Widget 列表中。
以下是该方法的详细解析:
-
assert(_lifecycleState != _ElementLifecycle.defunct);
这一行代码用于断言 Element 对象的生命周期状态不是“已销毁”,否则会抛出异常。这是因为已经销毁的 Element 对象不应该再被标记为需要重建。 -
if (_lifecycleState != _ElementLifecycle.active) { return; }
这一行代码用于检查 Element 对象的生命周期状态是否为“活跃”,如果不是,就直接返回,不进行后续的操作。这是因为只有活跃的 Element 对象才需要被重建。 -
在一些
assert
语句中,进行了更多的错误检查。例如,不应该在构建过程中调用setState
或markNeedsBuild
方法,也不应该在 Widget 树被锁定时调用这些方法。如果违反了这些规则,就会抛出一个异常。 -
if (dirty) { return; }
这一行代码用于检查 Element 对象是否已经被标记为需要重建,如果已经被标记,就直接返回,不进行后续的操作。这是为了避免重复标记。 -
_dirty = true;
这一行代码将 Element 对象标记为需要重建。 -
owner!.scheduleBuildFor(this);
这一行代码将 Element 对象添加到下一帧需要重建的全局 Widget 列表中。这是通过调用 Element 对象的owner
(也就是 Element 树的根)的scheduleBuildFor
方法实现的。
总的来说,markNeedsBuild
方法的作用就是将 Element 对象标记为需要重建,并通知 Flutter 的渲染引擎在下一帧重建它。这是 Flutter 实现响应式 UI 的重要机制之一。
/// Adds an element to the dirty elements list so that it will be rebuilt /// when [WidgetsBinding.drawFrame] calls [buildScope]. void scheduleBuildFor(Element element) { assert(element.owner == this); assert(() { if (debugPrintScheduleBuildForStacks) { debugPrintStack(label: 'scheduleBuildFor() called for $element${_dirtyElements.contains(element) ? " (ALREADY IN LIST)" : ""}'); } if (!element.dirty) { throw FlutterError.fromParts(<DiagnosticsNode>[ ErrorSummary('scheduleBuildFor() called for a widget that is not marked as dirty.'), element.describeElement('The method was called for the following element'), ErrorDescription( 'This element is not current marked as dirty. Make sure to set the dirty flag before ' 'calling scheduleBuildFor().', ), ErrorHint( 'If you did not attempt to call scheduleBuildFor() yourself, then this probably ' 'indicates a bug in the widgets framework. Please report it:\n' ' https://github.com/flutter/flutter/issues/new?template=2_bug.md', ), ]); } return true; }()); if (element._inDirtyList) { assert(() { if (debugPrintScheduleBuildForStacks) { debugPrintStack(label: 'BuildOwner.scheduleBuildFor() called; _dirtyElementsNeedsResorting was $_dirtyElementsNeedsResorting (now true); dirty list is: $_dirtyElements'); } if (!_debugIsInBuildScope) { throw FlutterError.fromParts(<DiagnosticsNode>[ ErrorSummary('BuildOwner.scheduleBuildFor() called inappropriately.'), ErrorHint( 'The BuildOwner.scheduleBuildFor() method should only be called while the ' 'buildScope() method is actively rebuilding the widget tree.', ), ]); } return true; }()); _dirtyElementsNeedsResorting = true; return; } if (!_scheduledFlushDirtyElements && onBuildScheduled != null) { _scheduledFlushDirtyElements = true; onBuildScheduled!(); } _dirtyElements.add(element); element._inDirtyList = true; assert(() { if (debugPrintScheduleBuildForStacks) { debugPrint('...dirty list is now: $_dirtyElements'); } return true; }()); }
这段代码是 scheduleBuildFor
方法的实现。该方法用于将指定的 Element 对象添加到“dirty elements”列表中,以便在 WidgetsBinding.drawFrame
调用 buildScope
时重建它。
以下是该方法的详细解析:
-
assert(element.owner == this);
:这一行代码用于断言要添加到列表中的 Element 对象的所有者就是当前的对象。 -
assert(() {...}())
:这部分代码是一个断言函数,其中包含了一些错误检查。例如,不应该为一个没有被标记为“dirty”的 Element 对象调用scheduleBuildFor
方法,也不应该在构建范围(build scope)之外调用scheduleBuildFor
方法。如果违反了这些规则,就会抛出一个异常。 -
if (element._inDirtyList) {...}
:这部分代码用于检查 Element 对象是否已经在“dirty elements”列表中。如果已经在列表中,就将_dirtyElementsNeedsResorting
标记设置为true
,然后返回。这是为了避免重复添加。 -
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {...}
:这部分代码用于检查是否已经安排了清空“dirty elements”列表,并且“onBuildScheduled”回调是否存在。如果这两个条件都满足,就调用“onBuildScheduled”回调,并将_scheduledFlushDirtyElements
标记设置为true
。 -
_dirtyElements.add(element);
:这一行代码将 Element 对象添加到“dirty elements”列表中。 -
element._inDirtyList = true;
:这一行代码将 Element 对象的_inDirtyList
标记设置为true
,表示它已经在“dirty elements”列表中。
总的来说,scheduleBuildFor
方法的作用就是将指定的 Element 对象添加到“dirty elements”列表中,以便在下一帧重建它。这是 Flutter 实现响应式 UI 的重要机制之一。
InheritedWidget 的使用
InheritedWidget
是一个非常重要的组件,它提供了一种在 Widget 树中有效地共享数据的方式。以下是一个简单的例子,展示了如何使用 InheritedWidget
:
假设我们有一个 CounterData
类,它将包含我们希望在多个组件中共享的数据:
class CounterData { CounterData(this.counter); final int counter; }
然后,我们可以创建一个 InheritedWidget
,将 CounterData
作为它的数据:
class CounterProvider extends InheritedWidget { CounterProvider({ Key? key, required this.data, required Widget child, }) : super(key: key, child: child); final CounterData data; @override bool updateShouldNotify(CounterProvider oldWidget) { return data.counter != oldWidget.data.counter; } static CounterProvider? of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<CounterProvider>(); } }
在上述代码中,CounterProvider
是继承自 InheritedWidget
的类,它接受一个 CounterData
对象作为参数,然后在 updateShouldNotify
方法中,它会检查当前的计数器值是否与旧的值不同。如果不同,它将通知依赖于这个 InheritedWidget
的所有小部件进行重建。of
方法是一个方便的方法,用于从构建上下文中找到最近的 CounterProvider
。
然后我们可以这样在 Widget 树中使用它:
void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return CounterProvider( data: CounterData(0), child: MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ), ); } } class MyHomePage extends StatelessWidget { MyHomePage({Key? key, this.title}) : super(key: key); final String? title; @override Widget build(BuildContext context) { CounterData counterData = CounterProvider.of(context)!.data; return Scaffold( appBar: AppBar(title: Text(title!)), body: Center(child: Text('Counter: ${counterData.counter}')), floatingActionButton: FloatingActionButton( onPressed: () {}, tooltip: 'Increment', child: Icon(Icons.add), ), ); } }
在上述代码中,我们在 MyApp
中使用 CounterProvider
包裹了 MaterialApp
,并传入了初始化的 CounterData
。然后在 MyHomePage
中,我们通过 CounterProvider.of(context)!.data
获取到 CounterData
,并使用它的 counter
值显示在页面上。
这只是一个简单的使用 InheritedWidget
的例子。实际上,在大型的 Flutter 应用中,我们通常会使用某些状态管理库(如 provider
或 riverpod
)来管理和共享状态,这些库的底层也是基于 InheritedWidget
实现的。
并不是所有 Widget 都有对应的 RenderObjectWidget
Container
是一个简单的 convenience widget,它将一些常用的 widget 组合在一起以提供一种方便的方式来应用 padding,margins,borders,和背景色。然而,Container
本身并不直接提供任何渲染(即,它不直接创建任何 RenderObject
),因此它不是 RenderObjectWidget
。
当你创建一个 Container
时,你可能会给它提供一些参数,例如 padding
, decoration
, margin
, transform
, child
等。然后,根据你提供的参数,Container
将创建一系列其他的 widget,例如 Padding
, DecoratedBox
, Transform
, ConstrainedBox
等。所有这些 widget 都是 RenderObjectWidget
或者它们自己可能会创建更多的 RenderObjectWidget
。
简单地说,Container
是一个组合 widget,它组合了多种其他的 widget 来实现它的功能。这样的设计使得 Container
能够非常灵活,而不需要自己直接处理渲染逻辑。
Flutter 渲染过程可以大致分为三个阶段
在 Flutter 中,渲染过程可以大致分为三个阶段:Widget 树、Element 树、Render 树。这些树形结构代表了应用的不同状态和行为。
-
Widget 树:这是最直接对应于应用代码的树形结构。每一个
Widget
都是一个不可变的配置描述,用来生成 Element 和 RenderObject。通常我们在编写 UI 代码时,就是在构造这个 Widget 树。 -
Element 树:Element 是实际运行的应用的运行时表示,它们是持久的、可变的,并且用于连接 Widget 和 RenderObject 的桥梁。每个 Widget 都有对应的 Element 类型,比如 Stateless Widget 对应 StatelessElement,Stateful Widget 对应 StatefulElement。当 Flutter 需要构建或者更新 UI 时,它首先会通过 Widget 来创建或者更新 Element 树。这个过程被称为 "mount"。
在这个过程中,
Widget.createElement()
方法会被调用,用于生成 Element。然后,Element.mount()
方法会被调用,这时 Element 会创建或更新对应的 RenderObject,并添加到 Render 树中。 -
Render 树:Render 树是描述如何在屏幕上渲染 UI 的树形结构。每个 RenderObject 包含了布局和绘制的逻辑。一旦 Element 树更新,它就会同步更新 Render 树。
在简单的理解中,Widget 树可以视为蓝图,Element 树是建筑工地,Render 树则是最终的建筑。在应用运行时,Widget 树会被转换为 Element 树,Element 树再生成 Render 树。当 Widget 更新时,Element 树会对应更新,并重新构建 Render 树来反映新的 UI。
在 Flutter 中,渲染的过程是通过调用栈来实现的。下面是一个简单的描述:
-
程序启动:当 Flutter 程序启动时,会首先创建一个根 Widget,这通常是 MaterialApp 或 CupertinoApp,这个根 Widget 会生成对应的 Element,并开始构建 Element 树。
-
Element 创建和挂载:当根 Widget 被创建时,它的
createElement
方法会被调用,生成对应的 Element。然后,Element 的mount
方法会被调用,开始构建 UI。 -
创建子 Element:在 Element 的
mount
方法中,它会调用updateChild
方法来创建或更新子 Element。这会导致子 Widget 的createElement
方法被调用,然后子 Element 的mount
方法被调用。 -
更新 RenderObject:在 Element 的
mount
方法中,它还会创建或更新对应的 RenderObject,并添加到 Render 树中。这是通过调用updateRenderObject
方法来完成的。
这个过程是递归的,也就是说,mount
方法会一直递归调用,直到整个 Element 树都被创建和挂载。这样,对应的 RenderObject 也就被添加到 Render 树中,UI 就可以被渲染出来了。
注意,当 Widget 需要更新时(例如,调用 setState
之后),Widget 会生成一个新的 Widget 树。然后,新的 Widget 树会和旧的 Element 树进行对比(这个过程称为 "diffing"),然后更新对应的 Element 和 RenderObject。
这个调用栈是由 Flutter 的框架代码来管理的,应用代码通常不需要直接操作这个调用栈。我们只需要创建和更新 Widget,Flutter 就会自动处理剩下的事情。