本文已同步发表于个人博客:0xforee's blog
上一期我们借用 Android 的 Lifecycle 库实现了生命周期的管理。但是其中有一个可能隐藏的坑不知道大家有没有发现?
欢迎关注本系列其他文章:
正文
我们把上期做好的图再搬过来看一看
我们发现,RequestManager 作为我们核心的请求管理类,对 Android LifecycleObserver 的实现有着直接的依赖。如果我们后续想要迁移其他的生命周期感知库,或者 Android 原生的 Lifecycle 库接口发生了变更,我们就需要修改 RequestManager 这个最核心的类。这其实违反了设计模式中的对拓展开放,对修改关闭
的原则。
接口隔离
许多同学如果经历过从 Volley 到 OkHttp,或从 GreenDao 到 Room,或从 Fresco 到 Glide 等等这些库迁移过程,一定会发现,如果和三方库耦合越深,那么迁移起来就会越痛苦。
💡 一个非常好的解决方案就是通过我们自己的接口做隔离。尽可能减少对外部库的依赖。
首先,我们将图片加载库和原生的 Lifecycle 能力尽可能的隔开,不能直接用原生的 Lifecycle 来驱动我们的代码(不直接依赖 Lifecycle 回调写逻辑),也就是说,我们需要一个"中介"来帮我们把 Lifecycle 传递过来的生命周期转交给我们,那我们和"中介"通过什么通信呢?
当然还是注册回调和分发回调的那一套咯。
从上边那段描述中,我们发现至少需要 2 个角色完成这个隔离:
- LifecycleLifecycle:中介,帮我们注册并监听原生的 Lifecycle 的生命周期回调 LifecycleObserver,然后通过通信接口转交给我们的 RequestManager
- LifecycleListener:通信接口,RequestManager 实现这个接口,然后向 LifecycleLifecycle 注册监听
但实际上,我们上期提到,除了真实有生命周期的 Activity 和 Fragment 之外,还有一个角色,也有特殊的生命周期,它就是 Application。
为了让 Application 和 Activity 等在生命周期对外表现上一致,我们同样抽象出 2 个角色
- ApplicationLifecyle:同 1 的中介角色,只是它只服务于 Application
- Lifecycle:是中介的抽象,用来抹平 Application "没有生命周期"的这段特殊逻辑,让它和 Activity,Fragment 外在接口层面表现一致。
ApplicationLifecycle 和 LifecycleLifecycle 的唯一的区别就是,Application 的生命周期的
onStart()
的起点就是addListener()
的时候
我们将新增加的 4 个角色补充到我们的上期的类图中:
说实话,这里的 LifecycleLifecycle 的奇葩类名真的是 Glide 中原始的类名,当时的我看到这个名词简直懵了。让同样身为起名黑洞的我瞬间感到一丝欣慰😆
关于这几个类之间的角色关系,大家也可以看看具体的代码来感受一下,以下为未删减版~
java
// 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 来使用的。
java
Glide#with(android.view.View)
虽然你可以通过传递 View 参数来使用 Glide,但是这并不代表生命周期是跟着 View 走的,Glide 内部还是需要找到是在哪个 Fragment 或者 Activity 中的。
那为什么不做到 View 级别的生命周期呢?
- View 自身没有对外暴漏可以监控的生命周期,常规手段无法做到,成本会比较高。
- 单一页面中可能会遇到数量庞大且层级很多的 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 ,为什么要在这里多维护一份列表?
摘抄代码如下:
java
// 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);
}
}
...
}
可以看到,主要有两个作用:
- 在内存紧张的时候,用做内存释放
- 用来根据 Target 移除 Request
我翻遍了关于 Glide 和 RequestManager 这 2 个文件的历史提交记录,依然找不到为什么不将内存释放的这个职责向下传递给 Retriever,而是非要 Glide 直接去管理 RequestManager,导致有两个地方同时存在着管理 RequestManager 的职责。
即使找到了当时提交这段代码的记录,却依然无法弄明白原因。因为提交记录只有简短的一行。
bash
HEAD is now at 7858f3ce3 Move tracking/untracking/cancelling requests into RequestManager
唯一可能得解释就在于这次代码变更中的这一句 !isOwnedByUs
遇到不属于 RequestManager 自身的 Target,需要从整体的列表中查找并移除。括号里的那句其实对应的就是 Glide 类中的这个函数:
java
// 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 是怎么仍然可以做到生命周期监控的。
下期见~😄