Google官方梳理,Android多返回栈技术详解
用户通过系统返回按钮导航回去的一组页面,在开发中被称为返回栈(backstack)。多返回栈即一堆返回栈,对多返回栈的支持是在Navigation2。4。0alpha01和Fragment1。4。0alpha01中开始的。本文将为你展开多返回栈的技术详解。
系统返回按钮的乐趣
无论你在使用Android全新的手势导航还是传统的导航栏,用户的返回操作是Android用户体验中关键的一环,把握好返回功能的设计可以使应用更加贴近整个生态系统。
在最简单的应用场景中,系统返回按钮仅仅finish你的Activity。在过去你可能需要覆写Activity的onBackPressed()方法来自定义返回操作,而在2021年你无需再这样操作。我们已经在OnBackPressedDispatcher中提供了针对自定义返回导航的API。实际上这与FragmentManager和NavController中已经添加的API相同。
这意味着当你使用Fragments或Navigation时,它们会通过来确保你调用了它们返回栈的API,系统的返回按钮会将你推入返回栈的页面逐层返回。
多返回栈不会改变这个基本逻辑。系统的返回按钮仍然是一个单向指令返回。这对多返回栈API的实现机制有深远影响。
Fragment中的多返回栈
在surface层级,对于多返回栈的支持貌似很直接,但其实需要额外解释一下Fragment返回栈到底是什么。FragmentManager的返回栈其实包含的不是Fragment,而是由Fragment事务组成的。更准确地说,是由那些调用了addToBackStack(Stringname)API的事务组成的。
这就意味着当你调用commit()提交了一个调用过addToBackStack()方法的Fragment事务时,FragmentManager会执行所有你在事务中所指定的操作(比如替换操作),从而将每个Fragment转换为预期的状态。然后会将该事务作为它返回栈的一部分。
当你调用popBackStack()方法时(无论是直接调用,还是通过系统返回键以内部机制调用),Fragment返回栈的最上层事务会从栈中弹出比如新添加的Fragment会被移除,隐藏的Fragment会显示。这会使得恢复到最初提交Fragment事务之前的状态。
也就是说变成了销毁操作:任何已添加的Fragment在事务被弹出的时候都会丢失它的状态。换言之,你会失去视图的状态,任何所保存的实例状态(SavedInstanceState),并且任何绑定到该Fragment的ViewModel实例都会被清除。这也是该API和新的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次修改时,发现PostponedFragment功能已经严重损坏,这一问题使得被推迟的事务处于一个中间状态既没有被提交也并不是未被提交。之后的65个修改和5个月的时间里,我们几乎重写了管理状态、延迟状态切换和动画的内部代码,具体请参见我们之前的文章《全新的Fragment:使用新的状态管理器》。
Fragment中值得期待的地方
随着技术问题的逐步解决,包括更加可靠和更易理解的,我们新增加了两个API:和。
如果你不使用这些新增API,则一切照旧:单个返回栈和之前的功能相同。现有的保持不变你可以将name赋值为null或者任意。然而,当你使用多返回栈时,的作用就非常重要了:在你调用和之后的方法时,它将作为Fragment事务的唯一的key。
举个例子,会更容易理解。比如你已经添加了一个初始的Fragment到Activity,然后提交了两个事务,每个事务中包含一个单独的replace操作:这是用户看到的初始的FragmentfragmentManager。commit{setReorderingAllowed(true)replaceHomeFragment(R。id。fragmentcontainer)}然后,响应用户操作,我们在返回栈中增加了两个事务fragmentManager。commit{setReorderingAllowed(true)replaceProfileFragment(R。id。)addToBackStack(profile)}fragmentManager。commit{setReorderingAllowed(true)replaceEditProfileFragment(R。id。)addToBackStack(editprofile)}
也就是说我们的FragmentManager会变成这样:
提交三次之后的FragmentManager的状态
比如说我们希望将profile页换出返回栈,然后切换到通知Fragment。这就需要调用并且紧跟一个新的事务:fragmentManager。saveBackStack(profile)fragmentManager。commit{setReorderingAllowed(true)replaceNotificationsFragment(R。id。)addToBackStack(notifications)}
现在我们添加ProfileFragment的事务和添加EditProfileFragment的事务都保存在profile关键字下。这些Fragment已经完全将状态保存,并且会随同事务状态一起保持它们的状态。很重要的一点:这些Fragment的实例并不在内存中或者在中存在的仅仅只有状态(以及任何以ViewModel实例形式存在的非配置状态)。
我们保存profile返回栈并且添加一个新的commit后的FragmentManager状态
替换回来非常简单:我们可以在事务中同样调用saveBackStack()操作,然后调用:fragmentManager。saveBackStack(notifications)fragmentManager。restoreBackStack(profile)
这两个堆栈项高效地交换了位置:
交换堆栈项后的FragmentManager状态
维持一个单独且活跃的返回栈并且将事务在其中交换,这保证了当返回按钮被点击时,和系统的其他部分可以保持一致的响应。实际上,整个逻辑并未改变,同之前一样,仍然弹出Fragment返回栈的最后一个事务。
这些API都特意按照最小化设计,尽管它们会产生潜在的影响。这使得开发者可以基于这些接口设计自己的结构,而无需通过任何非常规的方式保存Fragment的视图状态、已保存的实例状态、非配置的状态。
当然了,如果你不希望在这些API之上构建你的框架,那么可以使用我们所提供的框架进行开发。
使用Navigation将多返回栈适配到任意屏幕类型
NavigationComponent最初是作为通用运行时组件进行开发的,其中不涉及View、Fragment、Composable或者其他屏幕显示相关类型及你可能会在Activity中实现的目的地界面。然而,NavHost接口的实现中需要考虑这些内容,通过它添加一个或者多个Navigator实例时,这些实例确实清楚如何与特定类型的目的地进行交互。
这也就意味着与Fragment的交互逻辑全部封装在了navigationfragment开发库和它其中的FragmentNavigator与DialogFragmentNavigator中。类似的,与Composable的交互逻辑被封装在完全独立的navigationcompose开发库和它的ComposeNavigator中。这里的抽象设计意味着如果你希望仅仅通过Composable构建你的应用,那么当你使用NavigationCompose时无需任何涉及到Fragment的依赖。
该级别的分离意味着Navigation中有两个层次来实现多返回栈:保存独立的NavBackStackEntry实例状态,这些实例组成了NavController返回栈。这是属于的职责。保存Navigator针对每个的特定状态(比如与目的地相关联的Fragment)。这是属于Navigator的职责。
仍需特别注意那些尚未更新的,它们无法支持保存自身状态。底层的API已经整体重写来支持状态保存(你需要覆写新增的navigate()和API的重载方法,而不是覆写之前的版本),即使并未更新,仍会保存的状态(在Jetpack世界中向后兼容是非常重要的)。
备注:通过绑定TestNavigatorState使其成为一个miniNavController可以实现在新的API上更轻松、独立地测试你自定义的。
如果你仅仅在应用中使用Navigation,那么这个层面更多的是实现细节,而不是你需要直接与之交互的内容。可以这么说,我们已经完成了将和迁移到新的NavigatorAPI的工作,使其能够正确地保存和恢复它们的状态,在这个层面上你无需再做任何额外工作。
在Navigation中启用多返回栈
如果你正在使用NavigationUI,它是用于连接你的到Material视图组件的一系列专用助手,你会发现对于菜单项、BottomNavigationView(现在叫NavigationRailView)和NavigationView,多返回栈是默认启用的。这就意味着结合和navigationui使用就可以。
NavigationUIAPI是基于Navigation的其他公共API构建的,确保你可以准确地为自定义组件构建你自己的版本。保证你可以构建所需的自定义组件。启用保存和恢复返回栈的API也不例外,在NavigationXML中通过NavOptions上的新API,也就是navOptionsKotlinDSL,以及的重载方法可以帮助你指定pop操作保存状态或者指定navigate操作来恢复之前已保存的状态。
比如,在Compose中,任何全局的导航模式(无论是底部导航栏、导航边栏、抽屉式导航栏或者任何你能想到的形式)都可以使用我们在与底部导航栏集成所介绍的相同的技术,并且结合saveState和restoreState属性一起调用:onClick{navController。navigate(screen。route){当用户选择子项时在返回栈中弹出到导航图中的起始目的地来避免太过臃肿的目的地堆栈popUpTo(navController。graph。findStartDestination()。id){saveStatetrue}当重复选择相同项时避免相同目的地的多重拷贝launchSingleToptrue当重复选择之前已经选择的项时恢复状态restoreStatetrue}}
保存状态,锁定用户
对用户来说,最令人沮丧的事情之一便是丢失之前的状态。这也是为什么Fragment用一整页来讲解保存与Fragment相关的状态,而且也是我非常乐于更新每个层级来支持多返回栈的原因之一:Fragments(比如完全不使用NavigationComponent):通过使用新的API,也就是saveBackStack和restoreBackStack。核心的Navigation运行时:添加可选的新的方法用于(恢复状态)和(保存状态)以及新的popBackStack()的重载方法,它同样可以传入一个布尔型的参数(默认是false)。通过Fragment实现Navigation:现在利用新的NavigatorAPI,通过使用Navigation运行时API将Navigation运行时API转换为FragmentAPI。:每当它们弹出返回栈时,onNavDestinationSelected()、NavigationBarView。setupWithNavController()和NavigationView。setupWithNavController()现在默认使用restoreState和saveState这两个新的NavOption。也就意味着当升级到Navigation2。4。0alpha01或者更高版本后,任何使用NavigationUIAPI的应用无需修改代码即可实现多返回栈。
如果你希望了解更多使用该API的示例,请参考NavigationAdvancedSample(它是最新更新的,且不包含任何用于支持多返回栈的NavigationExtensions代码)。
对于NavigationCompose的示例,请参考Tivi。
如果你遇到任何问题,请使用官方的问题追踪页面提交关于Fragment或者Navigation的bug,我们会尽快处理。
本文由码农老K原创,欢迎关注,我们一起长知识!
深跌背后,阿里费力解释远大宏图(会议纪要)财报深度解析:《属于阿里的的移动互联网时代已然结束?》管理层陈述:在过去的一个季度里,阿里巴巴继续坚定地投资于三大战略支柱:内需、全球化、云计算数据智能。活跃……
联通或者移动的内部人员能不能随意查询号码信息,比如机主姓名,首先,先要明确的说,我们确实可以根据手机号查询到机主信息,比如姓名,余额等。但是通话记录等敏感信息现在都需要进行再次认证才可以被我们查询到,比如通过刷机主身份证认定和拍照,或者……
荣耀50被曝光!外观撞脸华为P50,骁龙888处理器,价格感在智能手机领域,华为有两款机型深受消费者喜爱,这就是华为的Mate系列和P系列,Mate40系列的发布,不仅实现了全球5nmSOC的首发,安兔兔跑分曾轻松摘得桂冠,同时还有徕卡……
GoogleAds中国第一社群2022年02月28日一分钟跨GoogleAds中国第一社群跨境早报:NO。1【行业新闻】1。TikTokshop新增三个站点,布局女神节选品攻略,女装类产品包括各种女士外套、裙装以及运动休闲类服装,……
华为鸿蒙系统硬件生态品牌升级为HarmonyOSConnecIT之家5月18日消息在今天举行的华为鸿蒙伙伴峰会上,华为宣布鸿蒙系统硬件生态品牌升级为HarmonyOSConnect。华为消费者业务AI与智慧全场景业务部副总裁杨海松……
试驾合创007新能源电动车,你值得关注合创007是一款中型SUV新能源电动车,有着亮眼的外观,长宽高分别为487919371680mm,轴距达2919mm,优于同级别竞品车型。很好的给乘客提供了宽敞的驾乘空间,即使……
没有美国压制,日本军事能达到什么程度,军国主义还能兴起吗?没有美国,今天的日本也许已经是一个彻底报废的国度。因麦克阿瑟被引导、诱使,也因蓝党在大陆溃败,所以USA在1948年迅速放弃了改造倭国的计划,停止拆解财阀,放弃战争责任追……
属于你的前端面试题来了,注意查收1、img标签的title和alt属性有什么区别?alt:图片加载失败时,显示在网页上的替代文字title:鼠标(手机端该属性无意义)放在图片上时显示的文字alt是必需属性(但……
运营到底是啥?很多人觉得电商运营是非常复杂以及需要很多技术的东西,导致他们对电商运营这个工作始终究存在着偏见,这种偏见可能是单纯地认为电商运营的工资高或者低。这其实都不正确的。了……
带你实现简易的JDBC封装在我们常规的jdbc操作中,代码冗余问题非常严重,所以我们就想将jdbc六部曲的操作进行封装处理,这样可以我们的操作更方便、简捷。演示新增操作:假设我们有一个关于Car信……
手头有十万,存哪个银行利息会高点?10万块钱不算多,但也不算少,具体在哪个银行存款利息最高,我们就要先了解一下目前市场上不同银行的利率水平。目前我国有4000多家银行,20多万个银行网点,不同的银行甚至同……
刘润我讲到一半,警察来了(本文首发于刘润公号,订阅刘润公号,和190万读者一起洞察商业本质)1、昨天(4月23日),我在易仓举办的一场跨境电商论坛,做主题演讲。人山人海。2、讲到一半,主办……