沉思篇-剖析JetPack的Lifecycle
这几年,对于Android开发者来说,最时髦的技术当属Jetpack了。谷歌官方从19年开始,就在极力推动Jetpack的使用,经过这几年的发展,Jetpack也基本完成了当时的设计目标——简单,一致,专注。而使得这一切成为可能的基石,我觉得当属架构组件了。最初架构组件作为单独的库和support库并没有太多联系,随着Jetpack的成型,给架构组件也注入了新的活力。在Jetpack库中,建构组件的使用随处可见,扮演着越来越重要的角色。今天,我就以作为架构组件基石的Lifecycle开始,尝试对架构组件进行剖析,向大家展示谷歌开发者是怎样设计一个高内聚,低耦合的库的。
前言
要说阅读源码,我觉得和把大象装进冰箱的操作是一样的,就是再大的事,得分步骤。阅读源码的第一步就是站在库开发者的角度,提炼出库需要完成的功能。明确了功能,才能针对功能进行代码设计,这也是我们阅读源码的第二步,理清实现逻辑。我们可以通过画UML图的方式帮助理解。通常来说画完UML图,源码的理解也就七七八八了。这时我们就可以走到第三步,品读实现细节。有了前两步的基础,我们就可以针对性地选取一些感兴趣的内容研读实现了,而且可以做到收放自如,指哪打哪。
那我们开始吧!
Lifecycle的源码思路
明确Lifecycle的设计目标
一句话概括,Lifecycle就是完成了生命周期感知的任务。生命周期感知是什么意思呢,就是一个类可以不依赖Activity或者Fragment的回调,正确处理状态更新的问题。那么我们为什么需要这样的一个类呢,这和我们直接在回调方法里面写逻辑有什么差别吗?为了回答这个问题,我们来设想一下这样一个场景:一个Activity需要获取用户实时的位置信息,同时需要使用相机,以往我们的做法是在Activity的某些回调里面设置位置,相机的监听及解监听。如果这时,Activity再加入其他的一些逻辑,它的代码就可能膨胀到不能忍受的地步,并且随着业务的增长,后续Activity的膨胀是不可预期的,这样是不利于调试和测试的,而且,从设计上来说,这样的设计也是脆弱的,耦合太紧了。但是我们的这些组件确确实实是需要响应生命周期回调的啊,那么怎样才能做到既能感知生命周期,又能降低耦合呢。计算机科学告诉我们这样一条真理,当找不到其他方法时,可以考虑加一层抽象。由此,Lifecycle诞生了。这就是Lifecycle的目标,很纯粹,就是生命周期感知,就是把专业的事情交给专业的去做。
Lifecycle的工作流程
概括来说,Lifecycle就是完成了状态监听和状态分发的两个功能。为了完成这两个功能,Lifecycle抽象出了三个概念,也可以说是三个流程吧。
- 拥有生命周期的对象,称为LifecycleOwner,这其实只是一个接口,只要能提供Lifecycle的都可以称为LifecycleOwner,这个类主要的功能就是提供原始的生命周期事件,供后续的操作提供数据,这是第一步——状态感知;
- 拥有生命周期状态,称为Lifecycle,这个类主要的功能就是提供状态抽象和提供状态信息,这是第二步——状态更新;
- 对生命周期状态感兴趣的观察者,称为LifecycleObserver,这个类主要的功能就是对状态信息进行响应,这是第三步——状态响应。
流程很简单,看着也很清晰的,就是观察者模式。但是Lifecycle库为了完成更好的解耦和提供更多的扩展,在这三个环节上衍生出了更多的类,这也是我以Lifecycle为剖析对象的原因。我觉得一个好的库,不仅仅要能够完成库的设计目标,同时还应该保持尽可能的扩展性和可读性。在这点上,Lifecycle无疑是我们很好的榜样。接下来,我们就一起以这三个阶段为主线,逐一剖析Lifecycle是怎样完成抽象,设计,及实现的。
Lifecycle的状态感知
传统的状态感知就是重写Activity和Fragment的生命周期回调,在回调里面进行状态更新。这其实也是Lifecycle实现的基本思路,只是它将这些个回调抽象为了一个个的事件。那么怎么将生命周期转化为一个个事件呢?Lifecycle用了一个巧妙的方法,自定义了一个ReportFragment。ReportFragment作用很明确,就是监听生命周期,生成状态事件。

如图所示,本质上ReportFragment还是监听了Activity的生命周期,绑定关系就发生在injectIfNeededIn方法中。然后为了将监听到的状态传递出去给其他类使用,ReportFragment借助了dispatch方法。这里有个很巧妙的设计细节,dispatch并没有直接引用自己的组件,而是使用了Activity,但是为什么还是能将事件发送出去呢?因为这里面使用了动态类型判断及转换的操作,最终,转换成了分发器LifecycleRegistry来完成事件分发操作。从而顺利讲逻辑转到了第二阶段,状态更新。
Lifecycle的状态更新
状态更新主要的逻辑还是放在了LifecycleRegistry类里,这个类是继承了Lifecycle的。

如图所示,这个类作为Lifecycle的子类完成了被观察对象的两个功能,接收和管理观察对象。其次作为核心类它又完成了状态更新的功能。事件在这里被转化为状态,保存了下来,然后通知给自己的观察者。从类缩略图中,我们也可以看出这些方法就是为了完成这两大功能而设计的。知道了这些,状态更新的步骤也就了解了。那么顺理成章的,我们马上进入第三个步骤。
Lifecycle的状态响应
LifecycleObserver是个空接口,那么状态更新怎么做呢?这就还得从LifecycleRegistry开始看起。LifecycleRegistry在添加LifecycleObserver的时候做了包装,于是LifecycleObserver变成了多种LifecycleObserver的子类,在不同的子类里面其实都直接或者间接地继承自LifecycleEventObserver。所以最终状态是通过LifecycleEventObserver的onStateChanged方法通知给观察者的。但是我们很快发现不对劲,官方Demo是直接实现LifecycleObserver,并且只需要用注解对感兴趣的状态注册就可以了。这里完全没有体现哇。按照刚才的思路,我们还是从添加LifecycleObserver的方法开始,LifecycleObserver被包装成ObserverWithState对象,而在构造方法里面委托给了Lifecycling,所以最终的秘密藏在Lifecycling。Lifecycling里面对多种LifecycleObserver进行了处理,其中就包括了我们熟悉的注解的方式。所以,总结来看,我们通过注解定义LifecycleObserver观察者后,注册到LifecycleRegistry就会被包装成新的观察者对象。然后,在状态更新的时候,用过注解找到合适的方法来通知观察者。到这里其实整个Lifecycle的工作流程已经理清了,我根据这些整理出了一份UML图。

由UML图,我们可以直观地得出一句话的结论,LifecycleRegistry使用LifecycleOwner提供的Lifecycle,使用观察者的模式把状态传递给了LifecycleEventObserver,这就是我最开始说的三个抽象之间的联系。
品读实现细节
经过多次的品读,我发现了Lifecycle的多个小细节值得我们细细品味,其一就是Lifecycle强大的抽象。Lifecycle使用了LifecycleOwner抽象了生命周期这个概念,生命周期不一定是和Activity绑定的,用户可以自己定义自己的LifecycleOwner,定制满足自己业务需求的Lifecycle。其次LifecycleRegistry是一个很好的高内聚,低耦合的实现典范——LifecycleRegistry依赖的都是LifecycleOwner和LifecycleObserver这样的顶层接口,遵循了依赖倒置原则、LifecycleRegistry内部对LifecycleObserver的包装又委托给了Lifecycling,遵循了单一职责原则,就连LifecycleRegistry的方法都是严格遵守单一职责原则的,不得不叹服开发者对代码强大的掌控力。
当然,还有其他也很不错的实现细节,我没有一一展开,因为代码是读不完的,我们抓住我们感兴趣的就行了,有些细节就是用来忽略的,我们不得不承认这个事实。
青山不改,绿水长流,咱们下期见!
沉思篇-剖析JetPack的Lifecycle的更多相关文章
- 硬核讲解 Jetpack 之 LifeCycle 源码篇
前一篇 硬核讲解 Jetpack 之 LifeCycle 使用篇 主要介绍了 LifeCycle 存在的意义,基本和进阶的使用方法.今天话不多说,直接开始撸源码. 本文基于我手里的 android_9 ...
- v77.01 鸿蒙内核源码分析(消息封装篇) | 剖析LiteIpc(上)进程通讯内容 | 新的一年祝大家生龙活虎 虎虎生威
百篇博客分析|本篇为:(消息封装篇) | 剖析LiteIpc进程通讯内容 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁 ...
- v78.01 鸿蒙内核源码分析(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 | 百篇博客分析OpenHarmony源码
百篇博客分析|本篇为:(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析( ...
- 看过这篇剖析,你还不懂 Go sync.Map 吗?
hi, 大家好,我是 haohongfan. 本篇文章会从使用方式和原码角度剖析 sync.Map.不过不管是日常开发还是开源项目中,好像 sync.Map 并没有得到很好的利用,大家还是习惯使用 M ...
- WPF基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)
一. 摘要 首先圣殿骑士非常高兴这个系列能得到大家的关注和支持.这个系列从七月份開始到如今才第七篇,上一篇公布是在8月2日,掐指一算有二十多天没有继续更新了,最主要原因一来是想把它写好,二来是由于近期 ...
- Android官方架构组件介绍之LifeCycle
Google 2017 I/O开发者大会于近日召开,在开发者大会上谷歌除了发布了Android O等一些新产品之外,也对Android代码的架构做出了一个官方的回应. Google 2017 I/O开 ...
- Android官方架构组件介绍之LifeCycle(一)
Android官方架构组件介绍之LifeCycle 下面是官方提供的Android App开发的架构图: 从上图可以看到一些关键字:ViewModel,LiveData,Room等.其实看了上面视频的 ...
- Jetpack Compse 实战 —— 全新的开发体验
公众号回复 Compose 获取安装包 项目地址: Wanandroid-Compose 经过前段时间的 Android Dev Summit ,相信你已经大概了解了 Jetpack Compose ...
- 大数据理论篇 - 通俗易懂,揭秘谷歌《The Dataflow Model》的核心思想(一)
目录 前言 目标 核心的设计原则 通用的数据处理流程 切合实际的解决方案 总结 延伸阅读 最后 作者:justmine 头条号:大数据达摩院 创作不易,未经授权,禁止转载,否则保留追究法律责任的权利. ...
- 反病毒攻防研究第006篇:简单木马分析与防范part2
一.前言 一般来说,木马是既有客户端也有服务器端的.上次讨论的不过是一种特殊情况,毕竟不是人人都懂得DOS命令,因此现在木马的客户端也都是做成非常直观的界面形式,方便操作.本篇文章会从客户端与服务器端 ...
随机推荐
- kubernetes 设置 Master 可调度与不可调度
kubernetes 设置 Master 可调度与不可调度 语法 kubectl taint node [node] key=value[effect] [effect] 可取值: [ NoSched ...
- python之修改本地Ip地址
安装模块pip install wmi # -*- coding: cp936 -*- # # FileName: ModifyIP.py # Date : 2008-01-15 # import w ...
- 一文搞懂JavaScript数组的特性
前言 数组是几乎所有编程语言的基础语法,JavaScript因为语法特性,之前缺少一些集合类对象,对数组的使用就会更多一些,因此我们更需要理解数组知识. 然而大部分人对数组都已经非常熟悉了,所以本文将 ...
- 【深入浅出Spring原理及实战】「源码调试分析」深入源码探索Spring底层框架的的refresh方法所出现的问题和异常
学习Spring源码的建议 阅读Spring官方文档,了解Spring框架的基本概念和使用方法. 下载Spring源码,可以从官网或者GitHub上获取. 阅读Spring源码的入口类,了解Sprin ...
- Kubernetes(K8S) kubesphere 安装
安装KubeSphere最好的方法就是参考官方文档,而且官方文档是中文的. 官网地址:https://kubesphere.com.cn/ https://github.com/kubesphere/ ...
- [Opencv-C++] 4.3 数组迭代器NAryMatIterator
图像和大型数组类型 4.3 数组迭代器NAryMatIterator
- 带你简单了解Chatgpt背后的秘密:大语言模型所需要条件(数据算法算力)以及其当前阶段的缺点局限性
带你简单了解Chatgpt背后的秘密:大语言模型所需要条件(数据算法算力)以及其当前阶段的缺点局限性 1.什么是语言模型? 大家或多或少都听过 ChatGPT 是一个 LLMs,那 LLMs 是什么? ...
- selenium测试用例的编写,隐式等待与显式等待的编写
开头 用配置好的 selenium 进行一个简单的测试用例的编写,可以参考allure的美化这一遍博文 https://www.cnblogs.com/c-keke/p/14837766.html 代 ...
- 2023-03-10:YUV420P像素数据编码为JPEG图片,请用go语言实现。
2023-03-10:YUV420P像素数据编码为JPEG图片,请用go语言实现. 答案2023-03-10: 方法一.使用 github.com/moonfdd/ffmpeg-go 库,基于雷霄骅的 ...
- 2021-02-05:给定一个数N,想象只由0和1两种字符,组成的所有长度为N的字符串。如果某个字符串,任何0字符的左边都有1紧挨着,认为这个字符串达标。请问有多少达标的字符串?
2021-02-05:给定一个数N,想象只由0和1两种字符,组成的所有长度为N的字符串.如果某个字符串,任何0字符的左边都有1紧挨着,认为这个字符串达标.请问有多少达标的字符串? 福哥答案2021-0 ...