安卓现代化开发系列——从生命周期到Lifecycle【扩展包1已更新】-1

简介: 安卓现代化开发系列——从生命周期到Lifecycle【扩展包1已更新】

由于安卓已经诞生快二十载,其最初的开发思想与现代的开发思想已经大相径庭,特别是Jetpack库诞生之后,项目中存在着新老思想混杂的情况,让许多的新手老手都措手不及,项目大步向屎山迈进。为了解决这个问题,开发者必须弄懂新旧两种开发模式,这就是《安卓现代化开发系列》诞生的意义,本系列并不会包含隐晦难懂的代码,一切的文字都是以理解本质为主,起到一个抛砖引玉的作用。

更新日志:

扩展包1——MaxLifecycle已更新至文章末尾


生命周期的前世今生


1.1、前世——初识篇

天地初开,一切皆为混沌的时代,安卓宇宙中诞生了名为Activity(活动)的组件,Activity 是Android应用中最关键的组件,一个Activity 通常对应的是App的一个页面,当手机使用者在不同的页面之间导航的时候,新的Activity 会诞生,同时也会在特定的时候销毁。一个页面的诞生之初到它销毁的这段时间,名为「生命周期」。

理解并掌握生命周期是每一个Android修炼者的必修功力,因为生命周期的每一个阶段均代表Activity 处于不同的状态之中,一旦错误处理生命周期周期,修炼者轻则内伤残疾(手机耗电过多,丢失信息),重则走火入魔(程序崩溃)。

关于生命周期,江湖中一直流传着一张「Activity生命周期总览图」,但个中奥秘,却鲜为人知,因此少有人能够修炼到最高境界:

image.png

由图可见,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被销毁前触发,此回调的有两种情况被调用:
  1. 用户手动关闭Activity(按返回键)或者系统主动关闭Activity(一般是App进程因内存不足被销毁,导致Activity也被销毁)。
  2. 配置变更(设备旋转、语言切换等)。

简单来说,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()的区别:

image.png

  • onStop() :当一个Activity可见但是没有获取到焦点的状态变为完全不可见的状态时就会触发此回调,按照上文类比,这种情况通常可以理解为:电脑桌面上的一个被遮挡的窗口被最小化了

1.2.3、onResume()、onPause()

  • onResume() :当Activity可见但是没有获取焦点的状态变成可见同时获取焦点的状态时,触发此回调,同样按照电脑系统的角度来理解,这种情况通常可以理解为:电脑桌面上的一个被遮挡的窗口此刻被用户交互了
  • onPause() :当Activity从“可见同时获取焦点”的状态变成可见但是没有获取焦点的状态时,触发此回调,同样同样按照电脑系统的角度来理解,这种情况通常可以理解为:电脑桌面上的一个正在被用户交互的窗口,由于用户操作了其他窗口,导致当前的窗口被遮挡了,也因此失去了焦点

1.3、前世——总结篇

我们从电脑系统的窗口去理解Activity的生命周期:

  1. 启动一个程序的时候,程序就会在电脑桌面上创建一个窗口,创建的那一瞬间(通常会很快,可能不需要1秒)就相当于ActivityonCreate()
  2. 创建完成后,窗口就可以被用户所看见了,被用户看到的那一瞬间就相当于ActivityonStart()
  3. 通常来说,一个新启动的程序会自动获得焦点并可被用户交互,因此onStart()之后,窗口会被置顶到顶层,这一瞬间就相当于ActivityonResume()
  4. 当用户选择其他窗口时,之前交互的窗口并不会消失,而是会失去焦点并被用户最新交互的窗口所遮挡,这一瞬间就相当于ActivityonPause()
  5. 当用户最小化窗口时,窗口就会进入后台(并不是销毁)而且并不能被用户所看见,这一瞬间就相当于进入了ActivityonStop()
  6. 当用户关闭程序亦或者电脑内存不足时,程序被销毁,窗口同时也被销毁了,这一瞬间就相当于进入了ActivityonDestroy()

一个窗口当然可以失去焦点后重新获取焦点,因此onPause()onResume()可能在生命周期中多次被执行,同理窗口也可以最小化之后重新最大化,onStart()onStop()也可能在生命周期中多次被执行。只不过对于移动设备来说,几乎不存在页面失去焦点后又重新获得焦点的情况,因为移动设备的页面绝大多数情况都是一个页面可被用户交互,被挡住的页面完全不可见,即等价于电脑系统中只存在一个最大化的页面,所以移动设备的Activity的生命周期通常只会在onStart()onStop()两者之间流转(当然,仍然会遵循onStart()->onResumt()->onPause()->onStop()的顺序)。

而一个窗口只能被创建和销毁一次,因此在Activity的生命周期中,onCreate()和onDestroy()只会被调用一次。

上文中提到,原生的Android生命周期设计中,只提供了进入某个生命周期状态的回调,并没有提供具体的状态的定义,例如onCreate()与onStart()之间的状态叫什么呢,官方的文档提到了这个叫“已创建”的状态,然而这只存在于文本性的文档中,这在代码中并不存在,只能作为一种“共识”的定义。这也为开发者之间沟通生命周期带来了极大的困扰。

2.1、今生——初识篇

image.png

经历漫长的混沌时代之后,Jetpack携带着「Lifecycle」正式进入到了Android的世界中,「Lifecycle」为千千万万的Android修炼者带来了福音,因为它比起传统的基于回调的方式来感知生命周期的方式有以下的优点:

  1. 提出了「生命周期状态」的概念,弥补了安卓传统的生命周期只有事件没有状态的缺陷
  2. 将生命周期管理从页面(如Activity和Fragment)脱离,将生命周期监听的职责转移到组件中,降低页面与组件的耦合度。

为了让读者更加清晰使用「Lifecycle」与不使用它之间的区别,这里使用两个代码案例来对比:

  • 首先,定义一个常见的基于回调的监听类,每秒钟会对外广播一次字符串。

image.png

  • Activity中的onCreate()阶段初始化监听,然后在onStart()中开启监听,在onStop()中关闭监听,这样的好处是当页面不可见的时候不会浪费手机性能。
    image.png

以上便是传统安卓开发中最直接也是最常见的一种根据生命周期来实现监听的方式,让我们分析一下这种方式的缺点:

  1. 真实业务开发中,同一页面中往往存在大量的生命周期监听需求,Activity等生命周期组件会同时管理大量的组件,让代码难以维护。
  2. 代码缺乏一致性,需要监听生命周期的组件存在许多模板代码。试想一下,一个需要在onStart()启动,在onStop()关闭的、同时在项目中大量存在的组件,某天需要它在onResume()做一些操作,那将会导致灾难,因为需要每一处使用它的代码中增加onResume()的修改,一旦遗漏这个修改将会导致不可预知的bug。
  3. 无法获取实时的生命周期状态。假设在onStart()的阶段,需要执行一个网络请求或者其他耗时操作之后再调用listener.start()的场景下,无法保证此刻页面仍然处于可见的状态,开发者也无法获取「当前所处状态」来避免不可见的时候仍然调用listener.start()(这个缺陷上文已经提到,原生安卓生命周期只提供了生命周期事件而没有生命周期状态)。

让我们看一看使用了「Lifecycle」库之后的生命周期是如何实现监听的:

image.png

我们让需要监听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」库中补全了状态,下图中阐述了事件与状态的关系:

image.png

根据「Lifecycle」库的定义,一个生命周期状态的起点是「Initialized」,终点是「Destroyed」,当发生生命周期事件时,生命周期状态就会发生移动,包括状态提升状态下降

我们把状态从Initialized到Resumed当做一个从小到大的状态,如果状态值变小了,则称为状态下降,反之则为状态提升

初步的定义有了,让我们把视角聚焦于Lifecycle类的源码:

image.png

可以看到,Lifecycle类的设计基本遵循生命周期事件与状态图例,一个Lifecycle只有2个核心功能:

  1. 缓存当前的生命周期状态(currentState)。
  2. 添加与移除生命周期观察者。

上述代码中,对EventState的部分代码进行了省略,下面展开讲解:

image.png

首先是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

image.png

State的代码非常简单甚至不用一丝的省略,除了枚举值外仅有一个方法:isAtLeast(state:State) ,此方法的含义是用于判断当前的状态是否大于或等于目标值的状态。

如何理解呢?还记得上文提到的吗,状态是有大小的:

我们把状态从Initialized到Resumed当做一个从小到大的状态,如果状态值变小了,则称为状态下降,反之则为状态提升

因此对于生命周期的状态而言,Created是比Initialized的,isAtLeast(state:State)的含义就是判断生命周期是否比某个预期值“走的更远”了,如果一个行为可以在组件创建后被执行,那么换句话说,只要生命周期的状态大于或者等于Created即可。

上文中提到,原生的生命周期回调无法实时获取生命周期所处的状态,一旦在生命周期回调方法中执行一些耗时操作,就无法耗时操作结束后,仍处于安全的生命周期区间,例如下面的代码:

image.png

我们尝试在onStart()中执行一段耗时操作再开启监听,但是执行耗时操作期间无法Activity是否已经处于onStop()了,此刻我们就可以使用isAtLeast(state:State)来判断耗时操作结束后的生命周期状态:

image.png

可见,「Lifecycle」库确实解决了生命周期只有事件没有状态的问题,开发者可以轻易获取当前的生命周期所处的阶段。

2.2.2、LifecycleOwner

首先,我们看看它的源码:

image.png

非常的简单,只是给实现者对外提供一个获取Lifecycle的入口,为什么要这样设计呢?还记得Lifecycle吗,它并不是一个接口而是一个抽象类,在Jvm中是单继承的,因此不太可能会让带有生命周期的组件直接继承Lifecycle抽象类。

因此在实际使用中,带有生命周期的组件和Lifecycle是包含的关系,即下图的情况:

image.png

为什么谷歌的开发人员要如此奇怪呢,让Lifecycle变成接口,让Activity实现接口不一样能让组件访问到Lifecycle吗?

先别急,Lifecycle的具体实现我们还没看,等到那一节将会解答这个疑问。

总结:LifecycleOwner只是一个简单的对外提供访问Lifecycle的接口。

2.2.3、LifecycleObserver

此处就不放代码了,因为这是一个空接口,作用是将其实现者变成一个生命周期的观察者

其本身不起作用,业务中我们通常使用其子接口,例如DefaultLifecycleObserverLifecycleEventObserver等,可以回去查看2.1节的MyListener实现了DefaultLifecycleObserver之后是如何感知Activity的生命周期的。

2.2.4、LifecycleRegistry

此类是「Lifecycle」库的核心类,也是Lifecycle抽象类的直接实现,它的作用是管理生命周期事件的派发,但是其做了非常多的优化,例如解决了产生事件时,迭代观察者过程中可能会新增或者移除观察者,用ArrayList遍历会崩的问题、新加入的观察者如何派发事件的问题,移除观察者如何更新状态的问题等等。

这些谷歌的开发人员都帮我们解决了,只需要按下图简单配置一下即可使用:

image.png

可见,我们只需要按照上文提到的结构,在Activity中实例化一个LifecycleRegistry,然后在合适的生命周期回调中派发响应的事件,所有监听当前Activity生命周期的组件就可以获取到当前Activity的生命周期了。

**需要注意的是:**上述代码仅仅是为了为你展示Lifecycle是如何实现生命周期事件派发的,实际使用中,并不需要为Activity手动派发事件,ComponentActivityAppcompatActivity实际上已经配置好了派发逻辑,开发中直接获取Lifecycle即可。

下面即将深入LifecycleRegistry的源码层面探究一下它的原理,但是需要注意的是,本文章的目的并不是让读者100%搞懂源码中每一行代码的运行逻辑,因为这违背了本系列文章的初衷——让读者能够在对库有足够充足的了解下开发,同时笔者也没有100%搞懂源码每一行的逻辑。

如果读者非常有钻研精神,可以看一下这个博主的文章,他对LifecycleRegistry的源码做了非常详细的讲解:【Jetpack】学穿:Lifecycle → 生命周期 (原理篇) - 掘金 (juejin.cn)(opens new window)

下面我们看看LifecycleRegistry的代码脉络:

image.png

笔者省去了绝大部分和业务无关的代码,只保留了最核心最精华的代码,其实被移除掉的代码都是为了解决前文中提到的“遍历过程中增删列表”、”新加入的观察者如何派发事件“等细枝末节的问题,与本文主题关系不大。

可以看到,LifecycleRegistry本质上就是一个强化版的观察者模式的设计,添加观察者(observer)、遍历派发事件的模式。

还记得上文提到的一个小问题吗,为什么LifecycleOwner不直接设计成接口而是以成员变量的方式挂载在对应的生命周期组件里面呢?通过LifecycleRegistry的源码我们可以看到,LifecycleOwner被以弱引用的方式存放着的,也就是说处理生命周期事件派发的LifecycleRegistry并不会直接引用LifecycleOwner,可以认为是谷歌的开发人员是为了防止产生内存泄漏而故意设计的。

2.2.5、小总结

我们已经依次浏览了「Lifecycle」库中的四个最核心的组件,他们的关系如果你已经搞混了,笔者再次通过一段极简的代码的方式来强化读者对他们的理解:

image.png

关于四个核心组件的总结:

  1. Lifecycle描述的是存放和管理生命周期的容器
  2. LifecycleRegistryLifecycle的实现类
  3. LifecycleObserver是观察生命周期变化的监听器
  4. LifecycleOwner是对外提供Lifecycle的提供者。


安卓现代化开发系列——从生命周期到Lifecycle【扩展包1已更新】-2

https://developer.aliyun.com/article/1398230?spm=a2c6h.13148508.setting.15.e4774f0eeznVNH


更多好文:

安卓现代化开发系列——从状态保存到SavedState

安卓现代化开发系列——传世不朽ViewModel

相关文章
|
2天前
|
安全 Android开发 开发者
探索安卓开发的未来:Kotlin的崛起与Flutter的挑战
在移动开发的广阔天地中,安卓平台始终占据着举足轻重的地位。随着技术的不断进步和开发者需求的多样化,Kotlin和Flutter成为了改变游戏规则的新玩家。本文将深入探讨Kotlin如何以其现代化的特性赢得开发者的青睐,以及Flutter凭借跨平台的能力如何挑战传统的安卓开发模式。通过实际案例分析,我们将揭示这两种技术如何塑造未来的安卓应用开发。
17 6
|
2天前
|
人工智能 Android开发 iOS开发
安卓与iOS开发:平台选择的艺术
在移动应用开发的广阔天地里,安卓和iOS两大操作系统各占半壁江山。本文将深入探讨这两个平台的开发环境、工具及市场趋势,帮助开发者在选择适合自己项目的平台时做出更明智的决策。通过比较各自的优势与局限,我们不仅能更好地理解每个系统的核心特性,还能洞察未来技术发展的脉络。无论你是刚入行的新手还是资深开发者,这篇文章都将为你提供有价值的参考和启示。
14 5
|
1天前
|
开发工具 Android开发 iOS开发
安卓与iOS开发:一场操作系统的较量
在数字时代的浪潮中,安卓和iOS这两大操作系统如同海上的两艘巨轮,各自承载着不同的使命与梦想。本文将深入浅出地探讨这两个系统在开发领域的异同,从用户体验、开发工具、市场趋势等多个维度进行比较分析。通过这场技术的较量,我们可以更好地理解每个系统的优势与局限,以及它们如何影响我们的日常生活和工作。
|
3天前
|
移动开发 开发工具 Android开发
安卓与iOS开发:平台差异及其对开发者的影响
在移动开发的大潮中,安卓和iOS两大阵营各领风骚。本文将探讨这两个平台的关键差异,包括开发环境、编程语言、用户界面设计、应用分发以及商业模式等方面。通过比较分析,我们旨在为开发者提供一个清晰的指导,帮助他们根据项目需求和个人偏好做出明智的平台选择。同时,文章也将分享一些跨平台开发工具的使用经验,以期最大化开发效率和市场覆盖。
|
4天前
|
Java 开发工具 Android开发
安卓与iOS开发:平台选择对项目成功的影响
在移动应用开发的浩瀚宇宙中,安卓和iOS两大星系璀璨夺目,各自拥有独特的光芒。本文将穿梭于这两个平台之间,探讨它们在开发环境、用户群体、成本效益等方面的差异,以及这些差异如何影响一个项目的航向和终点。我们将从初学者的视角出发,逐步深入,揭示选择合适平台的重要性,以及如何根据项目需求做出明智的选择。无论你是即将启航的新手开发者,还是已经在这片星海中航行的老手,这篇文章都将为你提供有价值的导航信息。
14 2
|
6天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义控件
【9月更文挑战第5天】在安卓开发的海洋中,自定义控件如同一艘精致的小船,让开发者能够乘风破浪,创造出既独特又高效的用户界面。本文将带你领略自定义控件的魅力,从基础概念到实战应用,一步步深入理解并掌握这一技术。
|
7天前
|
XML Java Android开发
探索Android开发之旅:打造你的第一个应用
【9月更文挑战第4天】在这篇专为初学者设计的文章中,我们将一起踏上激动人心的Android开发之旅。从设置开发环境到实现一个简单的“Hello World”应用,每一步都充满了发现和学习。文章将引导你理解Android开发的基础知识,并鼓励你动手实践。让我们开始吧,创造你的第一款Android应用,开启技术世界的新篇章!
|
1天前
|
调度 Android开发 开发者
探索安卓开发中的新技术:Kotlin协程
【9月更文挑战第9天】本文将深入探讨Kotlin协程在安卓开发中的应用,揭示其如何优雅地处理异步任务。我们将从基础概念入手,逐步深入到实际开发场景,最后通过代码示例直观展示协程的魔力。无论你是初学者还是有经验的开发者,这篇文章都将为你打开一扇通往更高效、更简洁代码的大门。
|
3天前
|
开发工具 Android开发 iOS开发
探索安卓与iOS开发的差异:构建未来应用的关键考量
在数字时代的浪潮中,安卓和iOS这两大操作系统如同双子星座般耀眼夺目,引领着移动应用的潮流。它们各自拥有独特的魅力和深厚的用户基础,为开发者提供了广阔的舞台。然而,正如每枚硬币都有两面,安卓与iOS在开发过程中也展现出了截然不同的特性。本文将深入剖析这两者在开发环境、编程语言、用户体验设计等方面的显著差异,并探讨如何根据目标受众和项目需求做出明智的选择。无论你是初涉移动应用开发的新手,还是寻求拓展技能边界的资深开发者,这篇文章都将为你提供宝贵的见解和实用的建议,帮助你在安卓与iOS的开发之路上更加从容自信地前行。
|
4天前
|
前端开发 搜索推荐 Android开发
探索安卓开发中的自定义视图##
【9月更文挑战第6天】 在安卓应用开发的世界里,自定义视图如同绘画艺术中的色彩,它们为界面设计增添了无限可能。通过掌握自定义视图的绘制技巧,开发者能够创造出既符合品牌形象又提升用户体验的独特界面元素。本文将深入浅出地介绍如何从零开始构建一个自定义视图,包括基础框架搭建、关键绘图方法实现、事件处理机制以及性能优化策略。准备好让你的安卓应用与众不同了吗?让我们开始吧! ##