Android 多返回栈技术详解

发表于 3年以前  | 总阅读数:2916 次

用户通过系统返回按钮导航回去的一组页面,在开发中被称为返回栈 (back stack)。多返回栈即一堆 "返回栈",对多返回栈的支持是在 Navigation 2.4.0-alpha01 和 Fragment 1.4.0-alpha01 中开始的。本文将为您展开多返回栈的技术详解。

  • 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 的事务组成的。

  • 多返回栈的支持 https://developer.android.google.cn/guide/fragments/fragmentmanager#multiple-back-stacks
  • addToBackStack(String name) https://developer.android.google.cn/reference/androidx/fragment/app/FragmentTransaction#addToBackStack(java.lang.String)

这就意味着当您调用 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 被销毁之前保存状态)。

  • Fragment 的视图状态 https://developer.android.google.cn/guide/fragments/saving-state#view
  • Fragment 生命周期切换 https://developer.android.google.cn/guide/fragments/lifecycle#states

这样的前提条件就开启了需要解决 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 最初是作为通用运行时组件进行开发的,其中不涉及 View、Fragment、Composable 或者其他屏幕显示相关类型及您可能会在 Activity 中实现的 "目的地界面"。然而,NavHost 接口的实现中需要考虑这些内容,通过它添加一个或者多个 Navigator 实例时,这些实例确实清楚如何与特定类型的目的地进行交互。

  • 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

这也就意味着与 Fragment 的交互逻辑全部封装在了 navigation-fragment 开发库和它其中的 FragmentNavigator 与 DialogFragmentNavigator 中。类似的,与 Composable 的交互逻辑被封装在完全独立的 navigation-compose 开发库和它的 ComposeNavigator 中。这里的抽象设计意味着如果您希望仅仅通过 Composable 构建您的应用,那么当您使用 Navigation Compose 时无需任何涉及到 Fragment 的依赖。

该级别的分离意味着 Navigation 中有两个层次来实现多返回栈:

  • 保存独立的 NavBackStackEntry 实例状态,这些实例组成了 NavController 返回栈。这是属于 NavController 的职责。

  • 保存 Navigator 针对每个 NavBackStackEntry 的特定状态 (比如与 FragmentNavigator 目的地相关联的 Fragment)。这是属于 Navigator 的职责。

  • NavBackStackEntry https://developer.android.google.cn/reference/kotlin/androidx/navigation/NavBackStackEntry

仍需特别注意那些尚未更新的 Navigator,它们无法支持保存自身状态。底层的 Navigator API 已经整体重写来支持状态保存 (您需要覆写新增的 navigate() 和 popBackStack() API 的重载方法,而不是覆写之前的版本),即使 Navigator 并未更新,NavController 仍会保存 NavBackStackEntry 的状态 (在 Jetpack 世界中向后兼容是非常重要的)。

备注: 通过绑定 TestNavigatorState 使其成为一个 mini-NavController 可以实现在新的 Navigator API 上更轻松、独立地测试您自定义的 Navigator。

  • TestNavigatorState https://developer.android.google.cn/reference/kotlin/androidx/navigation/testing/TestNavigatorState

如果您仅仅在应用中使用 Navigation,那么 Navigator 这个层面更多的是实现细节,而不是您需要直接与之交互的内容。可以这么说,我们已经完成了将 FragmentNavigator 和 ComposeNavigator 迁移到新的 Navigator API 的工作,使其能够正确地保存和恢复它们的状态,在这个层面上您无需再做任何额外工作。

在 Navigation 中启用多返回栈

如果您正在使用 NavigationUI,它是用于连接您的 NavController 到 Material 视图组件的一系列专用助手,您会发现对于菜单项、BottomNavigationView (现在叫 NavigationRailView) 和 NavigationView,多返回栈是默认启用的。这就意味着结合 navigation-fragment 和 navigation-ui 使用就可以。

  • NavigationUI https://developer.android.google.cn/guide/navigation/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
 }
}
  • 与底部导航栏集成 https://developer.android.google.cn/jetpack/compose/navigation#bottom-nav

保存状态,锁定用户

对用户来说,最令人沮丧的事情之一便是丢失之前的状态。这也是为什么 Fragment 用一整页来讲解保存与 Fragment 相关的状态,而且也是我非常乐于更新每个层级来支持多返回栈的原因之一:

  • Fragments (比如完全不使用 Navigation Component): 通过使用新的 FragmentManager API,也就是 saveBackStack 和 restoreBackStack。

  • 核心的 Navigation 运行时: 添加可选的新的 NavOptions 方法用于 restoreState (恢复状态) 和 saveState (保存状态) 以及新的 popBackStack() 的重载方法,它同样可以传入一个布尔型的 saveState 参数 (默认是 false)。

  • 通过 Fragment 实现 Navigation: FragmentNavigator 现在利用新的 Navigator API,通过使用 Navigation 运行时 API 将 Navigation 运行时 API 转换为 Fragment API。

  • NavigationUI: 每当它们弹出返回栈时,onNavDestinationSelected()、NavigationBarView.setupWithNavController() 和 NavigationView.setupWithNavController() 现在默认使用 restoreState 和 saveState 这两个新的 NavOption。也就意味着当升级到 Navigation 2.4.0-alpha01 或者更高版本后,任何使用 NavigationUI API 的应用无需修改代码即可实现多返回栈

  • 保存与 Fragment 相关的状态 https://developer.android.google.cn/guide/fragments/saving-state

如果您希望了解更多使用该 API 的示例,请参考 NavigationAdvancedSample (它是最新更新的,且不包含任何用于支持多返回栈的 NavigationExtensions 代码):

https://github.com/android/architecture-components-samples/tree/master/NavigationAdvancedSample 对于 Navigation Compose 的示例,请参考 Tivi: https://github.com/chrisbanes/tivi

如果您遇到任何问题,请使用官方的问题追踪页面提交关于 Fragment 或者 Navigation 的 bug,我们会尽快处理。

  • Fragment https://issuetracker.google.com/issues/new?component=460964
  • Navigation https://issuetracker.google.com/issues/new?component=409828

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/Xy-qFRQ1ZFJPc3lB58g7Uw

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
简化Android的UI开发 5年以前  |  521240次阅读
Android 深色模式适配原理分析 4年以前  |  29632次阅读
Android阴影实现的几种方案 2年以前  |  12220次阅读
Android 样式系统 | 主题背景覆盖 4年以前  |  10293次阅读
 目录