生命周期与Lifecycle
# 生命周期的前世今生
# 1.1、前世——初识篇
天地初开,一切皆为混沌的时代,安卓宇宙中诞生了名为Activity
(活动)的组件,Activity
是Android应用中最关键的组件,一个Activity
通常对应的是App的一个页面,当手机使用者在不同的页面之间导航的时候,新的Activity
会诞生,同时也会在特定的时候销毁。一个页面的诞生之初到它销毁的这段时间,名为「生命周期」。
理解并掌握生命周期是每一个Android修炼者的必修功力,因为生命周期的每一个阶段均代表Activity
处于不同的状态之中,一旦错误处理生命周期周期,修炼者轻则内伤残疾(手机耗电过多,丢失信息),重则走火入魔(程序崩溃)。
关于生命周期,江湖中一直流传着一张「Activity生命周期总览图」,但个中奥秘,却鲜为人知,因此少有人能够修炼到最高境界:
由图可见,Activity
的生命周期中,提供了6种回调:onCreate()
、onStart()
、onResume()
、onPause()
、onStop()
、onDestroy()
。
需要特别注意的是:这仅仅是一种回调,与我们通常的认识不同都是,生命周期的某个阶段是指一个时间段,而回调或者说事件只是一个瞬间,换句话来说,onCreate并不是指生命周期中存在一个阶段名为onCreate,而是
Activity
触发了onCreate事件,即将进入已创建阶段。
然而可惜的是,在远古Android的设计中,Android的创世神并没有为开发者提供具体的生命周期阶段的概念,仅仅是提供了进入某个生命周期阶段的回调,因此上述提到的“已创建”这个状态在原生安卓的概念中并不存在。然而在后人的努力中,生命周期阶段这一概念最终得到确定与落实,不过这都是后话了。
# 1.2、前世——详解篇
# 1.2.1、onCreate()、onDestroy()
onCreate():
Activity
生命周期的起点,首次被系统创建时触发,整个生命周期只会触发一次。此回调通常用于执行页面View的设置,例如setContentView()
。onDestroy():
Activity
生命周期的终点,在Activity
被销毁前触发,此回调的有两种情况被调用:
- 用户手动关闭
Activity
(按返回键)或者系统主动关闭Activity
(一般是App进程因内存不足被销毁,导致Activity
也被销毁)。 - 配置变更(设备旋转、语言切换等)。
简单来说,onCreate()是Activity
被创建的时刻,onDestroy()是Activity
即将被销毁的时刻。
一个Activity
进入onDestroy()之后,理应被GC回收,但是如果此时它仍然被引用(例如被某些网络请求的回调中被引用),那么此Activity
就会导致内存泄漏,这也是所有Android开发者需要关注其生命周期的原因。
# 1.2.2、onStart()、onStop()
- onStart():当
Activity
在onCreate()之后不久就会触发此回调,说明了Activity
此刻进入了“已开始”的状态,但是此刻的Activity
仍然未获取焦点。
很多Android开发者一直搞不懂Activity为什么会存在一个可见但是没有获取焦点的状态,会存在这种疑惑的原因是因为Android通常作为一种移动设备的系统而存在,而移动设备由于其特殊性,通常也只会同屏存在一个页面,因此可见但是没有获取焦点这种状态几乎只存在一瞬间(它马上就会遮住之前正在交互的页面),然而我们以电脑系统的角度来看,电脑系统的桌面上基本都是多窗口并存的,然而即使存在了多窗口,用户能交互的也仅仅只有获取焦点的那个窗口。
因此,可见但是没有获取焦点的窗口,就像是电脑上那些打开着、但被用户正在交互的窗口挡住的那些窗口,假如电脑桌面上存在着一个QQ窗口,然而用户正在编写一个Word文档,那么被Word挡住的那个QQ窗口,就是可见但未获取焦点的窗口,下面用一张图区别一下onStart()和onResume()的区别:
- onStop():当一个
Activity
从可见但是没有获取到焦点的状态变为完全不可见的状态时就会触发此回调,按照上文类比,这种情况通常可以理解为:电脑桌面上的一个被遮挡的窗口被最小化了。
# 1.2.3、onResume()、onPause()
onResume():当
Activity
从可见但是没有获取焦点的状态变成可见同时获取焦点的状态时,触发此回调,同样按照电脑系统的角度来理解,这种情况通常可以理解为:电脑桌面上的一个被遮挡的窗口此刻被用户交互了。onPause():当
Activity
从“可见同时获取焦点”的状态变成可见但是没有获取焦点的状态时,触发此回调,同样同样按照电脑系统的角度来理解,这种情况通常可以理解为:电脑桌面上的一个正在被用户交互的窗口,由于用户操作了其他窗口,导致当前的窗口被遮挡了,也因此失去了焦点。
# 1.3、前世——总结篇
我们从电脑系统的窗口去理解Activity
的生命周期:
- 启动一个程序的时候,程序就会在电脑桌面上创建一个窗口,创建的那一瞬间(通常会很快,可能不需要1秒)就相当于
Activity
的onCreate()
。 - 创建完成后,窗口就可以被用户所看见了,被用户看到的那一瞬间就相当于
Activity
的onStart()
。 - 通常来说,一个新启动的程序会自动获得焦点并可被用户交互,因此onStart()之后,窗口会被置顶到顶层,这一瞬间就相当于
Activity
的onResume()
。 - 当用户选择其他窗口时,之前交互的窗口并不会消失,而是会失去焦点并被用户最新交互的窗口所遮挡,这一瞬间就相当于
Activity
的onPause()
。 - 当用户最小化窗口时,窗口就会进入后台(并不是销毁)而且并不能被用户所看见,这一瞬间就相当于进入了
Activity
的onStop()
。 - 当用户关闭程序亦或者电脑内存不足时,程序被销毁,窗口同时也被销毁了,这一瞬间就相当于进入了
Activity
的onDestroy()
。
一个窗口当然可以失去焦点后重新获取焦点,因此onPause()
和onResume()
可能在生命周期中多次被执行,同理窗口也可以最小化之后重新最大化,onStart()
和onStop()
也可能在生命周期中多次被执行。只不过对于移动设备来说,几乎不存在页面失去焦点后又重新获得焦点的情况,因为移动设备的页面绝大多数情况都是一个页面可被用户交互,被挡住的页面完全不可见,即等价于电脑系统中只存在一个最大化的页面,所以移动设备的Activity
的生命周期通常只会在onStart()
和onStop()
两者之间流转(当然,仍然会遵循onStart()->onResumt()->onPause()->onStop()的顺序)。
而一个窗口只能被创建和销毁一次,因此在Activity
的生命周期中,onCreate()和onDestroy()只会被调用一次。
上文中提到,原生的Android生命周期设计中,只提供了进入某个生命周期状态的回调,并没有提供具体的状态的定义,例如onCreate()与onStart()之间的状态叫什么呢,官方的文档提到了这个叫“已创建”的状态,然而这只存在于文本性的文档中,这在代码中并不存在,只能作为一种“共识”的定义。这也为开发者之间沟通生命周期带来了极大的困扰。
# 2.1、今生——初识篇
经历漫长的混沌时代之后,Jetpack携带着「Lifecycle」正式进入到了Android的世界中,「Lifecycle」为千千万万的Android修炼者带来了福音,因为它比起传统的基于回调的方式来感知生命周期的方式有以下的优点:
- 提出了「生命周期状态」的概念,弥补了安卓传统的生命周期只有事件没有状态的缺陷
- 将生命周期管理从页面(如Activity和Fragment)脱离,将生命周期监听的职责转移到组件中,降低页面与组件的耦合度。
为了让读者更加清晰使用「Lifecycle」与不使用它之间的区别,这里使用两个代码案例来对比:
- 首先,定义一个常见的基于回调的监听类,每秒钟会对外广播一次字符串。
在
Activity
中的onCreate()阶段初始化监听,然后在onStart()中开启监听,在onStop()中关闭监听,这样的好处是当页面不可见的时候不会浪费手机性能。
以上便是传统安卓开发中最直接也是最常见的一种根据生命周期来实现监听的方式,让我们分析一下这种方式的缺点:
- 真实业务开发中,同一页面中往往存在大量的生命周期监听需求,
Activity
等生命周期组件会同时管理大量的组件,让代码难以维护。 - 代码缺乏一致性,需要监听生命周期的组件存在许多模板代码。试想一下,一个需要在onStart()启动,在onStop()关闭的、同时在项目中大量存在的组件,某天需要它在onResume()做一些操作,那将会导致灾难,因为需要每一处使用它的代码中增加onResume()的修改,一旦遗漏这个修改将会导致不可预知的bug。
- 无法获取实时的生命周期状态。假设在onStart()的阶段,需要执行一个网络请求或者其他耗时操作之后再调用
listener.start()
的场景下,无法保证此刻页面仍然处于可见的状态,开发者也无法获取「当前所处状态」来避免不可见的时候仍然调用listener.start()
(这个缺陷上文已经提到,原生安卓生命周期只提供了生命周期事件而没有生命周期状态)。
让我们看一看使用了「Lifecycle」库之后的生命周期是如何实现监听的:
我们让需要监听Activity
生命周期的MyListener
组件实现DefaultLifecycleObserver
接口,然后重写onStart()、onStop()方法,然后直接在Activity
中获取lifecycle
然后调用其addObserver()
即可。
我们会发现,「生命周期管理」的责任从Activity
转移到了组件中,Activity
本身只负责对外广播自身的生命周期,这样极大减少了Activity
的维护负担。
# 2.2、今生——详解篇
# 2.2.1、Lifecycle (opens new window)
Lifecycle包含两个定义,一个指的是Jetpack库中的Lifecycle组件库,一个指的是Lifecycle组件库中的一个核心类,后文中如果没有特指情况下,文章中描述的默认为类
上文中提到,安卓原生中只有描述生命周期的事件,缺乏一种描述当前生命周期所处的状态,但是「Lifecycle」库中补全了状态,下图中阐述了事件与状态的关系:
根据「Lifecycle」库的定义,一个生命周期状态的起点是「Initialized」,终点是「Destroyed」,当发生生命周期事件时,生命周期状态就会发生移动,包括状态提升和状态下降。
我们把状态从Initialized到Resumed当做一个从小到大的状态,如果状态值变小了,则称为状态下降,反之则为状态提升。
初步的定义有了,让我们把视角聚焦于Lifecycle
类的源码:
可以看到,Lifecycle
类的设计基本遵循生命周期事件与状态图例,一个Lifecycle
只有2个核心功能:
- 缓存当前的生命周期状态(currentState)。
- 添加与移除生命周期观察者。
上述代码中,对Event
和State
的部分代码进行了省略,下面展开讲解:
首先是Event
类,Event
类对应的是生命周期事件,也就是原生安卓生命周期的事件,即onCreate()、onPause()等。
该类提供了一个targetState
的属性,指的是发生了该事件之后,生命周期状态发生改变的状态目标。
例如发生了ON_CREATE事件,这是状态从「Initialized」向「Created」转移的瞬间,那么targetState自然就是「State.CREATED」了;同理发生ON_STOP事件时,是状态从「Started」向「Created」转移的瞬间,targetState也是「State.CREATED」。
此处不必死记硬背,只需要配合状态与事件图理解其意义即可。
该类还提供了四个方法,downFrom()、downTo()、upFrom()、upTo(),这些都是当状态发生提升或者降级的时候,方便获取对应的事件的便捷方法,以downFrom()举例:
downFrom(state:State)的含义是获取会导致state发生状态下降的事件,假如State.Created,发生什么事件会导致状态从State.Created下降呢,我们回去查看状态与事件图,发现是发生了ON_DESTROY事件,那么该方法就会返回ON_DESTROY。
此处不必死记硬背,只需要配合状态与事件图理解其意义即可。
看完了Event,我们把视角转向State:
State
的代码非常简单甚至不用一丝的省略,除了枚举值外仅有一个方法:isAtLeast(state:State),此方法的含义是用于判断当前的状态是否大于或等于目标值的状态。
如何理解呢?还记得上文提到的吗,状态是有大小的:
我们把状态从Initialized到Resumed当做一个从小到大的状态,如果状态值变小了,则称为状态下降,反之则为状态提升。
因此对于生命周期的状态而言,Created是比Initialized大的,isAtLeast(state:State)
的含义就是判断生命周期是否比某个预期值“走的更远”了,如果一个行为可以在组件创建后被执行,那么换句话说,只要生命周期的状态大于或者等于Created即可。
上文中提到,原生的生命周期回调无法实时获取生命周期所处的状态,一旦在生命周期回调方法中执行一些耗时操作,就无法耗时操作结束后,仍处于安全的生命周期区间,例如下面的代码:
我们尝试在onStart()中执行一段耗时操作再开启监听,但是执行耗时操作期间无法Activity
是否已经处于onStop()了,此刻我们就可以使用isAtLeast(state:State)
来判断耗时操作结束后的生命周期状态:
可见,「Lifecycle」库确实解决了生命周期只有事件没有状态的问题,开发者可以轻易获取当前的生命周期所处的阶段。
# 2.2.2、LifecycleOwner (opens new window)
首先,我们看看它的源码:
非常的简单,只是给实现者对外提供一个获取Lifecycle
的入口,为什么要这样设计呢?还记得Lifecycle
吗,它并不是一个接口而是一个抽象类,在Jvm中是单继承的,因此不太可能会让带有生命周期的组件直接继承Lifecycle
抽象类。
因此在实际使用中,带有生命周期的组件和Lifecycle
是包含的关系,即下图的情况:
为什么谷歌的开发人员要如此奇怪呢,让
Lifecycle
变成接口,让Activity实现接口不一样能让组件访问到Lifecycle
吗?先别急,
Lifecycle
的具体实现我们还没看,等到那一节将会解答这个疑问。
总结:LifecycleOwner
只是一个简单的对外提供访问Lifecycle
的接口。
# 2.2.3、LifecycleObserver (opens new window)
此处就不放代码了,因为这是一个空接口,作用是将其实现者变成一个生命周期的观察者。
其本身不起作用,业务中我们通常使用其子接口,例如DefaultLifecycleObserver
、LifecycleEventObserver
等,可以回去查看2.1节的MyListener
实现了DefaultLifecycleObserver
之后是如何感知Activity
的生命周期的。
# 2.2.4、LifecycleRegistry (opens new window)
此类是「Lifecycle」库的核心类,也是Lifecycle
抽象类的直接实现,它的作用是管理生命周期事件的派发,但是其做了非常多的优化,例如解决了产生事件时,迭代观察者过程中可能会新增或者移除观察者,用ArrayList遍历会崩的问题、新加入的观察者如何派发事件的问题,移除观察者如何更新状态的问题等等。
这些谷歌的开发人员都帮我们解决了,只需要按下图简单配置一下即可使用:
可见,我们只需要按照上文提到的结构,在Activity
中实例化一个LifecycleRegistry
,然后在合适的生命周期回调中派发响应的事件,所有监听当前Activity
生命周期的组件就可以获取到当前Activity
的生命周期了。
**需要注意的是:**上述代码仅仅是为了为你展示Lifecycle
是如何实现生命周期事件派发的,实际使用中,并不需要为Activity
手动派发事件,ComponentActivity
、AppcompatActivity
实际上已经配置好了派发逻辑,开发中直接获取Lifecycle
即可。
下面即将深入LifecycleRegistry
的源码层面探究一下它的原理,但是需要注意的是,本文章的目的并不是让读者100%搞懂源码中每一行代码的运行逻辑,因为这违背了本系列文章的初衷——让读者能够在对库有足够充足的了解下开发,同时笔者也没有100%搞懂源码每一行的逻辑。
如果读者非常有钻研精神,可以看一下这个博主的文章,他对LifecycleRegistry
的源码做了非常详细的讲解:【Jetpack】学穿:Lifecycle → 生命周期 (原理篇) - 掘金 (juejin.cn) (opens new window)
下面我们看看LifecycleRegistry的代码脉络:
笔者省去了绝大部分和业务无关的代码,只保留了最核心最精华的代码,其实被移除掉的代码都是为了解决前文中提到的“遍历过程中增删列表”、”新加入的观察者如何派发事件“等细枝末节的问题,与本文主题关系不大。
可以看到,LifecycleRegistry
本质上就是一个强化版的观察者模式的设计,添加观察者(observer)、遍历派发事件的模式。
还记得上文提到的一个小问题吗,为什么LifecycleOwner
不直接设计成接口而是以成员变量的方式挂载在对应的生命周期组件里面呢?通过LifecycleRegistry
的源码我们可以看到,LifecycleOwner
被以弱引用的方式存放着的,也就是说处理生命周期事件派发的LifecycleRegistry
并不会直接引用LifecycleOwner
,可以认为是谷歌的开发人员是为了防止产生内存泄漏而故意设计的。
# 2.2.5、小总结
我们已经依次浏览了「Lifecycle」库中的四个最核心的组件,他们的关系如果你已经搞混了,笔者再次通过一段极简的代码的方式来强化读者对他们的理解:
关于四个核心组件的总结:
Lifecycle
描述的是存放和管理生命周期的容器LifecycleRegistry
是Lifecycle
的实现类LifecycleObserver
是观察生命周期变化的监听器LifecycleOwner
是对外提供Lifecycle
的提供者。
# 3、谷歌眼中的Lifecycle
# 3.1、ComponentActivity (opens new window)
此类是谷歌官方基于Activity开发的子类,其集成了许多Jetpack库的核心功能,其中就包括了「Lifecycle」库,该类因此也实现了LifecycleOwner
接口,开发者常用的AppcompatActvity
也是该类的子类。
但是细读源码会发现,该类并没有像笔者之前展示的源码那样,直接调用LifecycleRegistry
在特定的Activity
生命周期回调中派发事件,那么该类是如何实现生命周期事件的派发的呢?下面介绍「Lifecycle」库中的另外一个关键类:ReportFragment。
# 3.1.1、ReportFragment与LifecycleCallbacks
在ComponentActivity
的onCreate()中,有一段ReportFragment.injectIfNeededIn(this)
的代码,这个就是实现了生命周期事件派发的核心类。
接下来让我们走进ReportFragment
的源码,正如前文所述,文章并不会阐述每一行代码的原理,而是抓住主要的脉络,隐藏了和主脉络无关的代码,但是剩余的代码量仍然挺多,读者不必对大量的代码感到恐慌,因为文章会逐一解释:
可见,ReportFragment
做的事非常简单,就是在其生命周期的各个阶段上报生命周期事件,因为Fragment
的生命周期和Activity
在绝大部分是保持一致的(特殊的如onCreate()
除外,不过也有onActivityCreated()
、onActivityPostCreated()
等可以感知Activity
生命周期的函数),谷歌的开发人员于是就利用ReportFragment
作为监听Activity
生命周期的工具,你可以看到这个Fragment是没有UI的,这也间接证明了它的任务并不是展示一个UI而仅仅是为了监听生命周期。
让我们回到injectIfNeedIn(),可以清楚的看到这里做了一个版本判断,如果大于api版本大于29,则使用LifecycleCallbacks
做一个注册的逻辑,这是怎么回事呢?
在我提到ReportFragment
是作为一个生命周期监听者而不是一个展示UI的模块的时候,你也许就已经隐隐约约闻到一种非常奇怪的味道。由于安卓源码设计的缺陷(只对外提供了回调方法而没有提供回调监听注册),开发者对待这一问题必须考虑向下兼容,因此他们选择了源码中已经存在的、可以监听Activity
的生命周期的Fragment
,但是在api 29之后,Activity原生自带了生命周期的回调监听注册,因此一旦检测到api大于或者等于29,ReportFragment
的作用就形同虚设了,因为广播生命周期的事件的任务已经转移给Activity
自带的生命周期回调来实现了。
你也许还会担心,现在有ReportFragment
和Activity
自带的生命周期回调两种方式了,会不会导致一个事件被广播两次呢?其实不用担心,广播的时候已经做了排除了,只有api小于29的情况下,ReportFragment
才会生效。
# 3.2、Fragment
Fragment
本身的生命周期和Activity
没有很大的差异,依然是内置LifecycleRegistry
然后在合适的生命周期回调中广播生命周期事件的一套,但是值得注意的是:
当
Fragment
被FragmentManager
管理时,例如执行replace()
事务中,逻辑上当前的Fragment
只是被另外一个同类所替换了,它并没有真的被销毁(因为待会还有重新回来的机会),因此该Fragment
并不会执行onDestroy()
,然而由于内存上的考量,不可见的Fragment
的View
理应被回收,因此View
会被销毁。换句话说,
Fragment
不可见之后,它的状态会保存起来,但是其View
会被销毁,待会再次可见的时候,会根据其状态再一次执行onCreateView()。
上述机制导致了一个问题:Fragment
的生命周期和其对应的View
的生命周期在实质上是不对等的,然而实际开发中感知生命周期大多数是为了与UI进行互动,这也导致了开发者单纯监听Fragment
的生命周期已经不能够满足开发上的需求了。
下面这张来源于谷歌官方开发者文档的图片很好的诠释了Fragment
和它的View
的生命周期关系:
假如一个Fragment
正在栈顶,他会处于Resumed的阶段,但是被replace之后(或者说是进入了回退栈),它会进入Created阶段,此刻View
被销毁,View
会进入Destroyed阶段,但是Fragment
重回栈顶的时候,Fragment
会从Created再次回归到Resume,而View
会从Destroyed重回Resumed状态。
换句话说,在Fragment
的生命周期中,它的View可能会反复的从Destroyed到Resumed之间移动(即不断地销毁与创建)
谷歌为了缓解这个问题,给Fragment
的View
单独添加了一套生命周期,我们可以通过代码看到端倪:
可以看到,在Fragment
执行performCreateView()
的时候,会初始化View
的Lifecycle,两者的生命周期事件是单独通知的。
- 如果开发者想访问
Fragment
的生命周期,在Fragment
中访问lifecycleOwner
即可。 - 如果开发者想访问
Fragment
的View
的生命周期,在Fragment
中访问viewLifecycleOwner
即可。
# 3.3、ViewTreeLifecycleOwner
在上述的代码中,能够直接访问Activity
、Fragment
的Lifecycle的只能是它们的类中,而很多需要访问生命周期的地方往往是一些View
中,例如要在View
中监听其父组件生命周期,然而View
的父控件有非常多,包括了Activity
、Fragment
甚至是Dialog
乃至更多,要想获取父组件的生命周期,只能做类型判断+类型强转的工作,这样就极大的限制了View
的使用范围:
为了缓解,谷歌的开发人员提出了一种叫ViewTreeLifecycleOwner
的设计,其实这个东西并没有什么神秘的,让我们直接看看源码:
通篇只有两个View
的扩展函数,第一个函数的意义是给对应的View
绑上一个LifecycleOwner
,第二个函数的意义是不断往上查找父控件,直到查出之前绑定的LifecycleOwner
。
这段源码的作用挺简单的,也就是说只要给某个顶层的控件提前绑好了LifecycleOwner
,那么他下辖的所有子View
都可以通过往上查找的方式来找到LifecycleOwner
,不得不说谷歌的开发人员真的是太厉害了,在简陋的基础下做出了非常强大的功能。
那么下面的问题是:LifecycleOwner
的绑定发生在哪里呢?
# 3.3.1、Activity中的绑定
Activity的直接子类ComponentActivity
、AppcompatActivity
均自动完成了绑定的工作,我们以ComponentActivity
为例看看相关的绑定代码:
可见在ComponentActivity
的setContentView
被执行时,会将Activity
的ViewLifecycleOwner
绑定其所在的Window
的DecorView
中,我们都知道Activity
下面的所有View
都是DecorView
的子View,因此它们都可以直接通过谷歌开发人员提供的扩展函数直接访问到最顶层的Activity
的Lifecycle
。
# 3.3.2、Fragment中的绑定
和Activity
类似,Fragment
也采用了几乎一致的绑定方式,只不过是将Lifecycle
绑定在了Fragment
的View
之上:
# 3.3.3、Dialog中的绑定
默认的Dialog
和Activity
是不支持ViewTreeLifecycleOwner
的,因此谷歌的开发人员重新继承实现了一个新的Dialog子类:ComponentDialog
,其中的绑定大同小异,简单看下源码即可了解:
看来和Activity
一样,把LifecycleOwner
绑定在了DecorView
中。
# 3.3.4、意义与总结
那么谷歌的开发人员费尽心思的为以上的组件绑定ViewTreeLifecycleOwner
有何用意呢?意义可大了,由于消除了组件之间的差异(均是通过View
往上查找父控件直到找到LifecycleOwner
的模式),我们不用在乎当前的View
是在哪个控件中,都是统一通过findViewTreeLifecycleOwner()
来获取最顶层控件的生命周期。
例如下面的自定义View的代码,无论在上述哪个控件中都可以用:
可见,开发者只需要关注生命周期本身,不再需要担心不同组件之间的差异了。
# 4、结语
安卓原生的生命周期设计只能说是毛坯房都算不上的水平,然而通过「Lifecycle」库的加持之后,开发者可以轻松访问组件的生命周期,让开发业务更加的合理与安全。
作为开发者的你,应该逐渐将重写生命周期函数的方式逐渐过渡到「Lifecycle」的开发方式中来,在一些工具类亦或者其他业务类中,你也可以使用「Lifecycle」辅助强化与生命周期相关的业务。
如果文章帮助到你,请为笔者点一个👍🏻支持一下,你的鼓励是我前进的动力!
# 扩展内容:
# 1、MaxLifecycle
在ViewPager
和ViewPager2
这类可以操作Fragment
的框架中,存在着一个重要的特性就是离屏缓存,即offscreenPageLimit
,这个特性的作用是在当前可见的元素两侧,缓存多少不可见的元素。
这个机制有利于ViewPager
在滑动过程中保持丝滑,因为当元素还未可见的时候,就提前加载并添加到视图树中。
下图展示的就是离屏缓存为1的情况,屏幕左侧的Fragment
提前被加载。
但是这个机制却导致Fragment
的生命周期与其可见度产生了冲突。
对于Activity
来说,当它进入到Resumed状态后,开发者可以轻易认为当前的Activity
对于用户是「可见」的。但是对于处于ViewPager
的离屏缓存区域的Fragment
来说,虽然他们被加载出来并进入了Resumed
状态,但是实际上用户是看不见这些Fragment
(上图的黄色Fragment
)。这就导致了生命周期与可见性产生了不同步的问题,毕竟Resumed
的定义就是可以看见并可以操作的意思。
下面简单用一个例子来演示这个问题:
上面的代码中虽然代码量不少,但是逻辑极其简单,就是构造一个ViewPager
,同时带有5个Fragment
,我们为每一个Fragment
添加生命周期监听。
当初次进入的时候,ViewPager
处于第1个位置,但是我们看一下日志:
当前位置:0进入了ON_CREATE
当前位置:0进入了ON_START
当前位置:1进入了ON_CREATE
当前位置:0进入了ON_RESUME
当前位置:1进入了ON_START
当前位置:1进入了ON_RESUME
可以看见的是,虽然当前只有第1个位置的Fragment
可见,然而第2个位置的Fragment
却进入了onResume
,这个就是上面提到的离屏缓存的机制导致的。这导致在业务上我们没办法单纯通过Fragment
的生命周期来判断是否被用户可见。
对于这个问题,谷歌为Fragment
增加了一个setUserVisibleHint(Boolean)
的方法来解决上述的问题。开发者可以手动通过调用这个方法来修改Fragment
的可见度。需要注意的是,这个机制只是一个简单的「标记」,它并不能实际决定Fragment
是否对于用户可见。
于是ViewPager
在页面跳转的时候,会主动去修改那些Fragment
的UserVisibleHint
,开发者则可以根据这个值来判断Fragment
是否可见。
长期以来,开发者确实能够使用这个机制去解决ViewPager
场景下的可见度问题。
但是复盘之后可以发现,这个简单的标记位也有不少缺陷:
- 缺乏统一的访问入口,子控件难以取值
- 与生命周期原本的设计产生偏移,增加维护难度
- 难以监听
于是谷歌在新版的Fragment
中,对源码进行了优化,增加了一个「MaxLifecycle」的机制。
一直以来,Fragment
的生命周期都是没法直接设置的,只能通过FragmentManager
对Fragment
进行操作来间接控制,虽然「MaxLifecycle」也没法直接控制,但是给Fragment
的生命周期控制增加了一层约束。
如果理解「MaxLifecycle」呢?简单来说就是最大的生命周期。
还记得生命周期是有大小的吗?它们从Created、Started、Resumed依次增大。
如果开发者把某个Fragment
的最大生命周期设置为Started,这意味着此Fragment
永远不会到达Resumed,哪怕它满足了原本应该到达Resumed的条件,而是最多停留在Started。
我们看看如果使用「MaxLifecycle」这个机制的简单使用:
和传统的FragmentManager
一样,开发者只需要在commit之前,额外调用setMaxLifecycle()
即可。
依照上图的设置之后,哪怕新添加的Fragment
马上可见了,他的生命周期也只会停留在Started。
MaxLifecycle对解决ViewPager导致的Fragment的可见性问题有什么实际性意义呢?
意义非常重大,我们复盘一下导致这个问题的原因:
- 当
Fragment
被ViewPager
通过FragmentManager
加入到视图树中时,在可见的Fragment
两侧(实际就是屏幕意外)会因为离屏缓存的机制存在「用户不可见,但是进入了Resumed」的Fragment
。
因此问题的根源就是Fragment
在用户仍为看到它的时候就”提前“进入了Resumed,如果我们能够让它不可见的时候,限制在Started,那问题不就解决了么?
实际上ViewPager
就是这样做的,在新版ViewPager
中适配了这个机制:
ViewPager
的常用Adapter之一FragmentStatePagerAdapter
可以看到,新增了一个构造函数,可以传入Behavior:
BEHAVIOR_SET_USER_VISIBLE_HINT
:原版Adapter的行为,Fragment
在用户可见与不可见状态切换时,将调用Fragment
的setUserVisibleHint(Boolean)
来修改可见标记。BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
:新版行为,使用setMaxLifecycle(Fragment,Lifecycle.State)
来限制Fragment
的最大生命周期。当Fragment
处于ViewPager
的不可见状态时,最大生命周期限制为Started。
我们从代码中可以看出端倪:
setPrimaryItem()
是PagerAdapter
切换Item的核心方法,每次切换Item的时候,Adapter先是「将之前显示的Fragment
设置为不可见」,然后「将即将显示的Fragment
设置为可见」。
但是设置的手段却不一样,从代码中看出,不同的Behavior有不同的设置手段,新版是通过设置MaxLifecycle,而旧版则是之前的VisibleHint。
因此,开发者只需要在使用ViewPager的时候,构建Adapter时将Behavior改为新版即可。
只有ViewPager需要单独设置,对于ViewPager2来说并不需要额外设置,它默认就是新版的MaxLifecycle机制。
回到一开始的代码,调用FragmentStatePagerAdapter
的第二个构造函数,将Behavior改为新版。
可以预期的是,在设置后,处于不可见状态的Fragment
将不会进入Resumed状态,大致情况如下图所示:
日志如下:
当前位置:0进入了ON_CREATE
当前位置:0进入了ON_START
当前位置:1进入了ON_CREATE
当前位置:1进入了ON_START
当前位置:0进入了ON_RESUME
初始状态下,用户看到的是第1个Fragment
,由于离屏缓存的机制,第2个Fragment
也被加载并纳入视图树了,但是由于它被ViewPager
设置了MaxLifecycle为Started,因此ViewPager
在没有发生移动的情况下,它的生命周期被限制在了Started。
滑动ViewPager至第2页,我们再观察日志:
当前位置:2进入了ON_CREATE
当前位置:2进入了ON_START
当前位置:0进入了ON_PAUSE
当前位置:1进入了ON_RESUME
第1个Fragment
由于不可见,进入了Paused,第2个Fragment
由于滑动导致可见了,从Started变为了Resumed。
为什么第3个Fragment
也会发生生命周期变化呢,其实就是离屏缓存在起作用,同时MaxLifecycle也发挥了作用,此刻它的生命周期被限制在了Started。
在使用了MaxLifecycle之后,开发者可以统一使用生命周期来管理Fragment
的首次加载,代码如下:
总结:自此关于MaxLifecycle的作用已经全部讲解完毕了,通过设置MaxLifecycle开发者可以避免Fragment出现生命周期与实际可见不一致的问题,而官方提供的ViewPager已经默认实现,非常的方便。