【譯】Fragment 的重大重構 —— 介紹 Fragment 新的狀態管理器

原文:Fragments: Rebuilding the Internals. Introducing: the new state manager
做者:Ian Lake
譯者:Flywith24java

多年以來,Fragment 要比大多數 Android API 更新得更多。它們最初是 Android platform 的一部分,後來成爲 Android Support Library 的一部分,如今以 AndroidX Fragments 的形式成爲了 Jetpack 組件的一部分。android

注意:您毫不應該使用 Android framework 版本的 Fragment。不只由於它已經在 Android 10 中徹底棄用了,還由於它已經長時間沒有被修復 bug,並且保證不一樣設備和 API 級別之間的一致性。git

雖然 Architecture Components 接管了不少過去須要使用 Fragment 的場景(例如 使用 LifecycleObserver 處理生命週期的回調,或者使用 ViewModel 來保留狀態),若是您使用 Fragment 來處理這些場景,則須要經過 FragmentManager 來進行 add / remove 操做來與之交互。markdown

隨着 Fragment 1.3.0-alpha08 的發佈,FragmentManager 內部的重大重構已經完成。該版本使用 更小的,可測試的,可維護的(內部)類替換了許多 FragmentManager 中的邏輯。其核心類是 FragmentStateManager架構

注意:我將在這篇文章中討論 FragmentManager 的內部原理。若是在使用 1.3.0-alpha08 遇到任何問題,請儘快提交 file issues 協助咱們修復。app

新的 state manager 的職責:oop

  • 經過 Fragment 的生命週期方法來移動它們
  • 運行轉場動畫
  • 處理延遲事務

咱們完全回顧了這些系統過去的工做方式,發現 它們須要被從頭重寫 ,因而咱們重寫了它們。它們比以往的任什麼時候候都更好,咱們可以關閉至少 10 個長期存在的相關 issues,而且這個內部重構爲單 FragmentManager 支持 多返回棧 掃清了道路(譯者注:Bottom Navigation 管理平級界面的問題),並簡化了 Fragment 的生命週期。源碼分析

FragmentManager moveToState()

每一個 FragmentManager 都與一個 host 關聯,對於大多數 fragment,host 爲 FragmentActivity(使用 FragmentControllerFragmentHostCallback 能夠自定義 host,但不在本文的討論範圍)。當 activity 轉移到 CREATEDSTARTED,以及RESUMED, FragmentManager 派發這些更改到它的 Fragments。這是 moveToState() 的職責。(譯者注:源碼分析參見 【背上Jetpack】從源碼角度看 Fragment 生命週期)。post

固然,它沒有那麼簡單。有不少條件邏輯來肯定 fragment 該處於什麼狀態—— activity 的生命週期狀態(若是是嵌套 Fragment 則是其 parent fragment 的生命週期狀態)只是第一部分,它被稱爲 fragment 可以處於的 max state。最大狀態保證了 activity ,fragment,以及它們的 child fragment 可以正確地嵌套。測試

所以 簡化 moveToState() 的首要任務是將全部邏輯抽離到一個位置。因而 FragmentStateManager 誕生了。每一個 fragment 實例與一個 FragmentStateManager 綁定。經過引入這個內部類,咱們可以從 FragmentManager 中抽取與 fragment 交互的大量代碼(例如調用 fragment 的 onCreateView 方法以及其餘生命週期方法)。

該拆分還使咱們可以編寫一個方法,該方法將使用全部向後兼容的所需邏輯來肯定 fragment 實際應處於的狀態,並將其集中在一個位置:computeExpectedState()。該方法追蹤當前的全部狀態而且肯定 fragment 應處於什麼狀態。98% 的時間,他們與 host / parent fragment 處於同一狀態,可是剩下的 2% 與那些創建在 fragment 上的 app 有很大不一樣。

可是有一種咱們沒法肯定正確狀態的狀況:postponed fragments。

Postponed fragments

不管是好是壞,Fragment 都繼承了許多與 Activity 相同的命名的 API。這種繼承的一部分是圍繞轉換以及推遲轉場直到目標準備好的能力。這對共享元素轉場很是重要,與此同時還要確保在轉場的同時不會產生更密集的數據加載(譯者注:爲了轉場時先完成動畫,再完成大數據的渲染)。

延遲 fragment 擁有兩個重要的特性:

  1. 它的 view 已被建立,但不可見
  2. 它的生命週期上限爲 STARTED

當您調用 startPostponedEnterTransition() 後,fragment 的轉場便會執行,view 將變得可見,而且 fragment 將會移動到 RESUMED。實際上,這正是新的 state manager 作的,過去的 fragment 不是這樣工做的,詳情參考 Postponed Fragments leave the FragmentsFragmentManager in an inconsistent state 這兩個bug。

當 fragment 被使用 postponeEnterTransition() 推遲了,預期的行爲是:fragment 被添加到的 container 不會運行任何進入動畫或者以前排隊的退出動畫(例如 replace 操做)直到 Fragment 調用 startPostponedEnterTransition()。同時當 fragment 的 container 被延遲時,fragment 不會達到 RESUMED 狀態。

然而,彷佛 FragmentManager 沒有執行上述操做,反而將 Fragment 和整個 FragmentManager 轉移至一個怪異的,不一致的狀態。

也就是說,任何與 postponed Fragment 的 container 相關的 FragmentTransaction 被「回滾」(如返回上一 fragment),但實際上,這些 fragment 沒有移至其正確的狀態。

這致使了一些列的問題;

實際上解決這些問題中的任何一個都意味着須要一個系統替換 postponed fragment 使用的整個回滾過程,該系統保證 FragmentManager 的一致性,最新狀態,同時保留 postponed fragment 的主要特性。

在 container 層工做

FragmentManager 具備 container 這個不錯的屬性(讀起來:很方便,但做爲維護者卻不那麼有趣),在該屬性中,您能夠爲要放置 Fragment 傳入任何 container id 。甚至在一個 FragmentTransaction 中,您能夠 add 一個 fragment 到一個 container,從一個不一樣的 container remove 另外一個,replace 第三個container 最頂端的 fragment,等等。

fragment 動畫進/出會產生接觸,這僅發生在 container 層。

Fragment 支持一些列的動畫系統:

  • 老舊破爛的 framework Animation API
  • framework Animator API
  • framework Transition API(僅支持 21+,一樣很爛)
  • AndroidX Transition API

衆所周知,命名是計算機科學中最難的問題之一,所以當咱們去構建一個能夠控制全部這些 API 的類時,花了一些時間才決定使用 SpecialEffectsController(該類不是 public API,所以命名可能更改)。該類存在於 container 層,而且協調進入和退出 fragment 相關的全部特效("special effects") 。

SpecialEffectsController 是 container 中真實狀況的 惟一信源。這意味着若是最頂部被 add 的 fragment 被推遲了,整個 container 也將被推遲。再也不須要 FragmentManager 層的邏輯,也不須要 transaction 的任何回滾(如咱們前文提到的,它將影響多個 container)。所以, FragmentManager 處於正確的狀態,咱們仍能夠得到被延遲 fragment 的全部屬性。

這個 API 還容許咱們將 fragment 的全部瘋狂的特效 API 集中到一個 DefaultSpecialEffectsController 中,該 controller 負責運行 transition,animation,以及 animator。再次過去分散在 FragmentManager 中的邏輯移動到了一個地方。

因此 'new state manager' 是個啥

它意味着要替代這種架構:

舊的 state manager:全部邏輯都在 FragmentManager

看起來更像這樣:

新的 state manager:FragmentManager 與各個 FragmentStateManager實例進行通訊,這些實例經過 SpecialEffectsController 與 container 中的其它 fragment 進行協調。

經過拆分 FragmentManager 的內部結構,每一層的邏輯都獲得了極大簡化:

  • FragmentManager 僅具備適用於全部 fragment 的狀態
  • FragmentStateManager 在 fragment 層管理狀態
  • SpecialEffectsController 在 container 層 管理狀態

職責的分離使咱們的測試套件擴大了近 30%,涵蓋了幾乎沒法單獨測試的更多場景。

我應該看到行爲變化嗎?

不。事實上,咱們對新舊狀態管理者都進行了很大一部分的 fragment 測試,專門用於確保咱們有一套強大的迴歸測試。

可是,若是您依賴於不一致的狀態,則能夠將 FragmentManager 放入延遲的 fragment 中,而後,是的,您會發現實際上已經得到了正確的狀態。 在 發行說明 中,您會找到與新狀態管理器相關的錯誤修復列表,所以請仔細閱讀以確保您的問題不是由您本身的解決方法引發的,而該解決方法是您能夠如今刪除的舊行爲 。

與 Fragment 1.2.0 中的 onDestroyView 計時更改 相似,新的狀態管理器將使 fragment 保持在 STARTED 狀態,直到其 transitions/animations/animators/special effects 所有完成爲止,從而使全部 fragment 保持一致,不管它們是不是直接 postponed 或因爲同一 container 中的其它 fragment 引發的 postponed 。

若是我真的看到了行爲變化?

新的狀態管理器會默認開啓。若是你在你的 app 中看到了與以前不一樣的行爲,首先使用如下新的實驗性的 API 來排查是不是因爲新的狀態管理器致使的。

當你更新了 Fragment 1.3.0-alpha08 後,新的狀態管理器會默認開啓。若是你在你的 app 中看到了與以前不一樣的行爲,首先使用如下新的實驗性的 API 來排查是不是因爲新的狀態管理器致使的。

我國啓新的狀態管理器。若是您發現應用程序和過去存在差別,首先可使用新的實驗性的 API 來排查是否與新的狀態管理器相關:

FragmentManager.enableNewStateManager(false)
複製代碼

該 API 可讓您重溫舊世界,讓您驗證所看到的的任何與以前不一致的改變是否因爲新的狀態管理器致使。若是確認是因爲新的狀態管理器致使,您能夠構建一個示例項目重現您遇到的問題,並 在此提 Issue

注意:FragmentManager.enableNewStateManager() API實驗性的

這意味着它不被視爲 Fragment 穩定API 的一部分,能夠隨時刪除。 刪除全部舊代碼能夠節省大量代碼,可是鑑於正確處理代碼的重要性,咱們可能要等到 Fragment 1.3.0 的穩定版本發佈後才能刪除 API,好比考慮在 fragment 1.3.1 發佈時移除。

在 11 個月內進行了 100 屢次我的更改,這絕對是一段時間內 Fragment 的最大內部更改,它使咱們創建了更加可維護,可持續和可理解的代碼。 這意味着跨 Fragment 的行爲更加一致,而且它成爲您在構建應用程序時能夠依靠的堅實基礎。 咱們很是感謝您繼續提出問題並 提供反饋

感謝 Jeremy Woods,Chet Haase,和 Nick Butcher。

譯文完。

譯者總結

Fragments: Past, Present, and Future (Android Dev Summit '19) 演講中 Ian Lake 闡述了 fragment 的將來。掘金官方文章在這

  • Fragment 的通訊問題
  • 多返回棧
  • 簡化 Fragment 生命週期

其中 棄用 targetFragment API 使用新的 FragmentResult API 已經發布:詳情移步【Jetpack更新之Fragment】1.3.0-alpha04 來襲,Fragment 間通訊的新姿式

Fragment 的生命週期正在被簡化,例如 onActivityCreated 被棄用了,詳情移步【Jetpack更新之Fragment】終於動手了,onActivityCreated 被棄用。不過距離合並 Fragment 自身與其內部 View 的生命週期還有很長一段路(不知官方是否會放棄)。

多返回棧一直被本文解決的問題阻塞,文中所說的過去 11 個月即是去年 9 月 Android Dev Summit '19 到如今。而下面的 issue 是2018 年 5 月建立的。

好在多返回棧的絆腳石已經解決,不過這裏咱們也能夠看到 SDK 級別代碼難以維護後的優化成本,文中重寫了 fragment 狀態管理邏輯,即便作了大量測試,仍要保留舊版邏輯並留出切換的入口。

查看 Fragment 代碼變化咱們能很明顯的認識到「職能單一」重要性,這對構建可維護的,可理解的,可測試的,健壯的代碼十分重要。

關於 fragment 的其餘內容,可參考

相關文章
相關標籤/搜索