从代码设计看 Glide 之生命周期(下)

终于来到我们生命周期的最后一期了。
这一期,我们探究一下 Glide 在低版本 Android 上是如何实现生命周期监控的。

从代码设计看 Glide 系列其他文章:从代码设计看 Glide 系列

那就让我们开始吧。

正文

冷知识:实际上 Glide 的生命周期监控能力是早于 Lifecycle 库的发布的。😎

先做一个小的思考

如果在有 Lifecycle 库的情况下,我们的生命周期可以来源于 Lifecycle 给我们提供的回调。那没有这个库的情况下,我们自己要实现生命周期监控,需要怎么做呢?

可能很多聪明的同学已经想到了,我们自己设计一个 Lifecycle 的框架,等 Activity 或者 Fragment 生命周期触发的时候,通知框架不就好了吗?

我们进一步想,如果监控的范围是我们工程内所有的 Activity 或者 Fragment 呢?

如果是我们自己的工程的话,我们可以实现一个 Base 基类,让所有的 Activity 和 Fragment 都继承。

这个思路很正确,因为我们在前两期中讲到的 Lifecycle 库生命周期也是这么实现的。
但和 Lifecycle 库不一样的是,我们没法随意去修改整个 Activity 基类的实现。
而如果每个实现了生命周期监控的三方库都去实现一个 Activity 基类,那我们工程中的 Activity 改继承谁呢?

不仅如此,这种实现方案对开源的三方库来说,还有以下这些缺点:

  1. 对业务代码有侵入性,稳定性需要考量
  2. 接入成本变高,不仅要在工程中使用图片加载功能,还得改造工程基类实现生命周期监控。
  3. 工程对 Glide 的依赖更重了,进一步导致后续迁移库的成本也会变高。
  4. 加剧接入成本,Android 有各个 support 库的变种,会进一步加剧接入成本
  5. 易用性下降,生命周期功能严格依赖使用者接入质量。

这些缺点都会使得开发者在选型这个库时望而却步。

这也是为什么绝大多数三方库都喜欢 hook 的方式。因为拥有侵入少、迁移成本低、接入快速、易用性好等优点。

说了这么多,其实也想从另外一个角度来陈述(吹捧)一下 Glide 之所以流行的一些原因吧。大家后续想开源一个模块或者库的时候,也可以从这些角度衡量一下。

回到正文。

如何实现?

在开始之前,我们先约定一下,Android 中的 Activity 和 Fragment 有 androidandroidx 这两个 support 包的实现, Glide 针对这两个包做了两份相应的实现。这两个实现除了类名不一致外,逻辑都是一样的。下边我们只拿一个实现来说明,另外的实现大家可以参考代码。

Glide 的生命周期监控的整体实现和我们上边的思路基本一致,不过他没有采用基类的方式。而是用了一个非常巧妙的办法,在 Activity 和 Fragment 中插入一个自定义的 Fragment

这里的设计简直惊为天人,还记得当初第一眼看到这个代码就震惊了。当时我们还在埋头苦干解决 Activity 中因为使用消息队列而导致的内存泄漏问题时,没想到 Glide 已经在源头上消灭了这类问题。🧎

源码就放在这里,大家一定要去拜读一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private RequestManagerFragment getRequestManagerFragment(
@NonNull final android.app.FragmentManager fm, @Nullable android.app.Fragment parentHint) {
// If we have a pending Fragment, we need to continue to use the pending Fragment. Otherwise
// there's a race where an old Fragment could be added and retrieved here before our logic to
// add our pending Fragment notices. That can then result in both the pending Fragmeng and the
// old Fragment having requests running for them, which is impossible to safely unwind.
RequestManagerFragment current = pendingRequestManagerFragments.get(fm);
if (current == null) {
current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (current == null) {
current = new RequestManagerFragment();
current.setParentFragmentHint(parentHint);
pendingRequestManagerFragments.put(fm, current);
fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
}
}
return current;
}

在这个方案中,插入的这个自定义 Fragment 会跟随所在宿主 Fragment 和 Activity 的生命周期。所以我们就可以通过这个自定义 Fragment 的生命周期来间接的感知到它所在的宿主 Fragment 和 Activity 的生命周期了。

这段话如果不好理解,大家多读两遍。这个思路真的吹爆。

除此之外,还有个好处,Fragment 会随着宿主销毁而销毁,所以我们也不需要关心是否会造成内存泄漏。

其实看到这里,这篇文章的主题就已经讲完了哈哈,不过,我们还是需要看看 Glide 的实现和代码结构。

好。顺着我们上两期的思路。要实现这种生命周期监控的方案,我们需要 3 个角色:

  1. RequestManagerFragment:生命周期提供者,就是我们的自定义 Fragment。
  2. Lifecycle:生命周期接口 ,用来通知生命周期,和自定义的 Fragment 是绑定的关系。
  3. ActivityFragmentLifecycle:生命周期接口注册和反注册的管理类,类似于我们上期说的 Registry 的职责

我们将这 3 个角色补充到类图

类图

其中:

  • RequestManagerFragment:是 Glide 继承自 android.app.Fragment 的自定义 Fragment 实现。职责是提供其所在宿主 Activity 或者 Fragment 的生命周期

  • ActivityFragmentLifecycle:生命周期接口注册和反注册,以及生命周期分发的管理类

其他的我们就不多介绍了。上期已经完整的介绍过了。

两种实现对比

我们将基于自定义 Fragment 的实现和上期基于 Lifecycle 的实现来做一下对比
基于自定义 Fragment 的实现

基于 Lifecycle 的实现

注意:

  1. 二者实现的差异,我用不同的颜色做了区分
  2. ApplicationLifecycle 的实现并没有依赖到原生的 Lifecycle 库,只依赖了 Glide 自己的接口,所以在两个实现中是相同的。

两种实现中各个职责对应的角色如下表:

基于自定义 Fragment 实现 基于 Lifecycle 实现
生命周期提供者 RequestManagerFragment LifecycleObserver
生命周期接口管理者 ActivityFragmentLifecycle LifecycleLifecycle
获取 RequestManager 工具类 LifecycleRequestManagerRetriver RequetManagerRetriver 兼任

这样他们的异同点是不是就一目了然呢。

使用场景

目前的版本中,以上两种实现方案同时存在。具体使用哪种实现是通过开关控制。如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// com.bumptech.glide.manager.RequestManagerRetriever#get(androidx.fragment.app.Fragment)
@NonNull
public RequestManager get(@NonNull Fragment fragment) {
...
FragmentManager fm = fragment.getChildFragmentManager();
Context context = fragment.getContext();
if (useLifecycleInsteadOfInjectingFragments()) {
Glide glide = Glide.get(context.getApplicationContext());
return lifecycleRequestManagerRetriever.getOrCreate(
context, glide, fragment.getLifecycle(), fm, fragment.isVisible());
} else {
return supportFragmentGet(context, fm, fragment, fragment.isVisible());
}
}

useLifecycleInsteadOfInjectingFragments() 这行就是控制是否使用新 Lifecycle 实现的关键。

你可以通过使用 GlideBuilder 来打开这个新的特性,前提是你的工程中在用的是 AndroidX 的 Activity 和 Fragment。

1
2
3
4
5
// com.bumptech.glide.GlideBuilder#useLifecycleInsteadOfInjectingFragments
public GlideBuilder useLifecycleInsteadOfInjectingFragments(boolean isEnabled) {
glideExperimentsBuilder.update(new UseLifecycleInsteadOfInjectingFragments(), isEnabled);
return this;
}

一些细节

Lifecycle or Context ?

不知道你有没有想过 LifecycleRequestManagerRetriver 类中为什么用 Lifecycle 映射 RequestManager,而不是 Context 呢?

1
2
// com.bumptech.glide.manager.LifecycleRequestManagerRetriever
@Synthetic final Map<Lifecycle, RequestManager> lifecycleToRequestManager = new HashMap<>();

从唯一性来说,Lifecycle 和 Context 确实是一一对应的,也就是 Activity 或者 Fragment 同时也是一个 LifecycleRegistry(生命周期提供者)。所以拿来映射 RequestManager 也是完全可以的。

但是用 Lifecycle 有两个好处:

  1. 语义上很清晰,Retriever 和生命周期相关,与 Lifecycle 对象而不是 Context 对象绑定,会让角色职责非常清晰。
  2. Lifecycle 是接口,而不是实现。这就意味着我们可以实现 Lifecycle 接口来做自动化测试,而 Context 就会受限很多。

其实在早期的时候 Glide 真的这么用过 Context。(大概在 15 年左右,大家可以查询这个 commitId 7858f3ce 看到具体提交记录)。当然那个时间点,Retriever 功能还比较简陋,职责并不需要拆分的这么细。
这也告诉我们,很多功能和设计其实是根据功能的演进来升级的,并不是一开始就长这个样子😎

RequestManagerTreeNode ?

在每个 RequestManager 中都有一个 RequestManagerTreeNode ,这个 TreeNode 会在 RequestManager 创建的时候传进来,是一个比较重要的参数,大家有没有想过,这个 RequestManagerTreeNode 是做什么的?

其实比较简单,Android 为每个组件(Component)都提供了一个 onTrimMemory() 的回调通知,旨在系统内存不足时,通知大家都适当释放一些不用的内存,你好我好大家好。否则内存真的不够时,Android 系统就要痛下杀手了。

那针对我们生命周期的这个场景来说,Fragment 是可以嵌套的,那么相对应的 RequestManager 就会大量存在,如果每个嵌套的 Fragment 中包含非常多的图片加载请求,势必会造成大量的内存占用,所以当系统内存告急时,我们需要通知这些 RequestManager 将加载的请求都缓一缓。

1
2
3
4
5
6
7
// com.bumptech.glide.RequestManager#onTrimMemory
@Override
public void onTrimMemory(int level) {
if (level == TRIM_MEMORY_MODERATE && pauseAllRequestsOnTrimMemoryModerate) {
pauseAllRequestsRecursive();
}
}

每个RequestManagerTreeNode 其实就关联了一个 RequestManager, 而每个 RequestManager 又关联了一个 Fragment,那么在 Fragment 嵌套的场景下就可以通过这些 TreeNode 来快速向下找到子节点们。这样,在内存不足时,可以递归通知子节点停止加载请求。

这种细节也是只有世界级的开源库才会关注到。🤞

完整类图

最后,我们来综合一下目前的解析结果,如下

Glide 完整类图

类图包含内容有点多了,稍微有点糊。大家如果想要原始的高清图片,可以关注我的公众号 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/

作者

0xforee

发布于

2023-08-31

更新于

2023-08-31

许可协议


欢迎关注我的公众号 0xforee,第一时间获取更多有价值的思考

评论