宏观剖析Glide4.8.0源码

最近因为对项目的图片库做了功能拓展和优化,花了点时间研究了下Glide,输出了总共6篇解析文章:
图片框架 - Glide 4.11.0源码走读
图片框架 - Glide自定义配置和组件及Registry机制
图片框架 - Glide加载webp动图流程解析
图片框架 - Glide解码webp动图浅析
图片框架 - Glide缓存机制解析
图片框架 - Glide磁盘缓存研究

本篇文章对整个Glide源码宏观剖析做一个简单总结。因为项目是基于Glide4.8.0,所以方便起见,分析的源码也是4.8.0版本。为了阅读方便,文章就尽量不贴对应代码了,Glide代码量确实有点多,想了解详情的可以参考上面的6篇文章,里面有详细源码解析。

一、Glide整体结构

1.1整个Glide主干功能:
1.2 图片加载整体执行流程:
Glide图片加载流程

Glide作为外部调用的入口函数,主要收集请求参数,构建一个图片请求,交由engine去获取图片资源,engine先从内存获取,如果活跃资源中有直接拿,如果没有则尝试去lruCache中获取,如果也没有则通过EngineJob线程池,发起一个异步任务即:DecodeJob来进行磁盘和网络获取。这里磁盘缓存策略会根据DiskCacheStrategy来设置,它主要是配置对图片原始数据流缓存以及解码转码后资源缓存两种类型数据。Generator是具体的图片资源获取管理类,它经由ModelLoader-LoadData-具体Fetcher的层层内部类调用关系最终交由对应的Fetcher类去处理图片资源获取的任务。成功获取资源后,会层层回调回来到DecodeJob来做解码工作(如果是原始资源),解码通过ModelLoader-LoadPath-具体Decoder的层层内部类调用关系最终交由对应的Decoder去做图片资源解码任务。后续就是将图片设置到目标控件上去。 这里,DecodeJob本身是通过Stage来调度自身不同任务类型,另外,网络IO和本地IO是分属于不同的DecodeJob,也就是两者任务的转换是需要通过EngineJob切线程来完成的。最后,Registry支持用户自定义配置和组件。

1.3 图片加载中数据转换流程
图片加载数据转换流程

一目了然,就不过多解释了。

二、Glide核心类图

类关系

这里简单例举了部分核心类之间的关系:

  • Glide: 入口类。
  • RequestBuilder: 收集参数,构建Request和Target交由RequestManager去统一处理。
  • RequestManager:左膀右臂:TargetTracker负责Target对应页面生命周期绑定、RequestTracker负责发起Request请求。
  • Engine:加载引擎。
  • EngineJob:线程池。
  • DcodeJob:异步任务,负责图片获取和解码。
  • Generator:负责获取图片资源,这里分了三类(ResourceCache对应解码后缓存、DataCache对应原始资源缓存、Source对应网络请求),通过ModelLoader最终匹配到对应的Fetcher来执行具体获取图片资源任务。
  • Target:显示图片的目标控件。

三、相关执行流程

3.1 首先看Glide.with(this).load(url).into(imageView) 整体调用流程
3.1.1 with
with

with主要干两件事:

  • 图片加载绑定对应页面生命周期;
    生命周期分为application 和 非application两种。分别通过ApplicationLifecycle、ActivityFragmentLifecycle来管理生命周期。

  • 初始化RequestManager;

3.1.2 load
load

load主要干一件事情:

  • 通过RequestManager初始化RequestBuilder。收集model和requestOption相关请求参数,为后续into封装request做准备。
3.1.3 into
into

into主要干了三件事:

  • 封装并发起request。
  • request获取图片数据。
  • 将图片显示到View上。
3.2 Glide整体缓存机制
3.2.1内存加载数据逻辑
内存获取图片资源逻辑

3.2.2 内存、磁盘、网络请求整体存取逻辑

图片数据存取逻辑

这里简单总结下:

取逻辑:
内存 > 磁盘 > 网络请求

  • 内存:
    上次刚被加载的资源(activeResources) > (最近被加载的资源)lruCache。

  • 磁盘:
    如果有主动设置DiskCacheStrategy,则按设置来。如果配置的是DiskCacheStrategy.ALL:则是取转换之后的资源(ResourceCache) > (DataCache)原始资源

  • 网络请求:
    走网络请求获取图片资源流。

存逻辑:

  • 内存:
    当前被加载的图片资源存到activeResources中,下次加载资源切换时,当前activeResources会remove然后被转移到lruCache。

  • 磁盘:
    网络请求成功之后,获取到资源流,然后看DiskCacheStrategy是否支持磁盘缓存,如果支持,会通过回调在SourceGenerator中通过cacheData进行资源缓存。

3.3 Glide磁盘缓存流程
网络请求成功后onDataReady回调触发的磁盘缓存流程

这里有两个关键节点:原始数据流缓存和解码后资源缓存。SourceGenerator本身除了发起网络请求之外,也会在网络请求成功后,在DiskCacheStrategy允许的条件下对原始数据进行磁盘缓存,其次在DecodeJob数据解码成功后,在DiskCacheStrategy允许的条件下对解码后的资源进行磁盘缓存。

这里有个问题:如果网络请求成功后缓存的图片原始数据流本身有问题,解码失败的话,框架顶多是不会缓存解码后资源,但是对有问题的原始数据缓存不会做处理,这就会导致后续获取图片时按优先级优先获取有问题的图片原始数据缓存,导致问题。

解决分析:
客户端层面:首先客户端无法单独判断是否是解码失败,因为Glide网络请求失败、解码失败、IO失败等等都统一抛的是onLoadFailed,其次对外暴露的api也没有单独清理一张图片的,只有批量清理磁盘缓存,可以批量清,但这样会影响到整体性能。也可以通过加signature绕过去,但是这样每次都会去请求网络,相当于不走缓存,最后也可以调整DiskCacheStrategy配置,不做原始数据缓存了,这个倒是可以。

解决:我本身也不太喜欢直接gradle引三方库,这样一来不好拓展功能、二来不好定位修复问题。如果开源,我一般都会引入源码,所以这里我直接改了源码:

DecodeJob.java

private void decodeFromRetrievedData() {
  if (Log.isLoggable(TAG, Log.VERBOSE)) {
    logWithTimeAndKey("Retrieved data", startFetchTime,
       "data: " + currentData
           + ", cache key: " + currentSourceKey
           + ", fetcher: " + currentFetcher);
  }
  Resource<R> resource = null;
  try {
    resource = decodeFromData(currentFetcher, currentData, currentDataSource);
  } catch (GlideException e) {
    e.setLoggingDetails(currentAttemptingKey, currentDataSource);
   throwables.add(e);
  }
  if (resource != null) {
    notifyEncodeAndRelease(resource, currentDataSource);
  } else {
    //解码失败,删除之前缓存磁盘的原始数据文件
    diskCacheProvider.getDiskCache().delete(new DataCacheKey(currentSourceKey, signature));
    runGenerators();
  }
}

具体分析过程参考文章:图片框架 - Glide磁盘缓存研究

好了,经过上面的图文并茂的解析,应该对Glide不管是整体还是部分都有了一个比较直观的了解了。

四、Glide中编译时注解+APT的应用

最后再简单介绍下Glide中编译时注解+APT的应用。

注解的玩法主要有两个场景:运行期和编译期。

  • 运行期:主要是注解+反射,注解提供标签,反射对注解类来做逻辑。
  • 编译期:注解+APT+反射,这里APT(Annotation Processor Tool)注解处理器,编译期会动态生成类,而类的内容要么自己手动拼串组装类内容,要么使用JavaPoet封装好的工具来组装类内容。

而Glide中,单例调用get()初始化时:

private static GeneratedAppGlideModule getAnnotationGeneratedGlideModules(Context context) {
  GeneratedAppGlideModule result = null;
  try {
    Class<GeneratedAppGlideModule> clazz =
        (Class<GeneratedAppGlideModule>)
            Class.forName("com.bumptech.glide.GeneratedAppGlideModuleImpl");
   result =
        clazz.getDeclaredConstructor(Context.class).newInstance(context.getApplicationContext());
  } catch (ClassNotFoundException e) {
  ... 
 }
  return result;
}

这里的com.bumptech.glide.GeneratedAppGlideModuleImpl就是APT动态生成的:

生成路径

源码对应的Processor路径:

annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'

这里就不详细分析GeneratedAppGlideModuleImpl的生成了,它最终的功能是针对manifest 和 注解两种注册方式分别调用其applyOptions和registerComponents来触发自定义配置和组件。

Demo地址:https://github.com/Zhto0/GlideWebpDemo

好了就写这么多吧,Glide总体来说还是比较复杂的,本篇文章主要是对Glide做一个宏观分析,以及工作中牵涉到的部分功能进行了浅析。在这个宏观了解的基础上,应该能够对Glide框架局部问题的定位和分析提供一点帮助。当然文章中如有不对地方,欢迎批评指正!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350