从代码设计看 Glide 之生命周期(下)
终于来到我们生命周期的最后一期了。
这一期,我们探究一下 Glide 在低版本 Android 上是如何实现生命周期监控的。
从代码设计看 Glide 系列其他文章:从代码设计看 Glide 系列
那就让我们开始吧。
正文
冷知识:实际上 Glide 的生命周期监控能力是早于 Lifecycle 库的发布的。😎
先做一个小的思考
如果在有 Lifecycle 库的情况下,我们的生命周期可以来源于 Lifecycle 给我们提供的回调。那没有这个库的情况下,我们自己要实现生命周期监控,需要怎么做呢?
可能很多聪明的同学已经想到了,我们自己设计一个 Lifecycle 的框架,等 Activity 或者 Fragment 生命周期触发的时候,通知框架不就好了吗?
我们进一步想,如果监控的范围是我们工程内所有的 Activity 或者 Fragment 呢?
如果是我们自己的工程的话,我们可以实现一个 Base 基类,让所有的 Activity 和 Fragment 都继承。
这个思路很正确,因为我们在前两期中讲到的 Lifecycle 库生命周期也是这么实现的。
但和 Lifecycle 库不一样的是,我们没法随意去修改整个 Activity 基类的实现。
而如果每个实现了生命周期监控的三方库都去实现一个 Activity 基类,那我们工程中的 Activity 改继承谁呢?
不仅如此,这种实现方案对开源的三方库来说,还有以下这些缺点:
- 对业务代码有侵入性,稳定性需要考量
- 接入成本变高,不仅要在工程中使用图片加载功能,还得改造工程基类实现生命周期监控。
- 工程对 Glide 的依赖更重了,进一步导致后续迁移库的成本也会变高。
- 加剧接入成本,Android 有各个 support 库的变种,会进一步加剧接入成本
- 易用性下降,生命周期功能严格依赖使用者接入质量。
这些缺点都会使得开发者在选型这个库时望而却步。
这也是为什么绝大多数三方库都喜欢 hook 的方式。因为拥有侵入少、迁移成本低、接入快速、易用性好等优点。
说了这么多,其实也想从另外一个角度来陈述(吹捧)一下 Glide 之所以流行的一些原因吧。大家后续想开源一个模块或者库的时候,也可以从这些角度衡量一下。
回到正文。
如何实现?
在开始之前,我们先约定一下,Android 中的 Activity 和 Fragment 有
android
和androidx
这两个 support 包的实现, Glide 针对这两个包做了两份相应的实现。这两个实现除了类名不一致外,逻辑都是一样的。下边我们只拿一个实现来说明,另外的实现大家可以参考代码。
Glide 的生命周期监控的整体实现和我们上边的思路基本一致,不过他没有采用基类的方式。而是用了一个非常巧妙的办法,在 Activity 和 Fragment 中插入一个自定义的 Fragment。
这里的设计简直惊为天人,还记得当初第一眼看到这个代码就震惊了。当时我们还在埋头苦干解决 Activity 中因为使用消息队列而导致的内存泄漏问题时,没想到 Glide 已经在源头上消灭了这类问题。🧎
源码就放在这里,大家一定要去拜读一下
1 | private RequestManagerFragment getRequestManagerFragment( |
在这个方案中,插入的这个自定义 Fragment 会跟随所在宿主 Fragment 和 Activity 的生命周期。所以我们就可以通过这个自定义 Fragment 的生命周期来间接的感知到它所在的宿主 Fragment 和 Activity 的生命周期了。
这段话如果不好理解,大家多读两遍。这个思路真的吹爆。
除此之外,还有个好处,Fragment 会随着宿主销毁而销毁,所以我们也不需要关心是否会造成内存泄漏。
其实看到这里,这篇文章的主题就已经讲完了哈哈,不过,我们还是需要看看 Glide 的实现和代码结构。
好。顺着我们上两期的思路。要实现这种生命周期监控的方案,我们需要 3 个角色:
- RequestManagerFragment:生命周期提供者,就是我们的自定义 Fragment。
- Lifecycle:生命周期接口 ,用来通知生命周期,和自定义的 Fragment 是绑定的关系。
- ActivityFragmentLifecycle:生命周期接口注册和反注册的管理类,类似于我们上期说的 Registry 的职责
我们将这 3 个角色补充到类图
其中:
RequestManagerFragment:是 Glide 继承自 android.app.Fragment 的自定义 Fragment 实现。职责是提供其所在宿主 Activity 或者 Fragment 的生命周期
ActivityFragmentLifecycle:生命周期接口注册和反注册,以及生命周期分发的管理类
其他的我们就不多介绍了。上期已经完整的介绍过了。
两种实现对比
我们将基于自定义 Fragment 的实现和上期基于 Lifecycle 的实现来做一下对比
注意:
- 二者实现的差异,我用不同的颜色做了区分
- ApplicationLifecycle 的实现并没有依赖到原生的 Lifecycle 库,只依赖了 Glide 自己的接口,所以在两个实现中是相同的。
两种实现中各个职责对应的角色如下表:
基于自定义 Fragment 实现 | 基于 Lifecycle 实现 | |
---|---|---|
生命周期提供者 | RequestManagerFragment | LifecycleObserver |
生命周期接口管理者 | ActivityFragmentLifecycle | LifecycleLifecycle |
获取 RequestManager 工具类 | LifecycleRequestManagerRetriver | RequetManagerRetriver 兼任 |
这样他们的异同点是不是就一目了然呢。
使用场景
目前的版本中,以上两种实现方案同时存在。具体使用哪种实现是通过开关控制。如下代码
1 | // com.bumptech.glide.manager.RequestManagerRetriever#get(androidx.fragment.app.Fragment) |
useLifecycleInsteadOfInjectingFragments()
这行就是控制是否使用新 Lifecycle 实现的关键。
你可以通过使用 GlideBuilder 来打开这个新的特性,前提是你的工程中在用的是 AndroidX 的 Activity 和 Fragment。
1 | // com.bumptech.glide.GlideBuilder#useLifecycleInsteadOfInjectingFragments |
一些细节
Lifecycle or Context ?
不知道你有没有想过 LifecycleRequestManagerRetriver 类中为什么用 Lifecycle 映射 RequestManager,而不是 Context 呢?
1 | // com.bumptech.glide.manager.LifecycleRequestManagerRetriever |
从唯一性来说,Lifecycle 和 Context 确实是一一对应的,也就是 Activity 或者 Fragment 同时也是一个 LifecycleRegistry(生命周期提供者)。所以拿来映射 RequestManager 也是完全可以的。
但是用 Lifecycle 有两个好处:
- 语义上很清晰,Retriever 和生命周期相关,与 Lifecycle 对象而不是 Context 对象绑定,会让角色职责非常清晰。
- Lifecycle 是接口,而不是实现。这就意味着我们可以实现 Lifecycle 接口来做自动化测试,而 Context 就会受限很多。
其实在早期的时候 Glide 真的这么用过 Context。(大概在 15 年左右,大家可以查询这个 commitId 7858f3ce 看到具体提交记录)。当然那个时间点,Retriever 功能还比较简陋,职责并不需要拆分的这么细。
这也告诉我们,很多功能和设计其实是根据功能的演进来升级的,并不是一开始就长这个样子😎
RequestManagerTreeNode ?
在每个 RequestManager 中都有一个 RequestManagerTreeNode
,这个 TreeNode 会在 RequestManager 创建的时候传进来,是一个比较重要的参数,大家有没有想过,这个 RequestManagerTreeNode
是做什么的?
其实比较简单,Android 为每个组件(Component)都提供了一个 onTrimMemory()
的回调通知,旨在系统内存不足时,通知大家都适当释放一些不用的内存,你好我好大家好。否则内存真的不够时,Android 系统就要痛下杀手了。
那针对我们生命周期的这个场景来说,Fragment 是可以嵌套的,那么相对应的 RequestManager 就会大量存在,如果每个嵌套的 Fragment 中包含非常多的图片加载请求,势必会造成大量的内存占用,所以当系统内存告急时,我们需要通知这些 RequestManager 将加载的请求都缓一缓。
1 | // com.bumptech.glide.RequestManager#onTrimMemory |
每个RequestManagerTreeNode
其实就关联了一个 RequestManager, 而每个 RequestManager 又关联了一个 Fragment,那么在 Fragment 嵌套的场景下就可以通过这些 TreeNode 来快速向下找到子节点们。这样,在内存不足时,可以递归通知子节点停止加载请求。
这种细节也是只有世界级的开源库才会关注到。🤞
完整类图
最后,我们来综合一下目前的解析结果,如下
类图包含内容有点多了,稍微有点糊。大家如果想要原始的高清图片,可以关注我的公众号 0xforee(也可以扫描底部二维码),回复 Glide完整类图
下载。
结束语
历经 3 周,生命周期的内容终于划上了句号😎。虽然写完这三篇文章后,对 Glide 的生命周期差不多可以倒背如流了,但对源码,我还是会不定期的翻一翻,因为总能发现一些新的东西。
这 3 期的生命周期能力其实是一个相对独立的设计思想,我们不能仅停留在理解 Glide 的设计思想基础上,也要将这种思想迁移,去通关其他的框架和库的生命周期思想,比如我们熟知的 LiveData,ViewModel等,甚至还有 Kotlin 协程。
我们更可以将这种设计思想,用到我们自己日常的代码中。
- 结合 MVP 设计模式,可以让 Presenter 自动释放引用。
- 结合数据库,可以在退出时停止读取数据。
- 结合网络库,可以在页面退出时,相关的网络请求自动停止
- 等等其他
总之,生命周期是一个通用的设计思想,你可以加在你想要加的任何位置。快来思考一下,你现有的工程中,可以有哪些能改造的。
如果你觉得这篇文章写得还不错,点个赞,点个收藏,点个关注吧。深夜码字不易😭
从代码设计看 Glide 之生命周期(下)
http://www.0xforee.top/2023/08/31/lifecycle-iii-of-glide-code-design/