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

本文已同步发表于个人博客:0xforee's blog

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

欢迎关注本系列其他文章:


正文

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

我们发现,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 中原始的类名,当时的我看到这个名词简直懵了。让同样身为起名黑洞的我瞬间感到一丝欣慰😆

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

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 级别的生命周期呢?

  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 ,为什么要在这里多维护一份列表?

摘抄代码如下:

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);
    }
  }
	...
}

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

  1. 在内存紧张的时候,用做内存释放
  2. 用来根据 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 是怎么仍然可以做到生命周期监控的。

下期见~😄

相关推荐
cnsxjean7 小时前
SpringBoot集成Minio实现上传凭证、分片上传、秒传和断点续传
java·前端·spring boot·分布式·后端·中间件·架构
sunly_7 小时前
Flutter:启动屏逻辑处理02:启动页
android·javascript·flutter
Sgq丶8 小时前
Android Studio 配置 proto
android·ide·android studio
那年星空9 小时前
Flutter 设计模式全面解析:抽象工厂
flutter·设计模式·架构
RememberLey9 小时前
【eNSP】ISIS动态路由协议实验
网络·架构·智能路由器·ensp·动态路由协议·isis·huawei
凡人的AI工具箱11 小时前
40分钟学 Go 语言高并发:【实战】并发安全的配置管理器(功能扩展)
开发语言·后端·安全·架构·golang
_小马快跑_12 小时前
ConstraintLayout 中的ImageFilterView探索:处理图片圆角、亮度、饱和度、图片重叠等
android
IT-sec12 小时前
jquery-picture-cut 任意文件上传(CVE-2018-9208)
android·前端·javascript·安全·web安全·网络安全·jquery
xiaoduyyy13 小时前
【Android】RecyclerView回收复用机制
android
林北芒大果13 小时前
【Flutter】搭建Flutter开发环境,安卓开发
android·flutter