探索 Glide 原理

Glide 原理.png

前言

1. Glide 基本用法

接下来的讲解将基于 Glide 目前的最新版本 4.11。

Glide 的使用特别简单,首先添加依赖。

添加依赖png

然后调用下面这三个方法。

三个方法png
  • with()

    可以传 ApplicaitonActivityFragmentView 等类型的参数,加载图片的请求会与该参数的生命周期绑定在一起。

  • load()

    可以传图片的网络地址、Drawable 等。

  • into()

    一般传 ImageView

2. Glide 基本原理

Glide 图片加载三阶段.png

Glide 图片加载流程大致三可以分为三个阶段:发起请求启动任务以及解码图片

1. 发起请求

Glide 加载图片的第一个阶段就是发起请求,发起请求最少要调用 with()load()into() 三个方法。

with() 方法要传一个 ActivityFragmentViewApplication 参数,这几种类型的参数都能够获取到能用来绑定生命周期的 Context ,如果 ContextActivity ,那么 Glide 会通过这个 Context 创建一个空白的 Fragment ,这个 Fragment 的意义就是监听 Activity 的生命周期,这样当 Activity 退出的时候,Glide 会取消这个 Activity 发起的图片加载请求,以避免不必要的流量等资源浪费。

发起请求的第二步是调用 RequestManagerload() 方法,load() 方法做的事情比较少,只是把入参作为 model 变量的值,这个 model 是用来创建 Request 的。

发起请求的第三步是调用 RequestBuilderinto() 方法,并传入一个 ImageViewinto() 方法中会创建一个请求 Request 对象,然后会通过请求跟踪器 RequestTracker 调用 Requestbegin() 方法。

2. 启动任务

Glide 加载图片的第二个阶段就是启动任务,这个任务就是解码任务 DecodeJob

当 Request 的 begin() 方法被 RequestTracker 调用后,Request 会调用 TargetgetSize() 方法获取最终的图片大小,Target 接口有很多实现类,比如我们传到 into() 中是 ImageView 的话,那 Target 就是 ImageViewTarget

获取到 Target 的大小后,Request 就会调用加载引擎 Engine 的 load() 方法尝试从内存中获取已有图片,如果获取不到的话,就会启动 DecodeJob 开始从图片来源加载请求。

3. 解码图片

Glide 图片加载的第三个阶段就是解码图片,这个阶段是从 DecodeJobrun() 方法开始的。

当 DecodeJob 的 run() 方法被 Engine 调用后,DecodeJob 就会调用 ModelLoader 的 buildLoadData() 方法从图片来源 model 获取图片,获取到的图片原始数据就是 Data ,一般 Data 就是输入流 InputStream

然后 ModelLoader 会调用 ResourceDecoder 的 decode() 方法把 Data 解码为 Resource ,Resource 就是 Glide 根据 Target 的大小对 Data 进行缩放后的图片资源。

把 Data 转换为 Resource 后, DecodeJob 会调用 Transformation 的 transform() 方法应用变换选项,这时 Resource 变成了 TransformedResource 。

然后 DecodeJob 会调用 ResourceTranscoder 的 transcode() 方法对资源进行转码操作,转码后的资源就是 TranscodedResource。

转码操作完成后,Engine 就会调用 Request 的 onResourceReady() 方法, 然后 Request 会调用 Target 的 onResourceReady() 方法,如果是 ImageViewTarget 的话,那 onResourceReady() 方法就会把图片设置到 ImageView 中。

当资源传给 Target 处理后,DecodeJob 就会调用 ResourceEncoder 的 encode() 的方法,把图片资源编码到磁盘中。

4. Glide 缓存原理

[图片上传失败...(image-392d44-1623041354520)]

Glide 的缓存用的是三级缓存机制,图片的缓存分为内存缓存、磁盘缓存和来源缓存,也就是从内存中获取不到图片时,再到磁盘获取,还是获取不到就去图片来源获取,比如从服务器上获取。

对于内存缓存,Glide 把内存缓存分为了 ActiveResources 和 MemoryCache 两级,如果从 ActiveResources 拿不到资源,则从 MemoryCache 中获取资源,其中 MemoryCache 的具体实现是 Glide 中的 LruCache

在 ActiveResources 中有类型为 Map 的 activeEngineResources 的字段和一个类型为 ReferenceQueue 的 resourceReferenceQueue 字段,ResourceWeakReference 就是图片资源的弱引用,父类是 WeakReference ,而 ReferenceQueue 就是引用队列,垃圾回收器在检测到恰当的可达性变化后,会把已注册的引用对象添加到引用队列中。

然后 MemoryCache 是一个接口,这个接口的实现类是 LruResourceCache ,这个类的主要逻辑在 Glide 自己实现的一个 LruCache 类中。

而 Glide 的磁盘缓存主要是由 DiskLruCache 和 ResourceEncoder 合作完成的,LruCache 是内存中的图片资源的缓存实现, DiskLruCache 则是磁盘中的图片文件的缓存实现,而 ResourceEncoder 则负责做具体的写入文件操作。

之所以把第三级缓存叫来源缓存,是因为严格意义上,就算图片文件不是在服务器上而是在设备上,只要图片对应的文件目录不属于 Glide 管理的范围,那么也是外部来源,Glide 中定义了 5 种数据源,分别是 LOCAL、REMOTE、DATA_DISK_CACHE、RESOURCE_DISK_CACHE 和 MEMORY_CACHE ,其中 LOCAL 和 REMOTE 就是外部来源,REMOTE 数据源一般就是由 HttpGlideUrlLoader 来加载,而 LOCAL 数据源则是由 UriLoader 和 MediaStoreFileLoader 等 ModelLoader 来加载。

5. Glide 初始化流程与配置

这一节会讲解 Glide 的初始化流程,包括 Glide 调用配置的方式、AppGlideModule 的两个方法中用到的 Registry 和 GlideBuilder 在 Glide 中的作用。

6. Glide 图片加载选项

Glide 开放了非常多的图片加载选项,我们不一定全都用得上,但是了解这些选项,可以让我们在需要的时候能调用对应的选项,不用再自己实现一遍。

1. 发起请求

当我们调用 with() 方法时,Glide 会通过 RequestManagerRetriver 找出或创建一个当前 Activity 对应的 RequestManager ,一个 Context 对应一个 RequestManager,RequestManager 有 3 个作用:绑定生命周期、监听网络状态、创建请求构建器。

1. 绑定生命周期

当我们调用 with() 方法时,RequestManager 会用对应的 Context 创建一个 RequestManagerFragment ,RequestManagerFragment 是一个无布局的 Fragment。

RequestManagerFragment 主要是用来做生命周期关联的,当这个 Fragment 感知到 Activity 的生命周期发生变化时,就会告诉 RequestManager,让它去做暂停请求、继续请求和取消请求等操作。

如果我们用的是 ApplicationContext 加载某张图片,那就意味着这次图片加载操作的生命周期是与应用的生命周期绑定的。

2. 监听网络状态

RequestManager 中有一个网络连接监听器 RequestManagerConnectivityListener ,它实现了ConnectivityListener 接口,每次网络状态切换时,RequestManager 就会重启所有的图片加载请求。

3. 创建请求构建器

我们在加载图片时调用的 load() 方法是 RequestManager 的方法,调用这个方法其实是创建了一个请求构建器 RequestBuilder,RequestManager 中有很多创建 RequestBuilder 的方法,比如 asDrawable()、asBitmap() 、asFile() 等,这些方法对应着不同泛型参数的 RequestBuilder 。

2. 启动任务

image

在我们调用 into() 方法后,RequestBuilder 会通过 RequestTracker 调用 Request 的 begin() 方法,然后 Request 在获取到 Target 的大小后就会调用 Engine 的 load() 方法,然后 Engine 会尝试从内存中获取图片,获取不到的话则启动 DecodeJob 。

2.1 Request

对于我们发起的图片加载请求,Glide 会把这个请求封装为 Request,而 RequestBuilder 就是 Request 的构建器,当我们调用 RequestBuilder 的 into() 方法时,RequestBuilder 会创建一个 Request ,并把 Request 传给请求管理器。

RequestBuilder 继承了 BaseRequestOptions 抽象类,我们平时用的 centerCrop() 等方法大部分都是 BaseRequestOptions 的方法,关于图片加载选项在后面会讲到。

2.1.1 Request 的 6 种状态

前面讲到的 Request 具体就是 SingleRequest ,SingleRequest 中有一个 Status 枚举类,包含了请求的 6 种状态。

1. 待运行 PENDING

当我们通过 into() 创建了一个 SingleRequest 后,该 Request 就进入了待运行状态。

2. 已清除 CLEARED

每次我们用 into() 方法加载图片时,RequestManager 都会先看下我们传入的 Target 是否有对应的 Request ,如果有的话就会调用该 Request 的 clear() 方法释放资源,这时 Request 就进入了已清除状态。

3. 待测量 WAITING_FOR_SIZE

当 RequestManager 调用 RequestTracker 的 runRequest() 方法后,RequestTracker 就会调用 Request 的 begin() 方法,这时请求就进入了待测量状态。

4. 运行中 RUNNING

在 SingleRequest 的 begin() 方法中,调用了 Target 的 getSize() 方法获取 ImageView 的尺寸,获取到尺寸后,SingleRequst 会调用 Engine 的 load() 方法启动图片加载请求,这时 Request 就进入了运行中状态。

5. 已完成 COMPLETE

当 Engine 从内存中加载到资源,或者通过解码任务加载到资源后,就会调用 SingleRequest 的 onResourceReady() 方法,这时 Request 就进入了已完成状态。

6. 失败 FAILED

当解码任务 DecodeJob 在处理图片的过程中遇到异常时,就会调用 EngineJob 的 onLoadFailed() 方法,然后 EngineJob 会调用 SingleRequest 的 onLoadFailed() 方法,这时 SingleRequest 就进入了失败状态。

2.1.4 三种占位图

我们在加载图片时,可以设置 placeholder、error 和 fallback 三种占位图。

  • placeholder

    图片加载完成前显示的占位图;

  • error

    图片加载失败时显示的占位图;

  • fallback

    图片来源为空时显示的占位图;

使用占位图时,要注意占位图是不会使用 Transformation 进行变换的,如果你想弄个圆角或圆形的占位图,可以用 submit().get() 获取对应变换后的占位图的 Drawable 对象,然后传到对应的占位图设置方法中。

2.2 Target

image

当我们调用 into() 方法,传入 ImageView 后,Glide 会把 ImageView 转化为 Target ,下面我们来看下不同 Target 的作用。

2.2.1 ImageViewTarget

1. SizeDeterminer

ImageViewTarget 继承了 ViewTarget ,在 ViewTarget 中有一个用来获取尺寸的 SizeDeterminer ,SizeDeterminer 的 getSize() 方法拿到的尺寸,是把 ImageView 的内边距 padding() 去掉后的尺寸。

在 Glide 中,宽高分为请求宽高和原始宽高 ,而 SizeDeterminer 拿到的尺寸就是请求宽高,Glide 会根据请求宽高对图片进行缩放操作,以减少不必要的内存消耗。

2. OnPreDrawListener

当 Request 获取 View 的尺寸失败时,ViewTarget 会通过 ViewTreeObserver 的 OnPreDrawListener 的回调来获取 View 的尺寸,然后再传给 Request。

3. setResource()

ImageViewTarget 主要有 BitmapImageViewTarget 和 DrawableImageViewTarget 两个子类,它们两个的区别就在于它们的 setResource() 方法。

  • BitmapImageViewTarget

    setResource() 用的是 ImageView 的 setImageBitmap() 方法;

  • DrawableImageViewTarget

    setResource() 用的是 ImageView 的 setImageDrawable() 方法;

2.2.2 RequestFutureTarget

1. submit()

FutureTarget 是一个实现了 Future 和 Target 接口的接口,它只有一个 RequestFutureTarget 子类 ,当我们用 submit() 方法获取 Glide 加载好的图片资源时,就是创建了一个 RequestFutureTarget 。

2. Waiter

RequestFutureTarget 是用 wait/notify 的方式来实现等待和通知的,这两个是 Object 的方法,Request 中有一个 Waiter ,当 DecodeJob 加载到图片后,RequestFutureTarget 就会让 Waiter 发出通知,这时我们的 get() 方法就能获取到返回值了。

这就是为什么我们用 RequestFutureTarget 的 get() 方法获取图片时,要把这个操作放在子线程运行。

2.2.3 CustomTarget

给不是 View 的 Target 加载图片时,Glide 都把它作为 CustomTarget 。

1. PreloadTarget

预加载 Target 。

当我们调用 preload() 选项预加载图片时,Glide 会把图片交给 PreloadTarget 处理,当 PreloadTarget 接收到图片资源后,就会让 RequestManager 把该请求的资源释放掉。

因为不需要等待资源加载完成,所以我们在用 preload() 预加载图片时,不用像 submit() 一样在子线程中执行。

2. AppWidgetTarget

桌面组件 Target 。

当 AppWidgetTarget 接收到处理好的图片资源后,会把它设置给 RemoteView ,然后通过桌面组件管理器 AppWidgetManager 更新桌面组件。

3. DelayTarget

GifTarget。

这是加载 Gif 图片时要用到的 Target ,关于 Glide 加载 Gif 图片的流程在后面会讲到。

4. NotificationTarget

通知栏 Target 。

这个 Target 有一个 setBitmap 方法,会把图片设置给通知栏的 RemoteView ,然后通过 NotificationManager 更新通知栏中的通知。

2.3 Engine

下面我们来看一些与 Engine 相关的实现。

  • Engine 的作用
  • Key 的作用
  • Resource 的作用
  • BitmapPool

2.3.1 Engine 的作用

Engine 是 Glide 的图片加载引擎,是 Glide 中非常重要的一个类,下面我们来看下 Engine 的作用。

1. load()

[图片上传失败...(image-78818c-1623041354519)]

前面讲到了当我们调用 into() 方法时,就是间接调用了 Request.begin() 方法,而 Request 的 begin() 方法又调用了 Engine 的 load() 方法。

在 load() 方法中,Engine 会先用 EngineKeyFactory 创建资源标识符 Key,然后用这个 Key 去内存缓存中加载资源。

如果从内存中找到了资源,Engine 就会直接把资源回传给 Resource,如果没有加载到资源,Engine 就会创建并启动新的 EngineJob 和解码任务 DecodeJob。

2. EngineKeyFactory

EngineKeyFactory 是 Engine 中一个负责生产 EngineKey 的工厂,EngineKey 是引擎任务资源标识符,关于什么是 Key 后面进一步讲。

在 Engine 启动新的任务加载图片前,会先通过 EngineKeyFactory 创建一个 EngineKey,然后让 DecodeJob 把资源与 EngineKey 进行绑定,这里说的绑定,其实就是把 model 放到 EngineKey 中。

3. 回收资源

Engine 中有一个资源回收器 ResourceRecycler ,Resource 接口中有一个 recycle() 方法,关于 Resource 我们后面再讲。

这里只要知道,当 SingleRequest 被清除,比如在 into() 方法中发现 Target 已经有对应的 Request 时,Request 就会让 Engine 释放资源,具体做释放资源操作的就是 ResourceRecycler。

4. 磁盘缓存提供器

LazyDiskCacheProvider 是 Engine 中的一个静态内部类,是磁盘缓存 DiskCache 的提供器,DiskCache 是一个接口,关于 DiskCache 的实现我们后面再讲。

5. 启动新的解码任务

当 Engine 从内存中找不到对应的 Key 的资源时,就会启动新的解码任务。

Engine 会用加载任务工厂 EngineJobFactory 构建一个加载任务 EngineJob,然后再构建一个解码任务 DecodeJob。

EngineJob 这个名字看起来很霸气,但是实际上它并没有做什么事情,它只是 Engine 与 DecodeJob 之间沟通的桥梁。

当构建了 EngineJob 和 DecodeJob 后,Engine 就会把 DecodeJob 提交到线程池 GlideExecutor 中。

2.3.2 Key

前面讲到了 Engine 会通过 EngineKeyFactory 创建资源标识符 Key ,那什么是 Key ?

Key 是 Glide 中的一个接口,是图片资源的标识符。

1. 避免比较有误

Glide 的内存缓存和磁盘缓存用的都是 Glide 自己实现的 LruCache,LruCache 也就是最近最少使用缓存算法(Least Recently Used),LruCache 中有一个 LinkedHashMap ,这个 HashMap 的 Key 就是 Key 接口,而 Value 则是 Resource 接口。

在用对象作为 HashMap 的 Key 时,要重写 equals() 和 hashCode() 方法。

如果不重写这两个方法,那么当两个 Key 的内存地址不同,但是实际代表的资源相同时,使用父类 Object的 hasCode() 直接用内存地址做比较,那么结果会是不相等。

此外 Object 的 equals() 方法也是拿内存地址作比较,所以也要重写。

比如下面就是 ResourceCacheKey 的 equals() 判断逻辑。

image
2. Key 实现类
image

下面是几个实现了 Key 接口的类。

  • DataCacheKey

    原始图片数据标识符。

  • ResourceCacheKey

    处理后的图片资源标识符。

  • AndroidResourceSignature

    Android 资源标识符。当我们传入 into() 方法的图片是 R.drawable.xxx 时,Glide 就会把它封装为 AndroidResourceSignature 。

  • ObjectKey

    通用资源标识符。

    可以说除了 App 自带的 Android 资源以外的图片资源都会用 ObjectKey 作为标识符,比如本地图片文件。

  • EngineKey

    引擎资源标识符。

    这个 Key 是 Engine 对其他 Key 的封装,这时传进来的 Key 是以签名(Signature)的身份存在 EngineKey 中的。

2.3.3 Resource

前面讲到了 Engine 会通过 ResourceRecycler 来回收资源,而 ResourceRecycler 调用了 Resource 的 recycle() 方法。

可能你想起来 Bitmap 就有一个可以回收图片内存的 recycle() 方法,没错,Glide 回收 Bitmap 的方式就是用的 Bitmap 自带的 recycle() 方法,但是这个过程又比这复杂一些。

Resource 是一个接口,其中一个实现类是 BitmapResource ,也就是位图资源,比如网络图片就会转化为 BitmapResource。

在 BitmapResource 中有一个位图池 BitmapPool,这是 Glide 用来复用 Bitmap 的一个接口,具体的实现类是 LruBitmapPool 。

在 BitmapResource 的 recycle() 方法中,会把对应的 Bitmap 通过 put() 方法放到 BitmapPool 中,关于 BitmapPool 在讲 Glide 缓存原理时会进一步讲。

2.4 DecodeJob

前面讲到了 Engine 在缓存中找不到资源时,就会创建新的加载任务 EngineJob 和新的解码任务 DecodeJob ,然后让 EngineJob 启动 DecodeJob。

DecodeJob 实现了 Runnable 接口,EngineJob 启动 DecodeJob 的方式就是把它提交给 GlideExecutor,如果我们没有调整磁盘缓存策略的话,那默认用的就是 diskCacheExecutor ,关于 GlideExecutor 在第 4 大节会讲,下面我们先看下 DecodeJob 的实现。

2.4.1 runWrapped()

DecodeJob 的 run() 方法只是对 runWrapped() 可能遇到的异常进行了捕获,而 runWrapped() 方法会根据不同的运行理由 RunReason 运行不同的数据生成器。

image
1. 三种运行理由

runWrapped() 会根据下面三种运行理由来执行解码任务。

  • INITAILIZE

    从缓存中获取数据并解码;

  • SWITCH_TO_SOURCE_SERVICE

    从来源获取数据后再进行解码;

  • DECODE_DATA

    当获取数据的线程与 DecodeJob 的线程不同时,比如使用了 OkHttp-Integration 时,DecodeJob 会直接对数据进行解码;

2. 初始化

当运行理由为默认状态 INITIALIZE 时,DecodeJob 会从磁盘中获取图片数据并进行解码。

3. 从来源获取数据

当 DecodeJob 从缓存中获取不到数据时,就会把运行理由改为 SWITCH_TO_SOURCE_SERVICE ,也就是从来源获取数据,然后运行来源数据生成器 SourceGenerator 。

4. 对检索到的数据进行解码

DecodeJob 通过数据生成器获取到数据后,就会调用 decodeFromRetrievedData() 方法来对检索到的数据进行解码。

2.4.2 DecodeJob 数据获取流程

image

在 DecodeJob 的 getNextStage() 方法中,会根据当前的解码步骤 stage 来判断进行什么操作。

DecodeJob 把提取数据分为了 6 个阶段,这 6 个阶段是 Stage 枚举类中的值。

1. INITIALIZE

初始化。

当解码处于这个阶段时,DecodeJob 会根据磁盘缓存策略,判断是否要从磁盘缓存中获取处理过的图片资源,是的话就用 ResourceCacheGenerator 获取图片资源,当用 ResourceCacheGenerator 获取到 Resource 后,就会开始对资源进行解码。

如果磁盘缓存策略设定了不从缓存中获取 Resource,那就会切换到 RESOURCE_CACHE 阶段。

2. RESOURCE_CACHE

从缓存中获取处理过的图片资源。

当解码处于这个阶段时,DecodeJob 会根据磁盘缓存策略,判断是否要从磁盘缓存中获取未处理过的图片原始数据,是的话就用 DataCacheGenerator 获取图片数据。

3. DATA_CACHE

从缓存中获取原始数据。

如果磁盘缓存策略设定了不获取缓存中的图片资源和原始数据 ,又或者是获取不到数据,DecodeJob 那就会切换到 DATA_CACHE 阶段。

如果我们在加载图片时调用了 onlyRetrieveFromCache(true) ,那么 DecodeJob 就会不会切换到 SOURCE 阶段从来源获取数据,而是会切换到 FINISH 阶段结束数据获取流程。

否则就会切换到 SOURCE 阶段。

4. SOURCE

从图片来源获取原始数据。

如果 DecodeJob 在 RESOURCE_CACHE 和 DATA_CACHE 阶段都没有拿到图片数据,那就会用 SourceGenerator 从图片来源获取图片数据。

5. ENCODE

编码。

当磁盘缓存策略设定了要对图片资源进行缓存时,那么在获取到数据后,DecodeJob 就会用 ResourceDecoder 对资源进行编码,也就是把图片放到磁盘缓存中。

6. FINISH

结束。

2.4.3 三种数据生成器

当 DecodeJob 切换阶段后,会调用 getNextGenerator() 切换不同阶段对应的生成器,这里说的生成器,指的是 DataFetcherGenerator 接口。

DataFetcherGenerator 不是像名字说的那样用来创建 DataFetcher 的,DataFetcherGenerator 与 DataFetcher 是通过 ModelLoader 来关联的。

DataFetcherGenerator 会通过 ModelLoader 构建数据封装对象 LoadData ,然后通过 LoadData 中的 DataFetcher 来加载数据。

LoadData 是 ModelLoader 的内部类,它有来源标识符 Key 和 DataFetcher 两个字段。

在 ModelLoader 中最重要的就是 buildLoadData() 方法,不同类型的 Model 对应的 ModelLoader 所创建出来的 LoadData() 也不同。

下面我们来看下 DataFetcherGenerator ,这个接口中最重要的方法是 startNext() ,具体实现了这个接口有下面三个类。

  • SourceGenerator

    来源数据生成器。

  • DataCacheGenerator

    原始缓存数据生成器。

  • ResourceCacheGenerator

    缓存资源生成器。

以 SourceGenerator 为例,我们来看下 startNext() 方法的处理流程。

image
1. 是否获取到了需要缓存的数据

当 SourceGenerator 加载完数据后,会再次进入 startNext() 方法,这时就获取到了需要缓存的数据。

2. 是否保存原始数据

如果磁盘缓存策略设定了要保存图片的原始数据,就用数据提取器加载数据,否则就直接把图片加载给 Target 。

3. 加载数据

当需要保存原始数据或数据有加载路径时,SourceGenerator 就会根据 Model 的类型,使用对应的 DataFetcher 来提取数据,比如从网络上下载图片。

4. 是否保存原始数据

当 SourceGenerator 获取到数据后,会再次判断是否要保存原始数据,否则就直接把图片加载给 Target 。

5. 编码

当 SourceGenerator 从 DataFetcher 中拿到数据后,会再走一遍 startNext() 方法,然后用编码器 Encoder 对数据进行编码,也就是把图片放到磁盘缓存中。

6. 从磁盘中获取数据

当 SourceGenerator 把数据保存到磁盘后,不会直接加载图片,而是从磁盘中拿这张图片,然后再进行加载。

2.4.4 onResourceDecoded()

image

当 DecodeJob 调用 ResourceDecoder 的 decode() 方法,并且获取到编码结果后,会调用 onResourceDecoded() 方法应用变换选项以及初始化编码管理器。

1. 应用变换选项

对于处理过的 Resource,onResourceDecoded() 不会再次进行变换,否则就会对图片进行变换操作。

2. 回收图片资源

当对资源应用了变换选项后,DecodeJob 会把原来的资源回收掉,因为这个资源接下来也用不上了。

3. 缓存变换后图片资源

onResourceDecoded() 方法中,会根据磁盘缓存策略判断是否要对资源进行编码,如果要进行编码的话,会根据不同的编码策略创建不同的 Key 。

Glide 有 SOURCE 和 TRANSFORMED 两种编码策略,分别代表对原始数据进行编码和对变换后资源进行编码。

  • SOURCE

    GIF 编码器 GifDrawableEncoder 中用的编码策略;

  • TRANSFORMED

    位图编码器 BitmapEncoder 中用的编码策略;

4. 初始化编码管理器

创建好 Key 后不会直接对图片进行编码,而是会修改编码管理器的 Key ,等到转码完成后再用 ResourceEncoder 进行编码。

3. 解码图片

[图片上传失败...(image-59f320-1623041354519)]

看完了解码任务启动流程,下面我们来看下当 DecodeJob 获取到图片数据后是怎么处理这些数据的。

其中关于 Target 在 1.2 小节已经讲过,下面就不再多讲了,我们来看下其他的对象。

Glide 对图片解码的过程涉及下面 6 个概念.

Glide 会以 Model 的形式封装图片来源 ,Model 可以是 URL、本地文件和网络图片等类型。

Glide 把数据源转换为Model 后,会把它加工成原始数据 Data ,一般就是输入流 InputStream ,而 ModelLoader 则负责从 Data 获取原始数据。

Glide 获取到原始数据后,会用资源解码器 ResourceDecoder 把原始数据 Data 解码为Resource,比如把输入流 InputStream 解码为 Bitmap。

Glide 会根据我们调用的变换选项用 Transformation 处理 Resource ,比如用 centerCrop() 裁剪就是一种变换选项,变换后的 Resource 就叫 TransformedResource ,负责变换的就是 Transformation 。

为了统一处理静态和动态图片,Glide 会用 ResourceTranscoder 把 Bitmap 转码为 GlideBitmapDrawable 。

最终会把图片显示到目标 Target 上,比如 ImageView 对应的就是 ImageViewTarget 。

3.1 ModelLoader

ModelLoader 是一个接口,负责创建 LoadData ,它有两个泛型参数 Model 和 Data。

  • Model

    代表图片来源的类型,比如图片的网络地址的 Model 类型为 String ;

  • Data

    代表图片的原始数据的类型,比如网络图片对应的类型为 InputStream ;

1. Factory

在 DataFetcherGenerator 获取图片数据时,会调用 ModelLoaderRegistry 的 getModelLoaders() 方法,这个方法中会根据 model 的类型用 MultiModelLoaderFactory 生成对应的 ModelLoader,比如能够解析字符串的 ModelLoader 就有 7 个,关于 ModelLoaderRegistry 在后面讲 Glide 配置的时候会讲到。

此外每一个 ModelLoader 的实现类中都定义了一个实现了 ModelLoaderFactory 接口的静态内部类 。

2. handles()

一个 Model 对应这么多 ModelLoader,每个 ModelLoader 加载数据的方式都不同,这时候就要用 handles() 方法了。

ModelLoader 接口有 handles() 和 buildLoadData() 两个方法,handles() 用于判断某个 Model 是否能被自己处理,比如 HttpUriLoader 的 handles() 会判断传进来的字符串是否以 http 或 https 开头,是的话则可以处理。

3. buildLoadData()

ModelLoader 之间是存在嵌套关系的,比如 HttpUriLoader 的 buildLoadData() 方法就是调用的 HttpGlideUrlLoader 的 buildLoadData() 方法,HttpGlideUrlLoader 会创建一个 HttpUrlFetcher ,然后把它放到 LoadData() 中。

LoadData 是 ModelLoader 中定义的一个类,它只是放置了图片来源的 Key 和要用来提取数据的 DataFetcher ,没有其他方法。

3.2 ResourceDecoder

DataFetcherGenerator 使用 ModelLoader 构建完数据后,就会用 DataRewinder 对数据进行重绕,也就是重置数据,比如 InputStreamRewinder 就会调用 RecyclableBufferedInputStream 的 reset() 方法重置输入流对应的字节数组的位置。

ResourceDecoder 是一个接口,有非常多的实现类,比如网络图片对应的解码器为 StreamBitmapDecoder ,StreamBitmapDecoder 的 decode() 方法调用了降采样器 Downsampler 的 decode() 方法,下图是 Downsampler 的解码逻辑。

image
1. 设置目标宽高

除非我们通过 override() 方法把尺寸改为 Target.SIZE_ROGINAL ,否则 Glide 默认会把 ImageView 的大小作为加载图片的目标宽高。

2. 计算缩放后宽高

根据不同的变换选项计算缩放后宽高。

3. 创建空 Bitmap

根据计算后的目标宽高创建一个空的 Bitmap 。

4. 使用 BitmapFactory 解码
image

Downsampler 的解码方式用的是 ImageReader 的 decodeBitmap() 方法,而 ImageReader 又调用了 BitmapFactory 的 decodeStream() 方法,BitmapFactory 最终调用的是 SkImageDecoder 的 decode() 方法。

5. 把 Bitmap 放入 BitmapPool 中

在前面讲 Resource 的时候讲到了 BitmapResource 中有一个 BitmapPool,这个 BitmapPool 是由 Downsampler 传过去的,而 Downsampler 的 BitmapPool 是由 Glide 创建并传进来的。

3.3 Transformation

Transformation 是一个接口,它有一个 transform() 方法,这个方法是在 DecodeJob 中调用的,当 DecodeJob 发现数据源不是缓存中的 Resource 时,就会调用变换选项的 transform() 方法。

Transformation 的其中一个实现类是 BitmapTransformation,我们平时调用的 centerCrop() 就是 BitmapTransformation 的子类,centerCrop() 选项对应的是 CenterCrop 类,它实现了 Transformation 接口,具体的变换实现在 TransformationUtils 中。

1. Matrix

以 centerCrop() 为例,TransformationUtils 的 centerCrop() 方法会先创建一个 Matrix 矩阵,然后根据传进来的 Bitmap 计算 Matrix 的缩放比例和平移坐标。

2. drawBitmap()

配置好 Matrix 后,就会根据目标宽高创建一个空的目标 Bitmap ,然后把原始 Bitmap、目标 Bitmap 和 Matrix 传给 Canvas 的 drawBitmap() 方法,然后返回 Canvas 处理好的图片。

3.4 ResouceTranscoder

ResourceTranscoder 是一个接口,是 Glide 中的资源转码器,它有两个泛型参数 Z 和 R ,分别代表需要进行原始类型和转码目标类型。

比如 BitmapDrawableTranscoder 的原始类型是 Bitmap,转码目标类型是 BitmapDrawable,在BitmapDrawableTranscoder 的 transcode() 方法中,会把 Bitmap 转换为 BitmapDrawable ,以便 Target 进行处理。

3.5 ResourceEncoder

ResourceEncoder 是一个接口,是 Glide 中的资源编码器,ResourceEncoder 有好几个实现类,比如网络图片对应的编码器为 StreamEncoder。

在转码完成后,DecodeJob 会先把图片加载到 Target 中,然后用 ResourceEncoder 对图片进行编码,比如 StreamEncoder 的编码操作就是把输入流 InputStream 转化为图片文件,然后保存到本地。

4. Glide 缓存原理

Glide 使用了三级缓存机制,图片的缓存分为内存、磁盘和来源,也就是从内存获取不到图片时,再去磁盘获取图片,从磁盘获取不到图片时,再从图片来源获取图片。

三级缓存的优势在于节省流量和内存,如果不用三级缓存,每次都从服务端获取图片的话,图片消耗的流量就会非常多,如果把所有图片都放在内存的话,那就有可能发生 OOM 。

下面我们来看下 Glide 的内存缓存原理、磁盘缓存原理和磁盘缓存策略。

4.1 Glide 内存缓存原理

前面提到 Engine 的 load() 方法会先在内存缓存中查找 Key 对应的资源,没有的话再启动新的解码任务。

这里说的内存缓存就是 MemoryCache,MemoryCache 是一个接口,它的实现类是 LruResourceCache。

LruResourceCache 不仅实现了 MemoryCache 接口,而且还是 LruCache 的子类,具体的内存缓存实现是在 LruCache 中。

image

在 LruCache 的 put() 方法中,首先会判断要保存的元素大小是否大于缓存最大值,如果是的话,则不进行保存,如果不是的话,则把当前容量加上元素的大小,并把该元素放入缓存。

LruCache 比较特别的就是它的 trimToSize() 方法和 LinkedHashMap 的 accessOrder 属性。

1. trimToSize()

LruCache 在用 put() 方法保存新的元素时,它会通过 trimToSize() 方法移除最近最少使用的元素。

2. accessOrder

LruCache 中是用 LinkedHashMap 保存数据的,并且这个 LinkedHashMap 的 accessOrder 的值为 true,也就是每一次获取 LinkedHashMap 中的元素时,这个元素都会被移到链表的尾端。

4.2 Glide 磁盘缓存原理

Glide 是用 DiskCache 保存图片文件的,DiskCache 是一个接口,这个接口中还定义了 Factory 和 Writer 两个接口,Writer 只是对 ResourceEncoder 的封装。

下面我们就来看看 DiskCache 和 DiskCache.Factory 的具体实现。

4.2.1 DiskLruCache

DiskCache 有两个实现类, DiskCacheAdapter 和 DiskLruCacheWrapper,DiskCacheAdapter 只是一个空实现。

从名字可以看得出来 DiskLruCacheWrapper 是对 DiskLruCache 的封装,具体的实现是在 DiskLruCache 中,DataCacheGenerator 和 ResourceCacheGenerator 都是用的 DiskLruCache 来获取磁盘缓存数据的。

1. Entry

和 LruCache 一样,DiskLruCache 中也有一个 LinkedHashMap ,这个 HashMap 的 Key 的类型为 String,Value 的类型为 Entry,从缓存中获取到的图片文件会放在 Entry的 cleanFiles 字段中。

2. Editor

当图片加载进入编码阶段时,DecodeJob 会通过编码管理器调用 DiskLruCacheWrapper 的 put() 方法保存图片文件。

在 DiskLruCacheWrapper 的 put() 方法中,会通过 DiskCache 的缓存编辑器 Editor 获取图片文件,获取到图片文件后,就会用 Writer 把文件写入本地,写完后再调用 Editor 的 commit() 方法,把清理缓存的回调提交到清理线程池中。

3. 清理资源

DiskLruCache 中有一个执行清理资源任务的线程池,线程池的线程数最多为 1,

这个线程池要执行的任务为 cleanupCallback 回调,这个回调会执行 trimToSize() 方法,为的就是把最近最少使用的文件清除掉。

4.2.2 DiskCache.Factory

在 DiskCache 中有一个 Factory 工厂接口,这个接口用在了 Engine 的 LazyDiskCacheProvider 中。

在 Factory 接口中,定义了默认的磁盘缓存大小为 250M,默认的缓存目录名称为 "image_manager_disk_cache" 。

Factory 主要有下面 2 个实现类。

  • ExternalPreferredCacheDiskCacheFactory

    用的是 getExternalCacheDir() 。

    对应的目录是 /data/user/0/包名/cache/image_manager_disk_cache。

  • InternalCacheDiskCacheFactory

    用的是 context.getCacheDir() 。

    对应的目录是 /data/user/0/包名/cache 。

默认情况下 Glide 用的是 InternalCacheDiskCacheFactory ,如果想把图片放在外部缓存目录的话,可以在自定义的 GlideModule 设置 DiskCache 。

4.3 Glide 磁盘缓存策略

在加载图片时,我们可以用 diskCacheStratgy() 方法设置图片在磁盘的缓存策略,这个选项传入的参数类型为抽象类 DiskCacheStrategy。

磁盘缓存策略涉及到 Glide 的数据源类型 DataSource 和编码策略 EncodeStratefy,编码策略前面讲过了,下面我们先来看看数据源 DataSource。

4.3.1 五种数据源

Glide 中定义了下面 5 种数据源 DataSource。

  • LOCAL

    设备上有的数据,比如 App 内置的 Drawable 也属于 LOCAL ;

  • REMOTE

    从服务端拿到的数据;

  • DATA_DISK_CACHE

    从缓存中取出来的原始数据;

  • RESOURCE_DISK_CACHE

    从缓存中取出来的图片资源;

  • MEMORY_CACHE

    从内存缓存中取出来的数据;

4.3.2 四个抽象方法

DiskCacheStrategy 有下面 4 个抽象方法,这个 4 个方法的返回值都是布尔值。

  • isDataCacheable()
  • isResourceCacheable()
  • decodeCachedResource()
  • decodeCacheData()
1. isDataCacheable()

是否保存图片的原始数据。

DecodeJob 中用到的 SourceGenerator 在从图片来源获取到数据后,会根据这个方法判断是否保存图片的原始数据。

2. isResourceCacheable()

是否保存解码后的图片数据。

当资源解码器对图片数据进行解码后,DecodeJob 就会根据这个方法的返回值决定是否保存该 Resource 。

3. decodeCachedResource()

是否对缓存的解码后的图片数据进行解码。

在 DecodeJob 的 getNextStage() 中,会根据这个方法的返回值判断,如果返回值为 false,意味着跳过 RESOURCE_CACHE 步骤,也就是不对缓存中处理过的图片资源进行处理。

4. decodeCachedData()

是否对缓存的原始数据进行解码。

在 DecodeJob 的 getNextStage() 方法中,会根据这个方法的返回值判断,如果该值为 false,意味着跳过 DATA_CACHE 步骤,也就是不对缓存中的原始图片数据进行处理。

4.3.2 五种缓存策略

Glide 定义好的磁盘缓存策略有下面 5 种,默认为 AUTOMATIC。

  • AUTOMATIC
  • ALL
  • NONE
  • RESOURCE
  • DATA
1. AUTOMATIC
  • isDataCacheable()

    只保存网络图片的原始数据;

  • isResourceCacheable()

    只保存数据源为 DATA_DISK_CACHE 或 LOCAL ,并且编码策略为 TRANSFORMED 的图片资源;

  • decodeCachedResource()

    true;

  • decodeCachedData()

    true;

2. ALL
  • isDataCacheable()

    只保存网络图片的原始数据;

  • isResourceCacheable()

    不保存数据源为 RESOURCE_DISK_CACHE 和 MEMORY_CACHE 的图片资源;

  • decodeCachedResource()

    true;

  • decodeCachedData()

    true;

3. DATA
  • isDataCacheable()

    不保存数据源为 DATA_DISK_CACHE 或 MEMORY_CACHE 的图片资源;

  • isResourceCacheable()

    false;

  • decodeCachedResource()

    false;

  • decodeCachedData()

    true;

4. RESOURCE
  • isDataCacheable()

    false;

  • isResourceCacheable()

    不保存数据源为 RESOURCE_DISK_CACHE 和 MEMORY_CACHE 的图片资源;

  • decodeCachedResource()

    true;

  • decodeCachedData()

    false;

5. NONE

所有方法的返回值都为 false。

4.4 BitmapPool

1. 减少 Bitmap 占用的内存

BitmapResource 的 BitmapPool 用的就是 GlideBuilder 中的 BitmapPool,Downsampler 在解码后,会把图片放入 BitmapPool 中,当 BitmapResource 被回收时,也会把 Bitmap 放到 BitmapPool 中。

具体需要用到 BitmapPool 中的 Bitmap 的地方在 TransformationUtils 中,TransformationUtils 在进行变换前会从 BitmapPool 中获取之前保存的 Bitmap。

之所以要这么做,是因为每一次变换都需要创建一个 Bitmap ,BitmapPool 就是为了复用这个 Bitmap 占用的内存,这样下次要做变换操作时,可以用同一个 Bitmap 就进行复用,以减少内存使用。

比如对于 RecyclerView 中的图片,它们的大小是一样的,没必要在变换时为每张图片都创建一个新的 Bitmap。

2. LruPoolStrategy

BitmapPool 是一个接口,实现类为 LruBitmapPool ,具体的逻辑在 LruPoolStrategy 中。

LruPoolStrategy 也是一个接口,它的实现类为 SizeConfigStrategy,从 LruPoolStrategy 的名字可以看得出来,BitmapPool 用的是 LruCache 来保存 Bitmap 的。

在 LruPoolStrategy 中,会根据 Bitmap 的大小和编码选项,把 Bitmap 放到 GroupedLinkedHamp 中。

3.5 ArrayPool

和 BitmapPool 一样,ArrayPool 用的也是 LruCache,也是为了减少不必要的内存浪费。

比如在输入流编码器 StreamEncoder 中,当把输入流转化为文件时,需要创建一个新的字节数组,如果不用 ArrayPool,而图片是在列表中加载的,那就会创建很多不必要的的字节数组。

5. Glide 初始化流程与配置

5.1 Glide 初始化流程

在看 Glide 的配置前,我们先来看下 Glide 的初始化流程,因为读取配置就是在初始化的过程中读取的。

5.1.1 with()

image

当我们调用 Glide.with() 方法时,Glide 会先用 getRetriever() 方法获取请求管理器检索器,在这个方法中还会用 get() 方法获取 Glide 实例,获取不到的话就会初始化 Glide 。

5.1.2 initializeGlide()

image

我们可以在 AndroidManifest 中声明 GlideModule,也可以用 @GlideModule 注解声明 GlideModule,走的都是上面这个流程。

1. 应用选项

Glide 有一个 ApppModuleGenerator,它会把读取我们设定的 AppGlideModule 中的配置,然后生成一个 GeneratedAppGlideModuleImpl 配置。

然后用反射读取这个配置,读取到配置后,就会应用我们在 applyOptions() 中给 GlideBuilder 设置的选项。

如果不用生成加反射的话读取配置的话,Glide 并不知道我们会把配置叫什么,放哪里。

2. 创建实例

应用选项后,就会创建一个 Glide 实例。

3. 注册组件

创建完实例后,就会把实例的 registry 传到 registerComponents() 中,也就是我们修改编解码逻辑的地方。

4. 注册回调

Glide 实现了 ComponentCallback 用于监听内存状态,这里的注册回调就是调用 ApplicationContext 的 registerComponentCallbacks() 方法。

5.2 Registry

Glide 有一个登记处 Registry ,它包含了下面这些 Registry 。

  • 数据加载器登记处 ModelLoaderRegistry
  • 编码器登记处 EncoderRegistry
  • 资源解码器登记处 ResourceDecoderRegistry
  • 资源编码器登记处 ResourceEncoderRegistry
  • 数据重绕器登记处 DataRewinderRegistry
  • 转码器登记处 DataRewinderRegistry
  • 图片头部信息解析器登记处 ImageHeaderRegistry

在 Glide 的构造方法中,会把所有的编码、解码和数据加载等逻辑通过 Registry 的 append() 方法登记到 Registry 中,我们可以在 AppGlideModule 的 registerComponents() 方法中获取到 registry 实例,通过这个实例就可以替换掉对应的实现。

1. registerComponents()

比如获取网络图片默认用的是 HttpUrlFetcher ,HttpUrlFetcher 是用的 HttpURLConnection 来获取图片数据的,我们可以在 registerComponents() 方法中,把 HttpUrlFetcher 替换为 OkHttp 。

2. Entry
image

之所以要用 Class 来替换 ModelLoader ,是因为 ModelLoaderRegistry 的 append() 方法会用来源类型(Model)、原始数据类型(Data)和 ModelLoaderFactory 来创建不同类型的 Entry ,这些 Entry 会保存在 MultiModelLoaderFactory 工厂中。

当 DataFetcherGenerator 通过 ModelLoader 获取数据时,则会通过 model 的 Class 信息来获取 ModelLoader,除了 ModelLoaderRegistry,其他的 Registry 中也有 Entry。

5.3 GlideBuilder

GlideBuilder 就是 Glide 的构建器,它包含了下面这些数据。

  • 线程池
    • 图片来源线程池 sourceExecutor
    • 磁盘缓存线程池 diskCacheExecutor
    • 动画线程池 animationExecutor
  • 内存大小计算器 memorySizeCalculator
  • 网络状态监听器工厂 connectivityMonitorFactory
  • 请求选项工厂 defaultRequestOptionsFactory
  • 请求管理器工厂 requestManagerFactory
  • 请求监听器列表 defaultRequestListeners
  • 位图池 bitmapPool
  • 数组池 arrayPool
  • 内存缓存 MemoryCache
  • 磁盘缓存工厂 diskCacheFactory
  • 引擎 Engine

上面这些字段大多数都是可以在 AppGlideModule 的 applyOptions() 方法中,调用 GlideBuilder 的 setXXX() 方法来替换实现的,下面我们主要看下 Glide 线程池和内存大小计算器。

5.3.1 Glide 线程池

GlideExecutor 是 Glide 的线程池实现,Glide 中有下面 4 种线程池。

1. SourceExecutor

对图片来源解码的任务的线程池,线程数为最多为 4 ,最小为设备的 CPU 核数。

2. unlimitedSourceExecutor

如果我们在加载图片时调用了 useUnlimitedSourceGeneratorsPool() 选项,那 Glide 就会用这个无线程数限制的线程池来获取图片。

3. DiskCacheExecutor

对磁盘缓存数据解码的任务的线程池,线程数为 1 。

4. AnimationExecutor

这也是用来从图片来源获取数据的线程池,而不是用来播放 GIF 动画的线程池,当我们加载图片时调用了 useAnimationPool(true) ,那在获取图片数据时 EngineJob 就会把 DecodeJob 放到 AnimationExecutor 中。

如果设备 CPU 核数大于等于 4 ,那 AnimationPool 线程数就是 2 ,否则就是 1 。

5. 自定义线程池

Glide 对于线程池只允许用 GlideBuilder 中的 Builder 来设置参数,GlideExecutor.Builder 支持下面几个参数。

  • setThreadTimeoutMillis()

    设置线程存活时间;

  • setThreadCount()

    设置线程数;

  • setUncaughtThrowableStrategy()

    设置异常处理策略,有三种可以选,也可以自己自定义,默认为 LOG。

    • IGNORE

      忽略异常;

    • LOG

      打印异常日志;

    • THROW

      抛出异常;

  • setName()

    设置线程名称;

5.3.2 内存大小计算器

MemorySizeCalculator 负责计算 BitmapPool 、ArrayPool 和 MemoryCache 的大小。

1. BitmapPool 大小

BitmapPool 大小为一屏可容纳的最高图片质量的大小,比如 1080 * 1920 * 4 ≈ 7.9M 。

2. ArrayPool 大小

默认为 4M,如果系统版本低于 19 则为 2M。

3. 内存缓存大小

内存缓存大小为两屏可容纳的最高图片质量的大小,比如 1080 * 1920 * 2 * 4 ≈ 15.8M 。

4. maxSizeMultiplier

MemorySizeCalculator 在计算 BitmapPool 和 MemoryCache 大小时,会通过 getMaxSize() 方法,用 ActivityManager 获取 memoryClasss,然后用 memoryClass 的值乘以 maxSizeMultiplier,maxSizeMultiplier 默认为 0.4。

memoryClass 就是获取应用可用内存大小,比如我的 VIVO 手机给应用分配的可用内存为 256M,以我的手机为例,256 * 0.4 = 102.4 ,也就是默认情况下, BitmapPool 、ArrayPool 和 MemoryCache 的大小最多不会超过 102.4M。

当 BitmapPool 、ArrayPool 和 MemoryCache 的大小加起来大于最大值时,会按这个最大值重新计算 BitmapPool 和 MemoryCache 的大小。

5. 自定义内存大小计算方式

和 GlideExecutor 一样,MemorySizeCalculator 也有一个 Builder,支持下面这些参数的设置。

  • setMemoryCacheSizeScreens(screens)

    设置内存缓存大小为几屏的大小;

  • setBitmapPoolScreens(screens)

    设置 BitmapPool 大小为几屏的大小;

  • setArrayPoolSize(sizeBytes)

    设置 ArrayPool 大小;

  • setMaxSizeMultiplier()

    设置最大值乘数大小;

6. Glide 图片加载选项

1. placeholder(drawable)

如果用户打开 App 的时候,本来应该显示图片的控件,由于网络原因等了好几秒都没加载出来,这样用户体验就不好,所以我们可以加上一张占位图,这样用户就知道图片等下就出来了。

这里要注意的是,占位图只能是 App 内置图片,不能是网络图片,否则无网络的时候它就没作用了。

2. error(drawable)

当用户的网络出现错误,图片加载失败时,一直显示占位图,用户就会一直等待,如果等了半天都没加载出来,用户就会觉得我们的 App 有问题。

这时候我们可以用一张错误占位图,这样用户就知道有可能是网络出问题了,切换一下网络,又或者是主动联系开发者。

如果没有设置这个参数的话,出错时会显示 placeholder 中传的占位图。

3. fallback()

设置当数据模型为空,也就是我们传入 load() 中的值为空时要显示的图片,没有设置 fallback 会显示错误占位图,连错误占位图也没设置就会显示 placeholder 占位图。

4. override(width, height)

如果我们不想让 Glide 把图片按 ImageView 的大小进行缩放,我们可以用这个方法来设置加载的目标宽高。

5. fitCenter()
image

fitCenter 是一个图片裁剪选项,用于把图片尺寸限定在 ImageView 内并居中,这样图片就能完全显示。

选择这个选项后,当图片的宽高比和 ImageView 的宽高比不同时,ImageView 就不会被填满。

6. centerCrop()

当我们使用了 centerCrop (),并且图片宽高比与 ImageView 不同时,Glide 会裁剪中间的部分,以填满 ImageView ,这时图像就不是完全显示的了。

7. centerInside()

与 fitCenter 类似,不同的是,当 ImageView 的尺寸为 wrap_content 时,fitCenter() 会把图片放大,而 centerInside() 则会保持原图大小。

image
8. transform(Transformation)

设置变换选项,比如旋转选项 Rotate 和圆角选项 RoundedCorners 没有对应的方法可以直接设置,就可以用这个方法传进去。

如果想要同时多种变换选项,也要从这个参数传进去,比如 transform(CenterCrop(), RoundedCorners()) ,这样创建的就是多重变换 MultiTransformation ,否则只有后面设置的变换选项会起效。

9. dontTransform()

禁止变换,调用这个方法后会删除之前设定的变换选项。

10. skipMemoryCache(boolean)

跳过内存缓存。

默认情况下 Glide 会把图片放在内存缓存中,如果我们不想要让某张图片保留在内存缓存中,比如加载高清原图时,可以把这个值改为 true 。

11. diskCacheStrategy(strategy)

磁盘缓存策略,Glide 自带了 5 种磁盘缓存策略,默认为 AUTOMATIC。

  • ALL
  • NONE
  • DATA
  • RESOURCE
  • AUTOMATIC
12. onlyRetrieveFromCache(boolean)

如果传 true ,表示不从图片来源获取数据,只从缓存中读取数据。

13.priority

设置加载图片的优先级,比如运营图的优先级就比其他图片高,Glide 的加载优先级有下面四种。

  • IMMEDIATE

    立即加载;

  • HIGH

    高优先级;

  • NORMAL

    正常优先级;

  • LOW

    低优先级;

14. sizeMultiplier(float)
image

按比例缩放图片。

15. encodeFormat(CompressFormat)

设置下载或缓存的图片的编码类型,比如 JPEG、PNG、WEBP。

如果没有设置 encodeFormat,并且图像有透明通道,那 Glide 默认会把以 PNG 的方式保存图片,否则就是 JPEG。

16. encodeQuality(int)

设置下载或缓存的图片的编码质量,这个参数会传到 Bitmap.compress() 方法中。

17. frame(long)

取视频中某一帧的作为图片。

18. format(DecodeFormat)

设置解码格式,比如 ARGB_8888、RGB_565 。

19. timeout(milliseconds)

设置网络图片的请求超时,默认为 2500 毫秒。

20. transition(options)

设置过渡动画,这里的 options 传的是 TransitionOptions ,这是一个抽象类,它有三个子类。

  • GenericTransitionOptions.with()

    自定义动画。

  • DrawableTransitionOptions.withCrossFade()

    设置图片淡入动画。

  • BitmapTransitionOptions.withCrossFade()

    如果调用了 asBitmap() 方法,就要用这个过渡动画。

21. dontAnimate()

不播放 GIF 动画 。

22. apply(options)

应用选项。

23. listener(RequestListener)

设置请求监听器 ,这个接口有下面两个回调。

  • onResourceReady()

    图片加载完成回调。

  • onLoadFailed()

    图片加载失败回调。

24. asBitmap()

把解码类型改为 Bitmap 。

这个方法并不是 BaseRequestOptions 提供的,而是 RequestManager 中的,但是对于外部使用者来说,这就是一个加载选项,不同的是,这个选项需要在 load() 方法前调用。

25.asFile()

把解码类型改为 File,也就是用 submit().get() 获取到的类型为 File 。

26. submit()

我们可以用 submit() 方法来获取我们想要的图片类型,比如文件、Bitmap 和 Drawable 等,但是要注意的是这个方法要在子线程中执行。

27. downloadOnly()

设定只下载图片,不加载图片。

这个选项就是 asFile() + diskCacheStrategy(DATA) + priority(LOW) + skiptMmoeryCache(true) 。

28. download()

这个方法相当于是用 download(image) 替换 downloadOnly().load(image) 。

29. preload(width, height)

预加载图片,可以用来提前下载一些下一次启动应用的时候会用到的图片,比如闪屏页广告。

和 downloadOnly() 的区别在于不需要在子线程调用。

参考资料

其他

如果你想交流 Android 开发相关的问题,欢迎加我的微信 oushaoze2015 一起探讨,添加时请备注“掘金”。

本文已投稿公众号 code小生

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