Flutter路由棧和生命週期解析

Harry_Chen發表於2020-02-08

前言

年前由於業務調整開始接觸學習Flutter,自己重寫了業務線的功能型App。其中遇到不少坑,這次主要總結下路由棧以及生命週期的內容,後續再慢慢補充~

什麼是路由

路由(Routes)是什麼?路由是螢幕或應用程式頁面的抽象。

Flutter 使我們能夠優雅地管理路由主要依賴的是 Navigator(導航器)類。這是一個用於管理一組具有某種進出規則的頁面的 Widget,也就是說用它我們能夠實現各個頁面間有規律的切換。而這裡的規則便是在其內部維護的一個“ 路由棧”。

命名路由

在一般應用中,我們用的最多的還是命名路由,它是將應用中需要訪問的每個頁面命名為不重複的字串,我們便可以通過這個字串來將該頁面例項推進路由。

例如:

new MaterialApp(
  home: new Screen1(),
  routes: <String, WidgetBuilder> {
    '/screen1': (BuildContext context) => new Screen1(),
    '/screen2' : (BuildContext context) => new Screen2(),
    '/screen3' : (BuildContext context) => new Screen3(),
    '/screen4' : (BuildContext context) => new Screen4()
  },
)
複製程式碼

路由棧

Navigator維護了一個堆疊,來儲存路由跳轉的狀態。

screen1跳轉到screen2

Navigator.pushNamed(context, '/screen2')
複製程式碼

Flutter路由棧和生命週期解析

退出路由pop

Navigator.pop()

Flutter路由棧和生命週期解析

切勿用pushNamed替代pop,不然就會出現下面的情況了。

Flutter路由棧和生命週期解析

生命週期

簡單地聊完路由棧,接下來介紹 StatefulWidget一系列的生命週期。

Flutter路由棧和生命週期解析

上圖就是 State 的生命週期圖。

StatefulWidget.createState()

Framework 通過呼叫 StatefulWidget.createState() 來建立一個 State。

initState()

新建立的 State 會和一個 BuildContext 產生關聯,此時認為 State 已經被安裝好了,initState() 函式將會被呼叫。通常,我們可以重寫這個函式,進行初始化操作。

didChangeDependencies()

在 initState() 呼叫結束後,這個函式會被呼叫。事實上,當 State 物件的依賴關係發生變化時,這個函式總會被 Framework 呼叫。

build()

經過以上步驟,系統認為一個 State 已經準備好了,就會呼叫 build() 來構建檢視。我們需要在這個函式中,返回一個 Widget。

deactivate()

當 State 被暫時從檢視樹中移除時,會呼叫這個函式。頁面切換時,也會呼叫它,因為此時 State 在檢視樹中的位置發生了變化,需要先暫時移除後新增。⚠️注意,重寫的時候必須要呼叫 super.deactivate()。

dispose()

當 State 被永久的從檢視樹中移除,Framework 會呼叫該函式。在銷燬前觸發,我們可以在這裡進行最終的資源釋放。在呼叫這個函式之前,總會先呼叫 deactivate()。⚠️注意,重寫的時候必須要呼叫 super.dispose()

didUpdateWidget(covariant T oldWidget)

當 widget 的配置發生變化時,會呼叫這個函式。比如,Hot-reload 的時候就會呼叫這個函式。這個函式呼叫後,會呼叫 build()。

setState()

當我需要更新 State 的檢視時,需要手動呼叫這個函式,它會觸發 build()

一般情況下我們用的最多的是initState,build,deactivate,dispose,setState。接下來也主要講initState以及dispose在路由跳轉過程中的觸發條件。

路由跳轉時的initState,deactivate,dispose

一開始我們是這麼寫的

@override
void initState() {
    super.initState();
    print('------initstate--------');
}

void deactivate() {
    super.deactivate();
    print('-----deactivate----');
}

void dispose() {
    super.dispose();
    print('----dispose--------');
}
複製程式碼

當我們通過pushNamed跳轉到其他路由,發現deactivate觸發了,但是dispose並沒有觸發,並且再次通過pushNamed回到本頁的時候,initstate也並沒有再次執行。

寫多了Vue單頁面應用的同學,應該知道這其實很讓人困擾。我們很多時候需要像Vue一樣,在created的時候初始化資料,在beforeDestroy的時候移除頁面一些監聽事件或者銷燬定時器,防止記憶體洩露。

讓我們回到生命週期說明那裡:

deactivate() 當 State 被暫時從檢視樹中移除時,會呼叫這個函式。

dispose()當 State 被永久的從檢視樹中移除,Framework 會呼叫該函式。

此時是不是恍然大悟!pushNamed只是暫時把檢視移除了,並沒有徹底移除,所以導致了一些奇怪的問題(前提是你跟我一樣,業務上需要控制頁面的初始化和銷燬)

那怎麼辦。此時popAndPushNamed閃亮登場!用它替代pushNamed,你就會發現該觸發的事件都觸發了~

popAndPushNamed將當前路由彈出並跳轉到新的路由,徹底移除舊路由。

還有類似的方法pushReplacementNamed,pushNamedAndRemoveUntil等,就不贅述了。

取消導航Header的返回鍵

AppBar(automaticallyImplyLeading: false)
複製程式碼

WillPopScope取消Android物理返回鍵

這個方法也可以取消iOS預設左右滑動切換路由。

Widget build(BuildContext context) {
    return WillPopScope(
        onWillPop: () async {
          return false;
        },
        child: Scaffold(
            // Your Code
        )
    );
}
複製程式碼

相關文章