日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

Android 多返回棧技術詳解

 

用戶通過系統返回按鈕導航回去的一組頁面,在開發中被稱為返回棧 (back stack)。多返回棧即一堆 "返回棧",對多返回棧的支持是在 Navigation 2.4.0-alpha01 和 Fragment 1.4.0-alpha01 中開始的。本文將為您展開多返回棧的技術詳解。

系統返回按鈕的樂趣

無論您在使用 Android 全新的 手勢導航 還是傳統的導航欄,用戶的 "返回" 操作是 Android 用戶體驗中關鍵的一環,把握好返回功能的設計可以使應用更加貼近整個生態系統。

在最簡單的應用場景中,系統返回按鈕僅僅 finish 您的 Activity。在過去您可能需要覆寫 Activity 的 onBackPressed() 方法來自定義返回操作,而在 2021 年您無需再這樣操作。我們已經在 OnBackPressedDispatcher 中提供了 針對自定義返回導航的 API。實際上這與 FragmentManager 和 NavController 中 已經 添加的 API 相同。

這意味著當您使用 Fragments 或 Navigation 時,它們會通過 OnBackPressedDispatcher 來確保您調用了它們返回棧的 API,系統的返回按鈕會將您推入返回棧的頁面逐層返回。

多返回棧不會改變這個基本邏輯。系統的返回按鈕仍然是一個單向指令 —— "返回"。這對多返回棧 API 的實現機制有深遠影響。

Fragment 中的多返回棧

在 surface 層級,對于 多返回棧的支持 貌似很直接,但其實需要額外解釋一下 "Fragment 返回棧" 到底是什么。FragmentManager 的返回棧其實包含的不是 Fragment,而是由 Fragment 事務組成的。更準確地說,是由那些調用了 addToBackStack(String name) API 的事務組成的。

這就意味著當您調用 commit() 提交了一個調用過 addToBackStack() 方法的 Fragment 事務時,FragmentManager 會執行所有您在事務中所指定的操作 (比如 替換操作),從而將每個 Fragment 轉換為預期的狀態。然后 FragmentManager 會將該事務作為它返回棧的一部分。

當您調用 popBackStack() 方法時 (無論是直接調用,還是通過系統返回鍵以 FragmentManager 內部機制調用),Fragment 返回棧的最上層事務會從棧中彈出 -- 比如新添加的 Fragment 會被移除,隱藏的 Fragment 會顯示。這會使得 FragmentManager 恢復到最初提交 Fragment 事務之前的狀態。

作者注: 這里有一個非常重要的事情需要大家注意,在同一個 FragmentManager 中絕對不應該將含有 addToBackStack() 的事務和不含的事務混在一起: 返回棧的事務無法察覺返回棧之外的 Fragment 事務的修改 —— 當您從堆棧彈出一個非常不確定的元素時,這些事務從下層替換出來的時候會撤銷之前未添加到返回棧的修改。

也就是說 popBackStack() 變成了銷毀操作: 任何已添加的 Fragment 在事務被彈出的時候都會丟失它的狀態。換言之,您會失去視圖的狀態,任何所保存的實例狀態 (Saved Instance State),并且任何綁定到該 Fragment 的 ViewModel 實例都會被清除。這也是該 API 和新的saveBackStack() 方法之間的主要區別。saveBackStack() 可以實現彈出事務所實現的返回效果,此外它還可以確保視圖狀態、已保存的實例狀態,以及 ViewModel 實例能夠在銷毀時被保存。這使得 restoreBackStack() API 后續可以通過已保存的狀態重建這些事務和它們的 Fragment,并且高效 "重現" 已保存的全部細節。太神奇了!

而實現這個目的必須要解決大量技術上的問題。

排除 Fragment 在技術上的障礙

雖然 Fragment 總是會保存 Fragment 的視圖狀態,但是 Fragment 的 onSaveInstanceState() 方法只有在 Activity 的 onSaveInstanceState() 被調用時才會被調用。為了能夠保證調用 saveBackStack() 時 SavedInstanceState 會被保存,我們  需要在 Fragment 生命周期切換 的正確時機注入對 onSaveInstanceState() 的調用。我們不能調用得太早 (您的 Fragment 不應該在 STARTED 狀態下保存狀態),也不能調用得太晚 (您需要在 Fragment 被銷毀之前保存狀態)。

這樣的前提條件就開啟了需要 解決 FragmentManager 轉換到對應狀態的問題,以此來保障有一個地方能夠將 Fragment 轉換為所需狀態,并且處理可重入行為和 Fragment 內部的狀態轉換。

在 Fragment 的重構工作進行了 6 個月,進行了 35 次修改時,發現 Postponed Fragment 功能已經嚴重損壞,這一問題使得被推遲的事務處于一個中間狀態 —— 既沒有被提交也并不是未被提交。之后的 65 個修改和 5 個月的時間里,我們幾乎重寫了 FragmentManager 管理狀態、延遲狀態切換和動畫的內部代碼,具體請參見我們之前的文章《全新的 Fragment: 使用新的狀態管理器》。

Fragment 中值得期待的地方

隨著技術問題的逐步解決,包括更加可靠和更易理解的 FragmentManager,我們新增加了兩個 API: saveBackStack() 和 restoreBackStack()。

如果您不使用這些新增 API,則一切照舊: 單個 FragmentManager 返回棧和之前的功能相同。現有的 addToBackStack() 保持不變 —— 您可以將 name 賦值為 null 或者任意 name。然而,當您使用多返回棧時,name 的作用就非常重要了: 在您調用 saveBackStack()和之后的 restoreBackStack() 方法時,它將作為 Fragment 事務的唯一的 key。

舉個例子,會更容易理解。比如您已經添加了一個初始的 Fragment 到 Activity,然后提交了兩個事務,每個事務中包含一個單獨的 replace 操作:

// 這是用戶看到的初始的 Fragment
fragmentManager.commit {
 setReorderingAllowed(true)
 replace<HomeFragment>(R.id.fragment_container)
}
// 然后,響應用戶操作,我們在返回棧中增加了兩個事務
fragmentManager.commit {
 setReorderingAllowed(true)
 replace<ProfileFragment>(R.id.fragment_container)
 addToBackStack(“profile”)
}
fragmentManager.commit {
 setReorderingAllowed(true)
 replace<EditProfileFragment>(R.id.fragment_container)
 addToBackStack(“edit_profile”)
}

也就是說我們的 FragmentManager 會變成這樣:

Android 多返回棧技術詳解

△ 提交三次之后的 FragmentManager 的狀態

比如說我們希望將 profile 頁換出返回棧,然后切換到通知 Fragment。這就需要調用 saveBackStack() 并且緊跟一個新的事務:

fragmentManager.saveBackStack("profile")
fragmentManager.commit {
 setReorderingAllowed(true)
 replace<NotificationsFragment>(R.id.fragment_container)
 addToBackStack("notifications")
}

現在我們添加 ProfileFragment 的事務和添加 EditProfileFragment 的事務都保存在 "profile" 關鍵字下。這些 Fragment 已經完全將狀態保存,并且 FragmentManager 會隨同事務狀態一起保持它們的狀態。很重要的一點: 這些 Fragment 的實例并不在內存中或者在 FragmentManager 中 —— 存在的僅僅只有狀態 (以及任何以 ViewModel 實例形式存在的非配置狀態)。

Android 多返回棧技術詳解

△ 我們保存 profile 返回棧并且添加一個新的 commit 后的 FragmentManager 狀態

替換回來非常簡單: 我們可以在 "notifications" 事務中同樣調用 saveBackStack() 操作,然后調用 restoreBackStack():

fragmentManager.saveBackStack(“notifications”)
fragmentManager.restoreBackStack(“profile”)

這兩個堆棧項高效地交換了位置:

Android 多返回棧技術詳解

△ 交換堆棧項后的 FragmentManager 狀態

維持一個單獨且活躍的返回棧并且將事務在其中交換,這保證了當返回按鈕被點擊時,FragmentManager和系統的其他部分可以保持一致的響應。實際上,整個邏輯并未改變,同之前一樣,仍然彈出 Fragment 返回棧的最后一個事務。

這些 API 都特意按照最小化設計,盡管它們會產生潛在的影響。這使得開發者可以基于這些接口設計自己的結構,而無需通過任何非常規的方式保存 Fragment 的視圖狀態、已保存的實例狀態、非配置的狀態。

當然了,如果您不希望在這些 API 之上構建您的框架,那么可以使用我們所提供的框架進行開發。

使用 Navigation 將多返回棧適配到任意屏幕類型

Navigation Component 最初 是作為通用運行時組件進行開發的,其中不涉及 View、Fragment、Composable 或者其他屏幕顯示相關類型及您可能會在 Activity 中實現的 "目的地界面"。然而,NavHost 接口 的實現中需要考慮這些內容,通過它添加一個或者多個 Navigator 實例時,這些實例 確實 清楚如何與特定類型的目的地進行交互。

這也就意味著與 Fragment 的交互邏輯全部封裝在了 navigation-fragment 開發庫和它其中的 FragmentNavigator 與 DialogFragmentNavigator 中。類似的,與 Composable 的交互邏輯被封裝在完全獨立的 navigation-compose 開發庫和它的 ComposeNavigator 中。這里的抽象設計意味著如果您希望僅僅通過 Composable 構建您的應用,那么當您使用 Navigation Compose 時無需任何涉及到 Fragment 的依賴。

該級別的分離意味著 Navigation 中有兩個層次來實現多返回棧:

  • 保存獨立的 NavBackStackEntry 實例狀態,這些實例組成了 NavController 返回棧。這是屬于 NavController 的職責。
  • 保存 Navigator 針對每個 NavBackStackEntry 的特定狀態 (比如與 FragmentNavigator 目的地相關聯的 Fragment)。這是屬于 Navigator 的職責。

仍需特別注意那些 尚未 更新的 Navigator,它們無法支持保存自身狀態。底層的 Navigator API 已經整體重寫來支持狀態保存 (您需要覆寫新增的 navigate() 和 popBackStack() API 的重載方法,而不是覆寫之前的版本),即使 Navigator 并未更新,NavController 仍會保存 NavBackStackEntry 的狀態 (在 Jetpack 世界中向后兼容是非常重要的)。

備注: 通過綁定 TestNavigatorState 使其成為一個 mini-NavController 可以實現在新的 Navigator API 上更輕松、獨立地測試您自定義的 Navigator。

如果您僅僅在應用中使用 Navigation,那么 Navigator 這個層面更多的是實現細節,而不是您需要直接與之交互的內容。可以這么說,我們已經完成了將 FragmentNavigator 和 ComposeNavigator 遷移到新的 Navigator API 的工作,使其能夠正確地保存和恢復它們的狀態,在這個層面上您無需再做任何額外工作。

在 Navigation 中啟用多返回棧

如果您正在使用 NavigationUI,它是用于連接您的 NavController 到 Material 視圖組件的一系列專用助手,您會發現對于菜單項、BottomNavigationView (現在叫 NavigationRailView) 和 NavigationView,多返回棧是 默認啟用 的。這就意味著結合 navigation-fragment 和 navigation-ui 使用就可以。

NavigationUI API 是基于 Navigation 的其他公共 API 構建的,確保您可以準確地為自定義組件構建您自己的版本。保證您可以構建所需的自定義組件。啟用保存和恢復返回棧的 API 也不例外,在 Navigation XML 中通過 NavOptions 上的新 API,也就是 navOptions Kotlin DSL,以及 popBackStack() 的重載方法可以幫助您指定 pop 操作保存狀態或者指定 navigate 操作來恢復之前已保存的狀態。

比如,在 Compose 中,任何全局的導航模式 (無論是底部導航欄、導航邊欄、抽屜式導航欄或者任何您能想到的形式) 都可以使用我們在與 底部導航欄集成 所介紹的相同的技術,并且結合 saveState 和 restoreState 屬性一起調用 navigate():

onClick = {
 navController.navigate(screen.route) {
   // 當用戶選擇子項時在返回棧中彈出到導航圖中的起始目的地
   // 來避免太過臃腫的目的地堆棧
   popUpTo(navController.graph.findStartDestination().id) {
     saveState = true
   }

   // 當重復選擇相同項時避免相同目的地的多重拷貝
   launchSingleTop = true
   // 當重復選擇之前已經選擇的項時恢復狀態
   restoreState = true
 }
}

保存狀態,鎖定用戶

對用戶來說,最令人沮喪的事情之一便是丟失之前的狀態。這也是為什么 Fragment 用一整頁來講解 保存與 Fragment 相關的狀態,而且也是我非常樂于更新每個層級來支持多返回棧的原因之一:

  • Fragments (比如完全不使用 Navigation Component): 通過使用新的 FragmentManager API,也就是 saveBackStack 和 restoreBackStack。
  • 核心的 Navigation 運行時: 添加可選的新的 NavOptions 方法用于 restoreState(恢復狀態) 和 saveState (保存狀態) 以及新的 popBackStack() 的重載方法,它同樣可以傳入一個布爾型的 saveState 參數 (默認是 false)。
  • 通過 Fragment 實現 Navigation: FragmentNavigator 現在利用新的 NavigatorAPI,通過使用 Navigation 運行時 API 將 Navigation 運行時 API 轉換為 Fragment API。
  • NavigationUI: 每當它們彈出返回棧時,onNavDestinationSelected()、NavigationBarView.setupWithNavController() 和 NavigationView.setupWithNavController() 現在默認使用 restoreState 和 saveState 這兩個新的 NavOption。也就意味著 當升級到 Navigation 2.4.0-alpha01 或者更高版本后,任何使用 NavigationUI API 的應用無需修改代碼即可實現多返回棧

如果您希望了解 更多使用該 API 的示例,請參考 NavigationAdvancedSample (它是最新更新的,且不包含任何用于支持多返回棧的 NavigationExtensions 代碼)。

對于 Navigation Compose 的示例,請參考 Tivi。

分享到:
標簽:Android
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定