這是我參與8月更文挑戰的第6天,活動詳情查看:8月更文挑戰android
前言:生命週期是一個組件加載到卸載的整個週期,熟悉生命週期可讓咱們在合適的時機作該作的事情, flutter中的State生命週期和android以及React Native的生命週期相似。markdown
先看一張生命週期的流程圖:app
大體能夠分爲3個階段:框架
State初始化時會依次執行 : 構造函數 > initState > didChangeDependencies > Widget build , 此時頁面加載完成。async
而後咱們看一下每一個函數的意義:ide
調用次數:1次函數
這個函數嚴格意義上來說不屬於生命週期的一部分,由於這個時候State的widget屬性爲空,沒法在構造函數中訪問widget的屬性 。可是構造函數必然是要第一個調用的。能夠在這一部分接收前一個頁面傳遞過來的數據。佈局
Called when this object is inserted into the tree.post
調用次數:1次ui
當插入渲染樹的時候調用,這個函數在生命週期中只調用一次。這裏能夠作一些初始化工做,好比初始化State的變量。
Called when a dependency of this [State] object changes.
這個函數會緊跟在initState以後調用,而且能夠調用BuildContext.inheritFromWidgetOfExactType,那麼BuildContext.inheritFromWidgetOfExactType的使用場景是什麼呢?最經典的應用場景是
new DefaultTabController(length: 3, child: new TabBar(
tabs: [ "主頁","訂單","個人" ]
.map( (data)=>new Text(data) ).toList(),
複製代碼
TabBar原本須要定義一個TabController,可是在外面套一層DefaultTabController就不須要定義TabContrller了,看下源碼:
@override
void didChangeDependencies() {
super.didChangeDependencies();
_updateTabController();
_initIndicatorPainter();
}
void _updateTabController() {
final TabController newController = widget.controller ?? DefaultTabController.of(context);
...
}
複製代碼
注意到這裏DefaultTabController.of(context)
static TabController of(BuildContext context) {
final _TabControllerScope scope = context.inheritFromWidgetOfExactType(_TabControllerScope);
return scope?.controller;
}
複製代碼
實際上就是調用BuildContext.inheritFromWidgetOfExactType,也就說在didChangeDependencies中,能夠跨組件拿到數據。
#運行時
調用次數:屢次
初始化以後開始繪製界面,當setState觸發的時候會再次被調用
Called whenever the widget configuration changes.
祖先節點rebuild widget時調用 .當組件的狀態改變的時候就會調用didUpdateWidget.
理論上setState的時候會調用,但我實際操做的時候發現只是作setState的操做的時候沒有調用這個方法。而在我改變代碼hot reload時候會調用 didUpdateWidget 並執行 build...
實際上這裏flutter框架會建立一個新的Widget,綁定本State,並在這個函數中傳遞老的Widget。 這個函數通常用於比較新、老Widget,看看哪些屬性改變了,並對State作一些調整。
須要注意的是,涉及到controller的變動,須要在這個函數中移除老的controller的監聽,並建立新controller的監聽。
組件移除,例如頁面銷燬的時候會依次執行:deactivate > dispose
Called when this object is removed from the tree.
在dispose以前,會調用這個函數。當組件卸載時會先一步dispose調用。
Called when this object is removed from the tree permanently.
調用次數:1次
一旦到這個階段,組件就要被銷燬了,這個函數通常會移除監聽,清理環境。
##reassemble hot reload調用
名稱 | 狀態 |
---|---|
initState | 插入渲染樹時調用,只調用一次 |
didChangeDependencies | state依賴的對象發生變化時調用 |
didUpdateWidget | 組件狀態改變時候調用,可能會調用屢次 |
build | 構建Widget時調用 |
deactivate | 當移除渲染樹的時候調用 |
dispose | 組件即將銷燬時調用 |
假設咱們從A頁面跳轉到B頁面, 那麼A,B頁面的生命週期會是怎樣的呢?
B頁面進入初始化狀態,依次執行4個函數:構造函數 > initState > didChangeDependencies > Widget build
, 此時頁面加載完成,進入運行態。 此時A頁面依次執行deactivate > build
函數。注意 此時A頁面並未卸載。
而後咱們假設B頁面只有一個按鈕,點擊B頁面中的按鈕,改變按鈕的文字,會執行widget的build
方法 ,(理論上也應該執行didUpdateWidget
,但我這裏沒有)。
這時,咱們點擊返回鍵從B頁面返回到A頁面。 A頁面從新顯示,B頁面開始卸載。 那麼A先執行deactivate > build
, 而後B頁面依次執行:deactivate > dispose
。 此時A頁面進入運行態,B頁面移除。
本次示例B頁面代碼:
/*
* Created by 李卓原 on 2018/9/13.
* email: zhuoyuan93@gmail.com
*
*/
import 'package:flutter/material.dart';
class NewsDetailPage extends StatefulWidget {
@override
State<StatefulWidget> createState() => NewsDetailState();
}
class NewsDetailState extends State<NewsDetailPage> {
int text = 1;
NewsDetailState() {
print('構造函數');
}
@override
void initState() {
print('init state');
super.initState();
}
@override
void didChangeDependencies() {
print('didChangeDependencies');
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
print('widget build');
return Scaffold(
body: Center(
child: _loading(),
),
appBar: AppBar(
title: Text('諮詢詳情'),
),
);
}
@override
void didUpdateWidget(NewsDetailPage oldWidget) {
print('組件狀態改變:didUpdateWidget');
super.didUpdateWidget(oldWidget);
}
@override
void deactivate() {
print('移除時:deactivate');
super.deactivate();
}
@override
void dispose() {
print('移除時:dispose');
super.dispose();
}
//預加載佈局
Widget _loading() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircularProgressIndicator(
strokeWidth: 1.0,
),
Container(
child: Text("正在加載"),
margin: EdgeInsets.only(top: 10.0),
)
],
);
}
}
複製代碼
下面內容來自鹹魚技術團隊.
當ListView中的item滾動出可顯示區域的時候,item會被從樹中remove掉,此item子樹中全部的state都會被dispose,state記錄的數據都會銷燬,item滾動回可顯示區域時,會從新建立全新的state、element、renderobject
使用hot reload功能時,要特別注意state實例是沒有從新建立的,若是該state中存在一下複雜的資源更新須要從新加載才能生效,那麼須要在reassemble()添加處理,否則當你使用hot reload時候可能會出現一些意想不到的結果,例如,要將顯示本地文件的內容到屏幕上,當你開發過程當中,替換了文件中的內容,可是hot reload沒有觸發從新讀取文件內容,頁面顯示仍是原來的舊內容.
didChangeDependencies有兩種狀況會被調用。
建立時候在initState 以後被調用
在依賴的InheritedWidget發生變化的時候會被調用
正常的退出流程中會執行deactivate而後執行dispose。可是也會出現deactivate之後不執行dispose,直接加入樹中的另外一個節點的狀況。
這裏的狀態改變包括兩種可能:1.經過setState內容改變 2.父節點的state狀態改變,致使孩子節點的同步變化。
須要指出的是若是想要知道App的生命週期,那麼須要經過WidgetsBindingObserver的didChangeAppLifecycleState 來獲取。經過該接口能夠獲取是生命週期在AppLifecycleState類中。經常使用狀態包含以下幾個:
名稱 | 狀態 |
---|---|
resumed | 可見並能響應用戶的輸入 |
inactive | 處在並不活動狀態,沒法處理用戶響應 |
paused | 不可見並不能響應用戶的輸入,可是在後臺繼續活動中 |
一個實際場景中的例子:
在不考慮suspending的狀況下:從後臺切入前臺生命週期變化以下: AppLifecycleState.inactive->AppLifecycleState.resumed;
從前臺壓後臺生命週期變化以下: AppLifecycleState.inactive->AppLifecycleState.paused;
class _MyWalletScreenState extends State<MyWalletScreen>
with WidgetsBindingObserver {
@override void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
void didChangeAppLifecycleState(AppLifecycleState state) async {
if (state == AppLifecycleState.resumed) {
getData();
}
}
}
複製代碼
記得註冊和移除監聽。