沉思篇-剖析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命令,因此现在木马的客户端也都是做成非常直观的界面形式,方便操作.本篇文章会从客户端与服务器端 ...
随机推荐
- hyperf协程免费查询快递物流
https://blog.csdn.net/lin5188230/article/details/124920474
- 解决 ssh 找不到对应主机密钥类型
解决办法 如果最近升级到了 openssh 8.8 版,你会发现连接某些之前连接得好好的服务器突然无法连接: Unable to negotiate with x.x.x.x port 2222: n ...
- ACM-学习记录-素数筛
前言 近期发现我NEFU低年级组校赛题目只有模拟+数论,恰恰都是我最不会做的,数论方面反反复复用到的就是素数筛,特在此记录一下,闲来无事自己翻阅当作复习复习,以免被到时候一道题都做不出来菜到巨佬们. ...
- ICMP隐蔽隧道攻击分析与检测(二)
• ICMP协议流量特征分析 一.ASCII与HEX对照转换表 二.ICMP正常流量分析 经常使用的ping命令就是基于ICMP协议,Windows系统下ping默认传输的是:"abcdef ...
- 多台服务器之间配置ssh免密登录
需求:假设有N台服务器,N台服务器之间都需要配置相互间免密登录 步骤1:在一台服务器上安装ansible yum -y install epel-release && yum -y ...
- Distinctive Image Features from Scale-Invariant Keypoints 论文解读
Distinctive Image Features from Scale-Invariant Keypoints 论文解读 著名的SIFT local feature提取方法 Scale-space ...
- Django之时区修改
修改时区方法 环境:windows10 修改settings.py文件,修改TIME_ZONE和USE_TZ参数 TIME_ZONE = 'Asia/Shanghai' USE_TZ = False
- 6.Get和Post
1.概述 URL是一个资源描述符,一个URL用于描述一个网上资源 Get用于获取/查询资源信息,Post用于更新资源信息 2.联系和区别 2.1.Get后退刷新无害,Post需要重新提交: 2.2.G ...
- docker安装python+nginx
一个容器安装python和nginx dockerfile FROM centos:7.9.2009 USER root RUN yum install gcc openssl-devel bzip2 ...
- Go语言实现基于HTTP的内存缓存服务
所有的缓存数据都存储在服务器的内存中,因此重启服务器会导致数据丢失,基于HTTP通信会将使开发变得简单,但性能不会太好 缓存服务接口 本程序采用REST接口,支持设置(Set).获取(Get)和删除( ...