17b3ccc59b9175de6560bb1965ee415e
重学安卓:我的碎片很听话,你的 Fragment 有自己的想法

温馨提示:如果这是第一次接触《重学安卓》,可借助 这份在 GitBook 上维护的 “导读” 来快速了解《重学安卓》专栏、获取它的目录、试读内容,以及了解它的最新动态 和 发展状况。

截至目前,专栏已对 体系化文章 做了 730 余次修订,数十位群友告诉我 受专栏的启发 他们也开启了写作之路。群里不定期会有小伙伴讨论适配问题、分享原创的开源库 和 提供内推机会,订阅后可随时进群交流。

前言

很高兴见到你!

上周交给实习生一项任务:开发一个云新闻客户端,推送行业内的消息,以及公司的活动近况。

该客户端由多个 Fragment 组成,Fragment 之间的关系有平级,也有跳转。

上周五任务完成,拿到手一看,“有点东西啊”,同事感叹道。短短几天时间,实习生就能从零开始把一个新闻客户端搭好。没想到呢,当屏幕一旋转,问题就暴露了 —— Fragment 发生了重叠。

啥是重叠呢?为了便于理解,我录制了《重学安卓》项目的测试示例。
如图可见,若 Activity 不做相关的处理,在 Activity 重建时,额外被创建的 Fragment 将叠加在被恢复的 Fragment 之上 —— 注意观察下方重叠的数字。

为什么会这样呢?

我们常常在网上见到《Fragment 重叠》相关的文章,可文章 一上来就是怎么做、贴一长串代码,就是不告诉读者为什么 会发生重叠、实际上究竟是什么状况引起的。

这使得多数人遇到了这样的文章,就只得先收藏着,然后过了一段时间又遇到这个题材的文章,觉得有用,又收藏了一篇,但都没细看,对不对?

这样问题就始终没有得到确认和解决。

所以本文的目标,就是借着介绍 Fragment 的机会,来特别地给大家明确一下 Fragment 重叠究竟是什么状况。

即使没有遭遇过重叠,也请务必阅读好这篇!

文章目录一览

  • Fragment 的缘起和职责为何?
  • Fragment 回退栈和 Activity 的区别?为何存在这样的区别?
  • Fragment 通信与 Activity 的区别?为何存在这样的区别?
  • Fragment 生命周期为何如此设计?
  • Fragment 生命周期和 Activity 的关系?
  • Fragment 的重建和状态管理与 Activity 的区别?
  • 为何会导致 Fragment 重叠?
  • 为何推荐使用 DialogFragment?
  • 为何视图控制器建议用 Fragment 而不是 Activity?
  • 综上
  • Note 2020.3.31 加餐:
    • Fragment 最新 API 支持 replace 回退后的状态恢复
  • 后记

Fragment 的缘起和职责为何?

Fragment 是在 Android 3.0 引入的,最初是专门针对平板视图而存在。

在 Android 4.0 中,Fragment 被升级为手机和平板都能使用。

如此一来,我们便知道了,Fragment 的存在是为了:

  • 适配横竖屏场景的不同需求。

例如在平板模式下显示手机布局,不美观,也浪费空间,Fragment 的出现就能解决这样的问题,让 List 和 Detail 同时显现。

  • 实现代码的复用。

一个 Fragment 类可以被实例化多次,也可以在多个 Activity 中使用。反之,每新建一个 Activity,就要重新部署一套视图控制,就很麻烦。

因而,更纯粹地说,Fragment 的存在,是 专注于承担视图控制器的责任,以分担 Activity 的责任、让 Activity 更加专注于 “幕后协调者” 的工作。

Fragment 回退栈和 Activity 的区别?为何存在这样的区别?

《你丢了 offer,只因拎不清 Activity 任务和返回栈》 一文中,我们正确区分了“任务”和“返回栈”实际是两个不同的概念,并且明白了“启动模式”是怎么影响 Activity 和任务之间的关系。

Fragment 也包含回退栈的设计。

Note 2020.07.31

注:即日起本文中使用 “回退栈” 来称谓 BackStack,区别于 Activity 的 “返回栈”,缘由是 Fragment 的 BackStack 是基于事务的操作,翻译成 “回退” 会更贴近 事务的背景下 RollBack(回滚)的含义。感谢读者 @AE.86 的提醒。

在了解 Fragment 回退栈之前,我们先来了解一下 Fragment 生存的背景:

Fragment 是完全依附于 FragmentActivity 而存在,因为有且只有 FragmentActivity 直接或间接地包含了与 Fragment 管理相关的一系列成员,包括 FragmentController、FragmentManager 等等。

注:AppCompatActivity 是 FragmentActivity 的子类,所以使用 AppCompatActivity 即代表支持 Fragment。

也因此,和 Activity 的区别在于,Fragment 的管理没有任务栈,而只有回退栈。

—— 为什么这么设计呢?

如果通过上述的描述,你还是不免要如此发问、而不能逻辑连贯地秒懂的话,那还是推荐请你先阅读 《你丢了 offer,只因拎不清 Activity 任务和返回栈》

我换个方式再简单地解释一下:因为 Activity 是组件,组件被设计成是要考虑跨进程通信,因而 Activity 的管理不仅被设计出任务栈来管理 Activity,还设计出返回栈,用于管理来自多个 App 的 SingleTask 模式且 taskAffinity 指明过不同任务名的任务。而 Fragment 因为背靠单个 Activity,而不需要考虑 跨进程/跨App 的问题,因而它只需要回退栈。

划重点 👆👆👆

getSupportFragmentManager().beginTransaction()
     .add(R.id.fragment_container, fragment, fragmentName)
     .addToBackStack(null)
     .commit();

通常我们会以如上的方式将一个 Fragment 以事务的方式添加到回退栈。

事务我们在大学学习数据库时接触过,是指一组原子性的操作,也即这一波操作要么全完成,要么全不完成,绝不导致烂尾的现象,而且完成后还可以回滚到完成前的现场。

很显然 Fragment 的回退,就是事务的回滚。

那既然 Fragment 回退栈采用事务管理的方式,Activity 的管理为何不如此设计呢?

因为 Activity 与任务的关系、任务与返回栈的关系随时可能发生变化,在栈中的位置随时动态发生变化,而非 Fragment 这种单纯地进或出。

划重点 👆👆👆

被 Fragment 回退栈管理的一个个对象就叫 BackStackRecord —— 是对 FragmentTransaction 的实现 —— 当你提交 Fragment 事务时,实际上提交的就是一个个 BackStackRecord。

值得注意的是,上述提到的 FragmentController,它只是直接隶属于 FragmentActivity 的,用于批量管理 Fragment 的一个代理,实际真正在管理 Fragment 的是 FragmentManager …… 这些你们顺着源码去看就行,能不贴代码我尽量不贴代码,因为动不动就贴一长串代码是最影响阅读、且最没水平的 :D

Fragment 通信与 Activity 的区别?为何存在这样的区别?

上一节我们讲到,Fragment 与 Activity 在返回栈设计上的差异,取决于 Fragment 和 Activity 在生存环境上的差异。

同样的,由于生存环境的差异,导致 Fragment 与 Activity、乃至 Fragment 之间的通信,不再是组件间的通信,而无需考虑跨进程通信、无需使用 Intent 通过 Binder 来通信,而是可以直接使用接口。

具体的 代码示例请在《重学安卓》项目的 OneTestFragment 中查看(不要慌,文末链接给出),此处不作累述。

Fragment 生命周期为何如此设计?

《Activity 生命周期的 3 个辟谣》 一文中,我们结合“进程模式”的概念,介绍了 Activity 生命周期的设计依据。

Fragment 的生命周期同样有其设计依据。

Fragment 生命周期和 Activity 生命周期存在一定的差异,并且受事务添加方式的影响。

主要的差异表现在,Fragment 的生命周期节点相比 Activity 多了 onAttach、onCreateView、onViewCreated、onActivityCreated、onDestroyView、onDetech,而少了 onRestart。

top Created with Sketch.