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

前言

很高兴见到你!

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

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

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

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

为什么会这样呢?

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

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

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

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

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

文章目录一览

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

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 也包含返回栈的设计。

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

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

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

也因此,和 Activity 的区别在于,Fragment 的管理没有任务栈,而只有返回栈。一个 AppCompatActivity 对应一个 FragmentManager 乃至于一个返回栈。

—— 为什么这么设计呢?

如果通过上述的描述,你还是不免要如此发问、而不能逻辑连贯地秒懂的话,那还是推荐请你先阅读 《你丢了 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。

这些 record 未必总是进入返回栈,当你没有 addToBackStack(null)。

值得注意的是,上述提到的 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 生命周期存在一定的差异,并且受事务添加方式的影响。

top Created with Sketch.