以下文章来源于Android 开发者 ,作者Android
Android 官方账号。汇集 Android, Kotlin, Jetpack Compose, Jetpack, Android Studio 等开发技术,以及 Google Play 平台出海及政策相关内容。为您带来更及时的资讯动态。
Navigation 2.4.0-alpha01
https://developer.android.google.cn/jetpack/androidx/releases/navigation#2.4.0-alpha01
Fragment 1.4.0-alpha01
https://developer.android.google.cn/jetpack/androidx/releases/fragment#1.4.0-alpha01
系统返回按钮的乐趣
无论您在使用 Android 全新的手势导航还是传统的导航栏,用户的 "返回" 操作是 Android 用户体验中关键的一环,把握好返回功能的设计可以使应用更加贴近整个生态系统。
在最简单的应用场景中,系统返回按钮仅仅 finish 您的 Activity。在过去您可能需要覆写 Activity 的 onBackPressed() 方法来自定义返回操作,而在 2021 年您无需再这样操作。我们已经在 OnBackPressedDispatcher 中提供了针对自定义返回导航的 API。实际上这与 FragmentManager 和 NavController 中已经添加的 API 相同。
OnBackPressedDispatcher
https://developer.android.google.cn/reference/androidx/activity/OnBackPressedDispatcher
针对自定义返回导航的 API
https://developer.android.google.cn/guide/navigation/navigation-custom-back
FragmentManager
https://developer.android.google.cn/reference/androidx/fragment/app/FragmentManager
NavController
https://developer.android.google.cn/reference/kotlin/androidx/navigation/NavController
这意味着当您使用 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 事务的修改 —— 当您从堆栈弹出一个非常不确定的元素时,这些事务从下层替换出来的时候会撤销之前未添加到返回栈的修改。
排除 Fragment 在技术上的障碍
虽然 Fragment 总是会保存 Fragment 的视图状态,但是 Fragment 的 onSaveInstanceState() 方法只有在 Activity 的 onSaveInstanceState() 被调用时才会被调用。为了能够保证调用 saveBackStack() 时 SavedInstanceState 会被保存,我们还需要在 Fragment 生命周期切换的正确时机注入对 onSaveInstanceState() 的调用。我们不能调用得太早 (您的 Fragment 不应该在 STARTED 状态下保存状态),也不能调用得太晚 (您需要在 Fragment 被销毁之前保存状态)。
这样的前提条件就开启了需要解决 FragmentManager 转换到对应状态的问题,以此来保障有一个地方能够将 Fragment 转换为所需状态,并且处理可重入行为和 Fragment 内部的状态转换。
解决 FragmentManager 转换到对应状态的问题
https://issuetracker.google.com/139536619
在 Fragment 的重构工作进行了 6 个月,进行了 35 次修改时,发现 Postponed Fragment 功能已经严重损坏,这一问题使得被推迟的事务处于一个中间状态 —— 既没有被提交也并不是未被提交。之后的 65 个修改和 5 个月的时间里,我们几乎重写了 FragmentManager 管理状态、延迟状态切换和动画的内部代码,具体请参见我们之前的文章《全新的 Fragment: 使用新的状态管理器》。
Postponed Fragment 功能已经严重损坏
https://issuetracker.google.com/147749580
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 会变成这样:
△ 提交三次之后的 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 实例形式存在的非配置状态)。
△ 我们保存 profile 返回栈并且添加一个新的 commit 后的 FragmentManager 状态
替换回来非常简单: 我们可以在 "notifications" 事务中同样调用 saveBackStack() 操作,然后调用 restoreBackStack():
fragmentManager.saveBackStack(“notifications”)
fragmentManager.restoreBackStack(“profile”)
这两个堆栈项高效地交换了位置:
△ 交换堆栈项后的 FragmentManager 状态
维持一个单独且活跃的返回栈并且将事务在其中交换,这保证了当返回按钮被点击时,FragmentManager 和系统的其他部分可以保持一致的响应。实际上,整个逻辑并未改变,同之前一样,仍然弹出 Fragment 返回栈的最后一个事务。
这些 API 都特意按照最小化设计,尽管它们会产生潜在的影响。这使得开发者可以基于这些接口设计自己的结构,而无需通过任何非常规的方式保存 Fragment 的视图状态、已保存的实例状态、非配置的状态。
当然了,如果您不希望在这些 API 之上构建您的框架,那么可以使用我们所提供的框架进行开发。
使用 Navigation 将多返回栈适配到任意屏幕类型
Navigation Component
https://developer.android.google.cn/guide/navigation/
NavHost 接口
https://developer.android.google.cn/reference/kotlin/androidx/navigation/NavHost
Navigator
https://developer.android.google.cn/reference/kotlin/androidx/navigation/Navigator
该级别的分离意味着 Navigation 中有两个层次来实现多返回栈:
保存独立的 NavBackStackEntry 实例状态,这些实例组成了 NavController 返回栈。这是属于 NavController 的职责。
保存 Navigator 针对每个 NavBackStackEntry 的特定状态 (比如与 FragmentNavigator 目的地相关联的 Fragment)。这是属于 Navigator 的职责。
NavBackStackEntry
https://developer.android.google.cn/reference/kotlin/androidx/navigation/NavBackStackEntry
备注: 通过绑定 TestNavigatorState 使其成为一个 mini-NavController 可以实现在新的 Navigator API 上更轻松、独立地测试您自定义的 Navigator。
在 Navigation 中启用多返回栈
NavigationUI
https://developer.android.google.cn/guide/navigation/navigation-ui
onClick = {
navController.navigate(screen.route) {
// 当用户选择子项时在返回栈中弹出到导航图中的起始目的地
// 来避免太过臃肿的目的地堆栈
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
// 当重复选择相同项时避免相同目的地的多重拷贝
launchSingleTop = true
// 当重复选择之前已经选择的项时恢复状态
restoreState = true
}
}
与底部导航栏集成
https://developer.android.google.cn/jetpack/compose/navigation#bottom-nav
保存状态,锁定用户
如果您希望了解更多使用该 API 的示例,请参考 NavigationAdvancedSample (它是最新更新的,且不包含任何用于支持多返回栈的 NavigationExtensions 代码):
如果您遇到任何问题,请使用官方的问题追踪页面提交关于 Fragment 或者 Navigation 的 bug,我们会尽快处理。
Fragment
https://issuetracker.google.com/issues/new?component=460964
Navigation
https://issuetracker.google.com/issues/new?component=409828
推荐阅读
点击屏末 | 阅读原文 | 即刻了解更多支持多个返回堆栈相关信息