Flutter 中的组件绘制完成监听、组件生命周期和APP生命周期

Flutter 的 生命周期

说到 Flutter 的生命周期,其实就是说 StatefulWidget 的生命周期,因为 StatelessWidget 是静态控件。

StatefulWidget,通过借助于 State 对象,处理状态变化,并体现在 UI 上。这些阶段,就涵盖了一个组件从加载到卸载的全过程,即生命周期。

而一个应用的生命周期,包括了页面组件的生命周期和整个 app 的生命周期。我们分别了解下。

State 生命周期

首先我们看下 State 里面的这几个方法:

1 @override 2 void initState() { 3 super.initState(); 4 print('MyHomePage==initState'); 5 } 6 7 @override 8 void didChangeDependencies() { 9 super.didChangeDependencies(); 10 print('MyHomePage==didChangeDependencies'); 11 } 12 13 @override 14 void didUpdateWidget(MyHomePage oldWidget) { 15 super.didUpdateWidget(oldWidget); 16 print('MyHomePage==didUpdateWidget'); 17 } 18 19 @override 20 void reassemble() { 21 super.reassemble(); 22 print('MyHomePage==reassemble'); 23 } 24 25 @override 26 void deactivate() { 27 super.deactivate(); 28 print('MyHomePage==deactivate'); 29 } 30 31 @override 32 void dispose() { 33 print('MyHomePage==dispose'); 34 super.dispose(); 35 } 36 @override 37 Widget build(BuildContext context) { 38 print('MyHomePage==build'); 39 。。。 40 } 41

接下来我们运行应用,看下打印日志:

1flutter: MyHomePage==initState 2flutter: MyHomePage==didChangeDependencies 3flutter: MyHomePage==build

然后路由Navigator.push到新页面:

1flutter: SecondPage==initState 2flutter: SecondPage==didChangeDependencies 3flutter: SecondPage==build

然后路由Navigator.pop到回到首页:

1flutter: SecondPage==deactivate 2flutter: SecondPage==dispose

State 初始化时会依次执行 :构造方法 -> initState -> didChangeDependencies -> build,随后完成页面渲染。
State 销毁时会依次执行 :deactivate -> dispose 随后完成页面释放。

接下来我们看下这几个方法。

构造方法

构造方法是 State 生命周期的起点,Flutter 会通过调用 StatefulWidget.createState() 来创建一个 State。我们可以通过构造方法,来接收父 Widget 传递的初始化 UI 配置数据。这些配置数据,决定了 Widget 最初的呈现效果。

initState

当Widget第一次插入到Widget树时会被调用,对于每一个State对象,Flutter framework只会调用一次该回调,所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。
但是使用 InheritFromWidget 的时候,不能在该回调中调用BuildContext.dependOnInheritedWidgetOfExactType(该方法用于在Widget树上获取离当前widget最近的一个父级InheritFromWidget,原因是在初始化完成后,Widget树中的InheritFromWidget也可能会发生变化,所以正确的做法应该在在build()方法或didChangeDependencies()中调用它。

didChangeDependencies

didChangeDependencies 则用来专门处理 State 对象依赖关系变化,会在 initState() 调用结束后,被 Flutter 调用。
哪些情况下 State 对象的依赖关系会发生变化呢?比如使用 InheritedWidget 作数据共享的时候,InheritedWidget 的发生了变化,子widget的didChangeDependencies()回调都会被调用。典型的场景是,系统语言 Locale 或应用主题改变时,系统会通知 State 执行 didChangeDependencies 回调方法。

build

它主要是用于构建Widget子树的,会在如下场景被调用:

  • 在调用initState()之后。
  • 在调用didUpdateWidget()之后。
  • 在调用setState()之后。
  • 在调用didChangeDependencies()之后。
  • 在State对象从树中一个位置移除后(会调用deactivate)又重新插入到树的其它位置之后。

deactivate

deactivate():当State对象从树中被移除时,会调用此回调。在一些场景下,Flutter framework会将State对象重新插到树中,如包含此State对象的子树在树的一个位置移动到另一个位置时(可以通过GlobalKey来实现)。如果移除后没有重新插入到树中则紧接着会调用dispose()方法。

dispose

dispose():当State对象从树中被永久移除时调用;通常在此回调中释放资源。

didUpdateWidget

下面我们写个嵌套布局,在 MyHomePage 里嵌套 ChildView,然后在 MyHomePage 里调用 setState:

1 body: Center( 2 child: Column( 3 mainAxisAlignment: MainAxisAlignment.center, 4 children: <Widget>[ 5 FlatButton( 6 onPressed: () { 7 setState(() { 8 _count++; 9 }); 10 }, 11 child: Text('点击父控件'), 12 ), 13 ChildView(), 14 ], 15 ), 16 ),

看下打印信息:

1flutter: MyHomePage==build 2flutter: ChildView==didUpdateWidget 3flutter: ChildView==build

didUpdateWidget:当 Widget 的配置发生变化时,比如,父 Widget 触发重建(即父 Widget 的状态发生变化时),热重载时,系统会调用这个函数。

reassemble

上面我们说热重载的时候,didUpdateWidget会被调用,其实 reassemble 也会被调用,reassemble 是一个 debug 方法,在热重载的时候调用,我们点 IDE 的热重载按钮:

1Performing hot reload... 2Syncing files to device iPhone 11 Pro... 3flutter: MyHomePage==reassemble 4flutter: ChildView==reassemble 5flutter: MyHomePage==didUpdateWidget 6flutter: MyHomePage==build 7flutter: ChildView==didUpdateWidget 8flutter: ChildView==build 9Reloaded 1 of 513 libraries in 286ms.

会先深度调用 reassemble ,然后再 调用 didUpdateWidget 和 build。

生命周期.jpg

APP 的生命周期

WidgetsBindingObserver

在原生开发中,我们都会获取应用是否在前台的状态,在 Flutter 中同样需要。我们可以利用 WidgetsBindingObserver 实现。

1abstract class WidgetsBindingObserver { 2 //页面pop 3 Future didPopRoute() => Future.value(false); 4 5 //页面push 6 Future didPushRoute(String route) => Future.value(false); 7 8//系统窗口相关改变回调,如旋转 9 void didChangeMetrics() {} 10 11// 文本缩放系数变化 12 void didChangeTextScaleFactor() {} 13 14// 系统亮度变化 15 void didChangePlatformBrightness() {} 16 17// 本地化语言变化 18 void didChangeLocales(List locale) {} 19 20// App生命周期变化 21 void didChangeAppLifecycleState(AppLifecycleState state) {} 22 23// 内存警告回调 24 void didHaveMemoryPressure() {} 25 26// Accessibility相关特性回调 27 void didChangeAccessibilityFeatures() {} 28}

可以看到,WidgetsBindingObserver 这个类提供的回调函数非常丰富,常见的屏幕旋转、屏幕亮度、语言变化、内存警告都可以通过这个实现进行回调。我们通过给 WidgetsBinding 的单例对象设置监听器,就可以监听对应的回调方法。

下面我们写个demo:

1class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver { 2 @override 3 void initState() { 4 super.initState(); 5 WidgetsBinding.instance.addObserver(this); 6 } 7 8 @override 9 void dispose() { 10 WidgetsBinding.instance.removeObserver(this); 11 super.dispose(); 12 } 13 14 @override 15 void didChangeAppLifecycleState(AppLifecycleState state) async { 16 print("$state"); 17 if (state == AppLifecycleState.resumed) { 18 } 19 } 20 }

从前台切到后台:

1flutter: AppLifecycleState.inactive 2flutter: AppLifecycleState.paused

再从后台切刀前台:

1flutter: AppLifecycleState.inactive 2flutter: AppLifecycleState.resumed

didChangeAppLifecycleState

didChangeAppLifecycleState 回调函数中,有一个参数类型为 AppLifecycleState 的枚举类,这个枚举类是 Flutter 对 App 生命周期状态的封装。它的常用状态包括 resumed、inactive、paused 这三个。

  • resumed:可见的,并能响应用户的输入。
  • inactive:处在不活动状态,无法处理用户响应。
  • paused:不可见并不能响应用户的输入,但是在后台继续活动中。

前后台.001.jpeg

封装一个工具类:

1class LifecycleEventHandler extends WidgetsBindingObserver{ 2 static const String TAG = '==lifecycle_event_handler=='; 3 final AsyncCallback resumeCallBack; 4 final AsyncCallback suspendingCallBack; 5 6 LifecycleEventHandler({ 7 this.resumeCallBack, 8 this.suspendingCallBack, 9 }); 10 11 @override 12 Future<Null> didChangeAppLifecycleState(AppLifecycleState state) async { 13 switch (state) { 14 case AppLifecycleState.resumed: 15 if (resumeCallBack != null) { 16 await resumeCallBack(); 17 } 18 break; 19 case AppLifecycleState.inactive: 20 case AppLifecycleState.paused: 21 case AppLifecycleState.detached: 22 if (suspendingCallBack != null) { 23 await suspendingCallBack(); 24 } 25 break; 26 } 27 } 28}

State就不需要再混入WidgetsBindingObserver ,使用起来就简单多了:

1class _MyHomePageState extends State<MyHomePage> { 2 3 LifecycleEventHandler _lifecycleEventHandler = LifecycleEventHandler( 4 resumeCallBack: () async {}, suspendingCallBack: () async {}); 5 6 @override 7 void initState() { 8 super.initState(); 9 WidgetsBinding.instance.addObserver(_lifecycleEventHandler); 10 } 11 @override 12 void dispose() { 13 WidgetsBinding.instance.removeObserver(_lifecycleEventHandler); 14 super.dispose(); 15 } 16 }

另一种用法:
我们可以在入口函数里直接调用,这样就不用侵入widget了:

1void main() { 2 WidgetsFlutterBinding.ensureInitialized(); 3 WidgetsBinding.instance.addObserver(LifecycleEventHandler( 4 resumeCallBack: () async {}, suspendingCallBack: () async {})); 5 6 runApp(MyApp()); 7}

SystemChannels.lifecycle

除了上面的方法,Flutter 还为我们提供了一种方法, SystemChannels.lifecycle,同样可以监听到 APP 的生命周期:

1class _MyHomePageState extends State<MyHomePage> { 2 @override 3 void initState() { 4 super.initState(); 5 SystemChannels.lifecycle.setMessageHandler((msg) { 6 switch (msg) { 7 case "AppLifecycleState.paused": 8 print(msg); 9 break; 10 case "AppLifecycleState.inactive": 11 print(msg); 12 break; 13 case "AppLifecycleState.resumed": 14 print(msg); 15 break; 16 default: 17 break; 18 } 19 }); 20 } 21 22 @override 23 void dispose() { 24 super.dispose(); 25 } 26}

前后台切换:

1Syncing files to device iPhone 11 Pro... 2flutter: MyHomePage==build 3flutter: AppLifecycleState.inactive 4flutter: AppLifecycleState.paused 5flutter: AppLifecycleState.inactive 6flutter: AppLifecycleState.resumed

同样我们可以封装个工具类:

1import 'package:flutter/foundation.dart'; 2import 'package:flutter/material.dart'; 3import 'package:flutter/services.dart'; 4 5class LifecycleEventHandler { 6 final AsyncCallback resumeCallBack; 7 final AsyncCallback suspendingCallBack; 8 9 LifecycleEventHandler({ 10 this.resumeCallBack, 11 this.suspendingCallBack, 12 }) { 13 SystemChannels.lifecycle.setMessageHandler((msg) async { 14 switch (msg) { 15 case "AppLifecycleState.resumed": 16 if (resumeCallBack != null) { 17 await resumeCallBack(); 18 } 19 break; 20 case "AppLifecycleState.paused": 21 case "AppLifecycleState.detached": 22 if (suspendingCallBack != null) { 23 await suspendingCallBack(); 24 } 25 break; 26 default: 27 break; 28 } 29 }); 30 } 31} 32

然后使用:

1class _MyHomePageState extends State<MyHomePage> { 2 @override 3 void initState() { 4 super.initState(); 5 handleAppLifecycleState(resumeCallBack:()async{ 6 print('resumeCallBack'); 7 },suspendingCallBack:()async{ 8 print('suspendingCallBack'); 9 }); 10 } 11 }

切换前后台:

1flutter: MyHomePage==build 2flutter: suspendingCallBack 3flutter: resumeCallBack

为了不入侵 Widget ,同样可以在入口函数处调用:

1void main() { 2 WidgetsFlutterBinding.ensureInitialized(); 3 handleAppLifecycleState(resumeCallBack: () async { 4 print('resumeCallBack'); 5 }, suspendingCallBack: () async { 6 print('suspendingCallBack'); 7 }); 8 runApp(MyApp()); 9} 10

View 绘制完成

在原生中,还有一个常用操作是监听 View 绘制完成,防止空指针。

1//view重绘时回调 2view.getViewTreeObserver().addOnDrawListener(new OnDrawListener() { 3 @Override 4 public void onDraw() { 5 // TODO Auto-generated method stub 6 7 } 8

Flutter 同样给我们有两个回调函数:

  1. addPostFrameCallback 只有首次绘制完才回调

  2. addPersistentFrameCallback 每次重绘都回调

    @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addPostFrameCallback(() { print("单次Frame绘制回调"); //只回调一次 }); WidgetsBinding.instance.addPersistentFrameCallback(() { print("实时Frame绘制回调"); //每帧都回调 }); }

    flutter: MyHomePage==build flutter: 实时Frame绘制回调 flutter: 单次Frame绘制回调 flutter: 实时Frame绘制回调

点击 setState:

1flutter: MyHomePage==build 2flutter: 实时Frame绘制回调 3flutter: 实时Frame绘制回调 4flutter: 实时Frame绘制回调 5flutter: 实时Frame绘制回调 6flutter: 实时Frame绘制回调 7flutter: 实时Frame绘制回调 8flutter: 实时Frame绘制回调 9flutter: 实时Frame绘制回调

因为是递归回调的,所以会调用多次。
有了这两个函数,我们可以实现原生一样的功能。

4人点赞

日记本

作者:A_si
链接:https://www.jianshu.com/p/ab3cd84f624c
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

React Native 中组件的生命周期

概述就像Android开发中的View一样,ReactNative(RN)中的组件也有生命周期(Lifecycle)。所谓生命周期,就是一个对象从开始生成到最后消亡所经历的状态,理解生命周期,是合理开发的关键。RN组件的生命周期整理如下图:!33componentlifecycle(https://static.osch