原文:Fragments: Rebuilding the Internals. Introducing: the new state manager
做者:Ian Lake
译者:Flywith24java
多年以来,Fragment 要比大多数 Android API 更新得更多。它们最初是 Android platform 的一部分,后来成为 Android Support Library 的一部分,如今以 AndroidX Fragments 的形式成为了 Jetpack 组件的一部分。android
注意:您毫不应该使用 Android framework 版本的 Fragment。不只由于它已经在 Android 10 中彻底弃用了,还由于它已经长时间没有被修复 bug,并且保证不一样设备和 API 级别之间的一致性。git
虽然 Architecture Components 接管了不少过去须要使用 Fragment 的场景(例如 使用 LifecycleObserver 处理生命周期的回调,或者使用 ViewModel 来保留状态),若是您使用 Fragment 来处理这些场景,则须要经过 FragmentManager
来进行 add / remove 操做来与之交互。markdown
随着 Fragment 1.3.0-alpha08
的发布,FragmentManager
内部的重大重构已经完成。该版本使用 更小的,可测试的,可维护的(内部)类替换了许多 FragmentManager
中的逻辑。其核心类是 FragmentStateManager。架构
注意:我将在这篇文章中讨论 FragmentManager 的内部原理。若是在使用 1.3.0-alpha08 遇到任何问题,请尽快提交 file issues 协助咱们修复。app
新的 state manager 的职责:oop
咱们完全回顾了这些系统过去的工做方式,发现 它们须要被从头重写 ,因而咱们重写了它们。它们比以往的任什么时候候都更好,咱们可以关闭至少 10 个长期存在的相关 issues,而且这个内部重构为单 FragmentManager 支持 多返回栈 扫清了道路(译者注:Bottom Navigation 管理平级界面的问题),并简化了 Fragment 的生命周期。源码分析
每一个 FragmentManager
都与一个 host 关联,对于大多数 fragment,host 为 FragmentActivity(使用 FragmentController
和 FragmentHostCallback
能够自定义 host,但不在本文的讨论范围)。当 activity 转移到 CREATED
,STARTED
,以及RESUMED
, FragmentManager 派发这些更改到它的 Fragments。这是 moveToState()
的职责。(译者注:源码分析参见 【背上Jetpack】从源码角度看 Fragment 生命周期)。post
固然,它没有那么简单。有不少条件逻辑来肯定 fragment 该处于什么状态—— activity 的生命周期状态(若是是嵌套 Fragment 则是其 parent fragment 的生命周期状态)只是第一部分,它被称为 fragment 可以处于的 max state。最大状态保证了 activity ,fragment,以及它们的 child fragment 可以正确地嵌套。测试
所以 简化 moveToState()
的首要任务是将全部逻辑抽离到一个位置。因而 FragmentStateManager 诞生了。每一个 fragment 实例与一个 FragmentStateManager
绑定。经过引入这个内部类,咱们可以从 FragmentManager
中抽取与 fragment 交互的大量代码(例如调用 fragment 的 onCreateView
方法以及其余生命周期方法)。
该拆分还使咱们可以编写一个方法,该方法将使用全部向后兼容的所需逻辑来肯定 fragment 实际应处于的状态,并将其集中在一个位置:computeExpectedState()。该方法追踪当前的全部状态而且肯定 fragment 应处于什么状态。98% 的时间,他们与 host / parent fragment 处于同一状态,可是剩下的 2% 与那些创建在 fragment 上的 app 有很大不一样。
可是有一种咱们没法肯定正确状态的状况:postponed fragments。
不管是好是坏,Fragment 都继承了许多与 Activity 相同的命名的 API。这种继承的一部分是围绕转换以及推迟转场直到目标准备好的能力。这对共享元素转场很是重要,与此同时还要确保在转场的同时不会产生更密集的数据加载(译者注:为了转场时先完成动画,再完成大数据的渲染)。
延迟 fragment 拥有两个重要的特性:
STARTED
当您调用 startPostponedEnterTransition()
后,fragment 的转场便会执行,view 将变得可见,而且 fragment 将会移动到 RESUMED
。实际上,这正是新的 state manager 作的,过去的 fragment 不是这样工做的,详情参考 Postponed Fragments leave the Fragments 和 FragmentManager in an inconsistent state 这两个bug。
当 fragment 被使用
postponeEnterTransition()
推迟了,预期的行为是:fragment 被添加到的 container 不会运行任何进入动画或者以前排队的退出动画(例如replace
操做)直到 Fragment 调用startPostponedEnterTransition()
。同时当 fragment 的 container 被延迟时,fragment 不会达到RESUMED
状态。然而,彷佛
FragmentManager
没有执行上述操做,反而将 Fragment 和整个 FragmentManager 转移至一个怪异的,不一致的状态。也就是说,任何与 postponed Fragment 的 container 相关的 FragmentTransaction 被「回滚」(如返回上一 fragment),但实际上,这些 fragment 没有移至其正确的状态。
这致使了一些列的问题;
实际上解决这些问题中的任何一个都意味着须要一个系统替换 postponed fragment 使用的整个回滚过程,该系统保证 FragmentManager 的一致性,最新状态,同时保留 postponed fragment 的主要特性。
FragmentManager
具备 container 这个不错的属性(读起来:很方便,但做为维护者却不那么有趣),在该属性中,您能够为要放置 Fragment 传入任何 container id 。甚至在一个 FragmentTransaction
中,您能够 add
一个 fragment 到一个 container,从一个不一样的 container remove
另外一个,replace
第三个container 最顶端的 fragment,等等。
fragment 动画进/出会产生接触,这仅发生在 container 层。
Fragment 支持一些列的动画系统:
Animation
APIAnimator
APITransition
API(仅支持 21+,一样很烂)Transition
API众所周知,命名是计算机科学中最难的问题之一,所以当咱们去构建一个能够控制全部这些 API 的类时,花了一些时间才决定使用 SpecialEffectsController(该类不是 public API,所以命名可能更改)。该类存在于 container 层,而且协调进入和退出 fragment 相关的全部特效("special effects") 。
SpecialEffectsController
是 container 中真实状况的 惟一信源。这意味着若是最顶部被 add 的 fragment 被推迟了,整个 container 也将被推迟。再也不须要 FragmentManager 层的逻辑,也不须要 transaction 的任何回滚(如咱们前文提到的,它将影响多个 container)。所以, FragmentManager 处于正确的状态,咱们仍能够得到被延迟 fragment 的全部属性。
这个 API 还容许咱们将 fragment 的全部疯狂的特效 API 集中到一个 DefaultSpecialEffectsController
中,该 controller 负责运行 transition,animation,以及 animator。再次过去分散在 FragmentManager 中的逻辑移动到了一个地方。
它意味着要替代这种架构:
旧的 state manager:全部逻辑都在
FragmentManager
看起来更像这样:
新的 state manager:
FragmentManager
与各个FragmentStateManager
实例进行通讯,这些实例经过SpecialEffectsController
与 container 中的其它 fragment 进行协调。
经过拆分 FragmentManager 的内部结构,每一层的逻辑都获得了极大简化:
FragmentManager
仅具备适用于全部 fragment 的状态FragmentStateManager
在 fragment 层管理状态SpecialEffectsController
在 container 层 管理状态职责的分离使咱们的测试套件扩大了近 30%,涵盖了几乎没法单独测试的更多场景。
不。事实上,咱们对新旧状态管理者都进行了很大一部分的 fragment 测试,专门用于确保咱们有一套强大的回归测试。
可是,若是您依赖于不一致的状态,则能够将 FragmentManager 放入延迟的 fragment 中,而后,是的,您会发现实际上已经得到了正确的状态。 在 发行说明 中,您会找到与新状态管理器相关的错误修复列表,所以请仔细阅读以确保您的问题不是由您本身的解决方法引发的,而该解决方法是您能够如今删除的旧行为 。
与 Fragment 1.2.0 中的 onDestroyView 计时更改 相似,新的状态管理器将使 fragment 保持在 STARTED 状态,直到其 transitions/animations/animators/special effects 所有完成为止,从而使全部 fragment 保持一致,不管它们是不是直接 postponed 或因为同一 container 中的其它 fragment 引发的 postponed 。
新的状态管理器会默认开启。若是你在你的 app 中看到了与以前不一样的行为,首先使用如下新的实验性的 API 来排查是不是因为新的状态管理器致使的。
当你更新了 Fragment 1.3.0-alpha08
后,新的状态管理器会默认开启。若是你在你的 app 中看到了与以前不一样的行为,首先使用如下新的实验性的 API 来排查是不是因为新的状态管理器致使的。
我国启新的状态管理器。若是您发现应用程序和过去存在差别,首先可使用新的实验性的 API 来排查是否与新的状态管理器相关:
FragmentManager.enableNewStateManager(false)
复制代码
该 API 可让您重温旧世界,让您验证所看到的的任何与以前不一致的改变是否因为新的状态管理器致使。若是确认是因为新的状态管理器致使,您能够构建一个示例项目重现您遇到的问题,并 在此提 Issue。
注意:
FragmentManager.enableNewStateManager()
API 是 实验性的。这意味着它不被视为 Fragment 稳定API 的一部分,能够随时删除。 删除全部旧代码能够节省大量代码,可是鉴于正确处理代码的重要性,咱们可能要等到 Fragment 1.3.0 的稳定版本发布后才能删除 API,好比考虑在 fragment 1.3.1 发布时移除。
在 11 个月内进行了 100 屡次我的更改,这绝对是一段时间内 Fragment 的最大内部更改,它使咱们创建了更加可维护,可持续和可理解的代码。 这意味着跨 Fragment 的行为更加一致,而且它成为您在构建应用程序时能够依靠的坚实基础。 咱们很是感谢您继续提出问题并 提供反馈。
感谢 Jeremy Woods,Chet Haase,和 Nick Butcher。
译文完。
在 Fragments: Past, Present, and Future (Android Dev Summit '19) 演讲中 Ian Lake 阐述了 fragment 的将来。掘金官方文章在这。
其中 弃用 targetFragment API 使用新的 FragmentResult API 已经发布:详情移步【Jetpack更新之Fragment】1.3.0-alpha04 来袭,Fragment 间通讯的新姿式
Fragment 的生命周期正在被简化,例如 onActivityCreated 被弃用了,详情移步【Jetpack更新之Fragment】终于动手了,onActivityCreated 被弃用。不过距离合并 Fragment 自身与其内部 View 的生命周期还有很长一段路(不知官方是否会放弃)。
多返回栈一直被本文解决的问题阻塞,文中所说的过去 11 个月即是去年 9 月 Android Dev Summit '19 到如今。而下面的 issue 是2018 年 5 月建立的。
好在多返回栈的绊脚石已经解决,不过这里咱们也能够看到 SDK 级别代码难以维护后的优化成本,文中重写了 fragment 状态管理逻辑,即便作了大量测试,仍要保留旧版逻辑并留出切换的入口。
查看 Fragment 代码变化咱们能很明显的认识到「职能单一」重要性,这对构建可维护的,可理解的,可测试的,健壮的代码十分重要。
关于 fragment 的其余内容,可参考