欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新

Android multiple back stacks导航的几种实现,

来源: 开发者 投稿于  被查看 43983 次 评论:217

Android multiple back stacks导航的几种实现,


Android multiple back stacks导航

谈谈android中多栈导航的几种实现.

什么是multiple stacks

当用户在app里切换页面时, 会需要向后回退到上一个页面, 页面历史被保存在一个栈里.
在Android里我们经常说"back stack".

有时候在app里我们需要维护多个back stack, 比较典型的场景是bottom navigation bar或者侧边的drawer.

如果需求要求在切换tab的时候保存每个tab上的历史, 这样当用户返回的时候还是返回到上次离开的地方, 这种就叫multiple stacks.

(与之对应的single stack行为是返回之后回到了tab首页.)

本文之后的内容都以bottom bar的多栈导航为例.

multi-stack的需求

首先还是讨论一下需求.

当bottom bar不支持多栈时, 当点击切换底部tab, 再返回原来的tab, 所有在之上打开的页面都会消失, 只有第一层(根)页面会显示.

这也是可以接受的, 甚至在material design里面作为Android平台的默认行为被提及: material design

但它同时也说了, 如果需要的话, 这个行为是可以被改的.

如果你想保留用户在上个tab看过的内容状态, 很可能就需要做multi-stack, 每个tab上的栈是独立退出, 分别保留的.

通常, 这还不是仅有的需求.

如果用户点击已选中的tab, 需要重置这个stack吗?

需要定制转场动画吗?

需要保留tab历史吗? 比如从tab A -> B -> C, 在C的根页面back, 是想回到B还是回到home tab?

在bottom navigation的默认实现中(用Android Studio创建一个Bottom Navigation的新项目), 在非home tab的根节点, 点击back, 总是先回到home tab, 再次back才会退出app.
因为这样是符合固定start destination的原则的. 用户在打开后和关闭前, 看到的是同一个页面.

但是如果你有保存tab历史的需求, 也可以考虑如何定制它.

当你更进一步地涉及到实现层面, 你会遇到更多实际操作的问题, 比如怎么把一个详情页push到一个指定的栈, 如何pop destination.

让我们列一下几个需求点:

  • 维护多个栈.
  • 切换tab: 手动点击tab或者其他tab内的交互. 比如dashboard跳转到某个内容tab.
  • Push/pop destinations.
  • 重选(reselect)tab会重置该栈. (clear history.)
  • 转场动画
  • tab历史.

技术背景

要进行导航的选型, 首先确定一下你的"destination"是什么.

是composable还是fragment, 或者干脆是View, 解决方案可能有很大的不同.

以这篇文章的scope来说, 我们就关注一个传统的android app, 用Activity和Fragment实现.
所以bottom tab上的tab内容, 是不同Fragment.

Fragment lifecycle

为什么这里要提一下Fragment的生命周期呢?

因为fragment的生命周期和它的ViewModel紧密关联, 进一步关系到了在导航过程中我们是否需要关注fragment的状态恢复和刷新.

首先复习一下Fragment生命周期的回调: 什么时候onDestroy会被调用?

  • replacetransaction没有addToBackStack().
  • 当fragment被removed或者被popBackStack().

replacetransaction加上addToBackStack(), 旧的fragment会被压入栈, 但它的生命周期只调用到onDestroyView().
当在它之上的其他fragment pop出来以后, 旧的这个fragment实例依然是同一个, 它重新显示, 重新从onCreateView()开始走.

这是我们在single back stack下预期的行为.

ViewModel的生命周期和Fragment是对齐的, 也即Fragment的onDestroy()调用时, ViewModel的onCleared()被调用.

在导航切换目的地时, 如果fragment被destroy了, 我们可以保存一些关注的变量在saved instance bundle或者SavedStateHandle里, 用于之后的状态恢复.
但是如果fragment没有被destroy, 我们可以剩下不少力气做这些状态恢复.

所以理想的状态是, 压栈后的fragment实例不会被销毁重建.

为了比较不同的解决方案, 我把一些sample放在了一起: https://github.com/mengdd/bottom-navigation-samples

Jetpack navigation component

官网: https://developer.android.com/guide/navigation

即便在FragmentManager的文档 里, 也建议开发者使用jetpack的navigation library来处理app的navigation.

multiple back stack的支持是Navigation 2.4.0-alpha01 和 Fragment 1.4.0-alpha01才加的.

试了下这个 demo,
代码非常简单, 我们基本什么都不用做.

关于这里面的思想可以看这篇文章: https://medium.com/androiddevelopers/multiple-back-stacks-b714d974f134

优点:

  • 最知名, 毕竟是官方的库.
  • 支持类型安全的参数.
  • NavigationController支持pop到一个指定的destination.
  • 可以和Compose navigation库一起使用.

缺点:

  • Multi-stack的支持: 当切换tab时, 前一个tab上的所有fragment都会被destroy, 当返回tab时栈内fragment会重建. 所以状态会丢, 页面可能会刷新.
  • 每个tab都需要是一个内嵌的navigation graph, 如果有一些common的destination, 需要include到每个graph中去. xml的navigation文件感觉很像一个大块的样板代码.

FragmentManager

如果我们想做更多的定制, 我们可以考虑用FragmentManager的新APIs自己手动实现.

在文档中doc 介绍的:

FragmentManager allows you to support multiple back stacks with the saveBackStack()
and restoreBackStack() methods. These methods allow you to swap between back stacks by saving one back stack and restoring a different one.

这是navigation component实现中实现多栈导航使用的方法.
所以也可以解释为什么切tab的时候fragment都被销毁了.

saveBackStack() works similarly to calling popBackStack() with the optional name
parameter: the specified transaction and all transactions after it on the stack are popped.
The difference is that saveBackStack() saves the state of all fragments in the popped transactions.

优点:

  • 精细控制, 开发者获得更多控制, 也更明白到底是怎么回事.
  • 如果我们当前项目没有采用任何navigation library, 都是手动跳转, 采用这种方法我们就不用考虑迁移navigation.

缺点:

  • 要写很多fragment transaction的样板代码.
  • 和navigation components一样: 多栈实现中在切换栈时, 在旧的tab上的Fragments会被销毁, 返回时全部重建.

Enro

https://github.com/isaac-udy/Enro

对于多module的大型项目来说, 我很推荐这个库, 它可以帮助我们解耦module间的依赖.

multi-stack的demo

优点:

  • 基于注解, 所以要写的代码很少, 导航使用很方便.
  • 多module项目解耦.
  • 传类型安全的参数和返回结果都很容易.
  • 可以在ViewModel中获取navigation handle, 获取参数.
  • 支持Compose做节点.
  • 对Unit Test也有一个辅助测试的依赖.
  • multi-stack support: 保持了切换tab的时候fragment实例.

缺点:

  • 可能目前还不是很知名. 需要说服别人学和采用这个.
  • Fragment的multi-stack: 不能rest stack到根节点. (尝试了一下定制这个行为, 有点难).

Simple-stack

https://github.com/Zhuinden/simple-stack

这里推荐一下这个库作者的文章Creating a BottomNavigation Multi-Stack using child Fragments with Simple-Stack.
关于如何用simple-stack来做multi-stack.

最开始作者展示了一个不用任何库, 仅用child fragments来实现的版本.

这是手动实现的另一种思想了.

后来才引入了用simple-stack做的demo
这是采用了原作者提供的sample, 比较简单, 试了一下以后我发现可能还需要添加更多的代码, 来做实际的应用.
比如详情页需要获得某个tab的local stack的实例, 从而把自己push上去.

优点:

  • 作者在社区十分活跃, 有很多视频和文章介绍simple-stack这个库. 所以社区支持挺好.
  • multi-stack support: 保持了切换tab的时候fragment实例.
  • 支持控制和清空栈的历史.
  • 有compose的扩展.

缺点:

  • 如果你的bottom bar当前是在activity的布局里, 你需要把bottom bar和相关的东西都挪进一个RootFragment, 作为总的节点.
  • 作者提供的multi-stack sample还非常简单, 需要写更多的代码来或者当前正确的栈来做push和pop操作. 不了解这个库可能会写得很丑.

其他库

还有一些库, 不是通用的navigation解决方案, 而只是为多栈导航设计的小库.
比如:

  • https://github.com/DimaKron/Android-MultiStacks
  • https://github.com/JetradarMobile/android-multibackstack

这些库都自带sample.

优点:

  • 实现简单, 只用几个类. 如果我们想定制我们可以用这个代码.
  • 要改动的范围可以限制在bottom navigation的部分, 而不是整体改变navigation方案.

缺点:

  • 这些库都不是很出名, 有不再维护的风险.
  • 可能和其他的navigation方案不能兼容, 比如Navigation Components. 需要考虑整体.

总结

android (fragment实现) multi-stack navigation的可能解决方案:

方案 流行 整体方案 活跃 支持清空栈 Fragment被保存, 不被销毁 支持Multi-modules Compose扩展
Jetpack Navigation Components 官方, 最出名 Yes Yes Yes No Yes Yes
Fragment Manager Android SDK - Yes Yes No No -
Enro Star: 188 Yes Yes No Yes Yes Yes
Simple Stack Star: 1.2k Yes Yes Yes Yes Yes Yes
Child Fragments Android SDK - Yes Yes Yes No -
JetradarMobile/android-multibackstack Star: 224 No No Yes No No -
DimaKron/Android-MultiStacks Star: 32 No Not sure Yes Yes No -

注意:

  • 整体方案: 表示该方案可以用于app整体的navigation解决方案, 而不仅仅是解决multi-stack的问题.
  • Fragment被保存, 不被销毁: 当跳转或者切tab时, 被压入栈中的fragments不会被destroyed. 多栈支持的情况下, 尽管fragment被返回时都会被重建, 但是如果它不被销毁, 我们就不需要做额外的工作来缓存状态.

References:

  • Sample: https://github.com/mengdd/bottom-navigation-samples
  • Jetpack Navigation Components
  • Enro
  • Simple-stack
作者: 圣骑士Wind
出处: 博客园: 圣骑士Wind
Github: https://github.com/mengdd
微信公众号: 圣骑士Wind

相关文章

    暂无相关文章

用户评论