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

上一期我们借用 Android 的 Lifecycle 库实现了生命周期的管理。但是其中有一个可能隐藏的坑不知道大家有没有发现?

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


正文

我们把上期做好的图再搬过来看一看

这个图不用再讲了吧

我们发现,RequestManager 作为我们核心的请求管理类,对 Android LifecycleObserver 的实现有着直接的依赖。如果我们后续想要迁移其他的生命周期感知库,或者 Android 原生的 Lifecycle 库接口发生了变更,我们就需要修改 RequestManager 这个最核心的类。这其实违反了设计模式中的对拓展开放,对修改关闭 的原则。

接口隔离

许多同学如果经历过从 Volley 到 OkHttp,或从 GreenDao 到 Room,或从 Fresco 到 Glide 等等这些库迁移过程,一定会发现,如果和三方库耦合越深,那么迁移起来就会越痛苦。

💡 一个非常好的解决方案就是通过我们自己的接口做隔离。尽可能减少对外部库的依赖。

首先,我们将图片加载库和原生的 Lifecycle 能力尽可能的隔开,不能直接用原生的 Lifecycle 来驱动我们的代码(不直接依赖 Lifecycle 回调写逻辑),也就是说,我们需要一个”中介“来帮我们把 Lifecycle 传递过来的生命周期转交给我们,那我们和”中介“通过什么通信呢?

当然还是注册回调和分发回调的那一套咯。

从上边那段描述中,我们发现至少需要 2 个角色完成这个隔离:

  1. LifecycleLifecycle:中介,帮我们注册并监听原生的 Lifecycle 的生命周期回调 LifecycleObserver,然后通过通信接口转交给我们的 RequestManager
  2. LifecycleListener:通信接口,RequestManager 实现这个接口,然后向 LifecycleLifecycle 注册监听

但实际上,我们上期提到,除了真实有生命周期的 Activity 和 Fragment 之外,还有一个角色,也有特殊的生命周期,它就是 Application。

为了让 Application 和 Activity 等在生命周期对外表现上一致,我们同样抽象出 2 个角色

  1. ApplicationLifecyle:同 1 的中介角色,只是它只服务于 Application
  2. Lifecycle:是中介的抽象,用来抹平 Application ”没有生命周期“的这段特殊逻辑,让它和 Activity,Fragment 外在接口层面表现一致。

ApplicationLifecycle 和 LifecycleLifecycle 的唯一的区别就是,Application 的生命周期的onStart() 的起点就是addListener() 的时候

我们将新增加的 4 个角色补充到我们的上期的类图中:

不知道这图该起啥名

说实话,这里的 LifecycleLifecycle 的奇葩类名真的是 Glide 中原始的类名,当时的我看到这个名词简直懵了。让同样身为起名黑洞的我瞬间感到一丝欣慰😆

关于这几个类之间的角色关系,大家也可以看看具体的代码来感受一下,以下为未删减版~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// com.bumptech.glide.manager.LifecycleRequestManagerRetriever#getOrCreate()
RequestManager getOrCreate(
Context context,
Glide glide,
final Lifecycle lifecycle,
FragmentManager childFragmentManager,
boolean isParentVisible) {
Util.assertMainThread();
RequestManager result = getOnly(lifecycle);
if (result == null) {
LifecycleLifecycle glideLifecycle = new LifecycleLifecycle(lifecycle);
result =
factory.build(
glide,
glideLifecycle,
new SupportRequestManagerTreeNode(childFragmentManager),
context);
lifecycleToRequestManager.put(lifecycle, result);
glideLifecycle.addListener(
new LifecycleListener() {
@Override
public void onStart() {}

@Override
public void onStop() {}

@Override
public void onDestroy() {
lifecycleToRequestManager.remove(lifecycle);
}
});
// This is a bit of hack, we're going to start the RequestManager, but not the
// corresponding Lifecycle. It's safe to start the RequestManager, but starting the
// Lifecycle might trigger memory leaks. See b/154405040
if (isParentVisible) {
result.onStart();
}
}
return result;
}

除此之外,如何获取 Context,如何获取 Android 的 Lifecycle,如何通过注册 Lifecycle 拿到生命周期回调等等这些细节就不在这里展开了。

大家可以带着以上给出的各角色职责来深入代码,学习一些实现的细节。

如果看代码看懵了,可以多想一想,这个类职责是什么?服务于谁?又是依赖谁的服务?秉持着这样的思想,就不会迷失在庞大的代码细节中了。

为啥不做到 View 粒度呢?

Glide 中的 with 方法其实是可以传递 View 来使用的。

1
Glide#with(android.view.View)

虽然你可以通过传递 View 参数来使用 Glide,但是这并不代表生命周期是跟着 View 走的,Glide 内部还是需要找到是在哪个 Fragment 或者 Activity 中的。

那为什么不做到 View 级别的生命周期呢?

  1. View 自身没有对外暴漏可以监控的生命周期,常规手段无法做到,成本会比较高。
  2. 单一页面中可能会遇到数量庞大且层级很多的 View,如果要做到一一对应的话,资源消耗会比较大,但收益并不明显。

综上所述,没有必要对 View 做监控,只需要对 Fragment 粒度就足够了。

就和我们管理内存一样,一般会以线程或者进程为单位,却不会以变量为单位。

Factory 模式

如果有看过代码的同学,肯定记得有一个细节:RequestManagerRetriever 创建 RequestManager 并没有使用简单的 new 一个对象的方式,而是通过传递的 Factory 完成的,这样的好处是什么?

有些同学比较熟悉 Factory 模式,可能脱口而出,可以屏蔽对象创建的细节。

没错,那么它和 Builder 模式又有什么区别呢?Glide 中也存在了大量使用 Builder 模式的逻辑,同样作为创建型设计模式的他们两可以互相替换吗?

要解答以上的问题,还需要花费一番功夫,我们这里再卖个关子,挖个坑吧,后边单独出一篇文章来专门讲一讲 Glide 中的 Factory 设计模式。

这里,我们把创建 RequestManager 用到的 Factory 类也补充到类图中:

这个起名也头疼

多说两句

除了 Factory 模式,实际在翻看代码的时候,发现一个比较困惑的问题。切回到 Glide 的代码中,大家可以看到,Glide 和 RequestManager 也是多对一,是组合的关系,也就是说 Glide 中维护了一个 RequestManager 的列表。 这是为啥呢?明明 Glide 将创建和管理 RequestManager 的职责都交给了 Retriever ,为什么要在这里多维护一份列表?

摘抄代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// com.bumptech.glide.Glide
public class Glide implements ComponentCallbacks2 {
...
private final List<RequestManager> managers = new ArrayList<>();

public void trimMemory(int level) {
// Engine asserts this anyway when removing resources, fail faster and consistently
Util.assertMainThread();
// Request managers need to be trimmed before the caches and pools, in order for the latter to
// have the most benefit.
synchronized (managers) {
for (RequestManager manager : managers) {
manager.onTrimMemory(level);
}
}
// memory cache needs to be trimmed before bitmap pool to trim re-pooled Bitmaps too. See #687.
memoryCache.trimMemory(level);
bitmapPool.trimMemory(level);
arrayPool.trimMemory(level);
}
...
boolean removeFromManagers(@NonNull Target<?> target) {
synchronized (managers) {
for (RequestManager requestManager : managers) {
if (requestManager.untrack(target)) {
return true;
}
}
}

return false;
}

void registerRequestManager(RequestManager requestManager) {
synchronized (managers) {
if (managers.contains(requestManager)) {
throw new IllegalStateException("Cannot register already registered manager");
}
managers.add(requestManager);
}
}

void unregisterRequestManager(RequestManager requestManager) {
synchronized (managers) {
if (!managers.contains(requestManager)) {
throw new IllegalStateException("Cannot unregister not yet registered manager");
}
managers.remove(requestManager);
}
}
...
}

可以看到,主要有两个作用:

  1. 在内存紧张的时候,用做内存释放
  2. 用来根据 Target 移除 Request

我翻遍了关于 Glide 和 RequestManager 这 2 个文件的历史提交记录,依然找不到为什么不将内存释放的这个职责向下传递给 Retriever,而是非要 Glide 直接去管理 RequestManager,导致有两个地方同时存在着管理 RequestManager 的职责。

即使找到了当时提交这段代码的记录,却依然无法弄明白原因。因为提交记录只有简短的一行。

1
HEAD is now at 7858f3ce3 Move tracking/untracking/cancelling requests into RequestManager

唯一可能得解释就在于这次代码变更中的这一句 !isOwnedByUs

起名这件事我放弃了

遇到不属于 RequestManager 自身的 Target,需要从整体的列表中查找并移除。括号里的那句其实对应的就是 Glide 类中的这个函数:

1
2
3
4
5
6
7
8
9
10
11
12
// com.bumptech.glide.Glide#removeFromManagers
boolean removeFromManagers(@NonNull Target<?> target) {
synchronized (managers) {
for (RequestManager requestManager : managers) {
if (requestManager.untrack(target)) {
return true;
}
}
}

return false;
}

但是整体的列表在 Retriever 也是有维护的,也完全不需要经过 Glide??

这里的逻辑写法让我满脸问号❓,有知道的同学可以在评论区解答一下。

结束语

这一期主要讲了我们通过接口隔离的方式,来让图片加载库与其他三方的库依赖最小化。来做到隔离变化的能力。

并且初步认识了一下 Glide 中 Factory 设计模式的简单使用。

下一期是生命周期的最后一期了,我们来看看在 Android 官方的 Lifecycle 出现之前,Glide 是怎么仍然可以做到生命周期监控的。

下期见~😄

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

http://www.0xforee.top/2023/08/24/lifecycle-ii-of-glide-code-design/

作者

0xforee

发布于

2023-08-24

更新于

2023-08-26

许可协议


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

评论