这几年,对于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抽象出了三个概念,也可以说是三个流程吧。

  1. 拥有生命周期的对象,称为LifecycleOwner,这其实只是一个接口,只要能提供Lifecycle的都可以称为LifecycleOwner,这个类主要的功能就是提供原始的生命周期事件,供后续的操作提供数据,这是第一步——状态感知;
  2. 拥有生命周期状态,称为Lifecycle,这个类主要的功能就是提供状态抽象和提供状态信息,这是第二步——状态更新;
  3. 对生命周期状态感兴趣的观察者,称为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的更多相关文章

  1. 硬核讲解 Jetpack 之 LifeCycle 源码篇

    前一篇 硬核讲解 Jetpack 之 LifeCycle 使用篇 主要介绍了 LifeCycle 存在的意义,基本和进阶的使用方法.今天话不多说,直接开始撸源码. 本文基于我手里的 android_9 ...

  2. v77.01 鸿蒙内核源码分析(消息封装篇) | 剖析LiteIpc(上)进程通讯内容 | 新的一年祝大家生龙活虎 虎虎生威

    百篇博客分析|本篇为:(消息封装篇) | 剖析LiteIpc进程通讯内容 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁 ...

  3. v78.01 鸿蒙内核源码分析(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 | 百篇博客分析OpenHarmony源码

    百篇博客分析|本篇为:(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析( ...

  4. 看过这篇剖析,你还不懂 Go sync.Map 吗?

    hi, 大家好,我是 haohongfan. 本篇文章会从使用方式和原码角度剖析 sync.Map.不过不管是日常开发还是开源项目中,好像 sync.Map 并没有得到很好的利用,大家还是习惯使用 M ...

  5. WPF基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)

    一. 摘要 首先圣殿骑士非常高兴这个系列能得到大家的关注和支持.这个系列从七月份開始到如今才第七篇,上一篇公布是在8月2日,掐指一算有二十多天没有继续更新了,最主要原因一来是想把它写好,二来是由于近期 ...

  6. Android官方架构组件介绍之LifeCycle

    Google 2017 I/O开发者大会于近日召开,在开发者大会上谷歌除了发布了Android O等一些新产品之外,也对Android代码的架构做出了一个官方的回应. Google 2017 I/O开 ...

  7. Android官方架构组件介绍之LifeCycle(一)

    Android官方架构组件介绍之LifeCycle 下面是官方提供的Android App开发的架构图: 从上图可以看到一些关键字:ViewModel,LiveData,Room等.其实看了上面视频的 ...

  8. Jetpack Compse 实战 —— 全新的开发体验

    公众号回复 Compose 获取安装包 项目地址: Wanandroid-Compose 经过前段时间的 Android Dev Summit ,相信你已经大概了解了 Jetpack Compose ...

  9. 大数据理论篇 - 通俗易懂,揭秘谷歌《The Dataflow Model》的核心思想(一)

    目录 前言 目标 核心的设计原则 通用的数据处理流程 切合实际的解决方案 总结 延伸阅读 最后 作者:justmine 头条号:大数据达摩院 创作不易,未经授权,禁止转载,否则保留追究法律责任的权利. ...

  10. 反病毒攻防研究第006篇:简单木马分析与防范part2

    一.前言 一般来说,木马是既有客户端也有服务器端的.上次讨论的不过是一种特殊情况,毕竟不是人人都懂得DOS命令,因此现在木马的客户端也都是做成非常直观的界面形式,方便操作.本篇文章会从客户端与服务器端 ...

随机推荐

  1. SaaS、PaaS、IaaS区别

    SaaS SaaS 越久,觉得它个庞大的领域 SaaS 收入的"长江流域". 传统软件像买房:什么都得自己买,价格昂贵,一般人用不起.SaaS模式就像是租赁预先装修好的共享公寓,拎 ...

  2. put、delete、post、get四种传参方式

    PUT: this.$http.put('url', { modifyTime:this.sizeForm.modifyTime, mqttRes:this.sizeForm.mqttRes, udp ...

  3. hdfs集群的扩容和缩容

    目录 1.背景 2.集群黑白名单 3.准备一台新的机器并配置好hadoop环境 3.1 我们现有的集群规划 3.2 准备一台新的机器 3.2.1 查看新机器的ip 3.2.2 修改主机名和host映射 ...

  4. 方差分析2——双因素方差分析(R语言)

    双因素方差分析(Double factor variance analysis) 有两种类型:一个是无交互作用的双因素方差分析,它假定因素A和因素B的效应之间是相互独立的,不存在相互关系:另一个是有交 ...

  5. kubernetes(k8s) 安装 Prometheus + Grafana

    kubernetes(k8s) 安装 Prometheus + Grafana 组件说明 MetricServer:是kubernetes集群资源使用情况的聚合器,收集数据给kubernetes集群内 ...

  6. Semantic Kernel 入门系列:💬Semantic Function

    如果把提示词也算作一种代码的话,那么语义技能所带来的将会是全新编程方式,自然语言编程. 通常情况下一段prompt就可以构成一个Semantic Function,如此这般简单,如果我们提前可以组织好 ...

  7. day104:MoFang:个人中心页面&flask-admin&基于faker生成仿真数据

    目录 BUG:登陆跳转并解决页面卡顿现象 1.前端显示个人中心页面 2.flask-Admin构建和配置后台运营站点管理用户信息 3.基于Faker生成仿真测试数据 BUG:登陆跳转并解决页面卡顿现象 ...

  8. day11:return关键字&全局/局部变量&函数名的使用&函数的嵌套&nonlocal关键字&locals/globals

    关键字:return 自定义函数的返回值,return可以把值返回到函数的调用处        1.return+六大标准数据类型,还有类和对象,函数        如果不定义return,默认返回的 ...

  9. 从桌面和应用内 Activity的启动流程

    1.APP还没有被打开过从桌面启动 <1>首先桌面进程会像AMS服务发送startActivity的请求,AMS从system_service中去拿----一次IPC通信 <2> ...

  10. MySQL匿名空用户名处理

    问题描述:公司漏扫发现数据库内出现空用户名及密码,需要对这些用户进行整改 1.首先出现了疑问,这些空的用户名是怎么出现的,而且不附带密码. 2.可以手动这样创建这样的用户名和密码形式么. 3.如果能这 ...