Glide

Glide加载图片流程

Glide.with(context).load(String).into(xx)

这一行代码干了多少事,其完整的流程:

With()

Glide.with(context) 生成感知生命周期的requestManager;

1
2
3
1 with(context) 
调用getRetriever(activity).get(activity);
通过获取RequestManagerRetriever,拿到其fragmentManager(如果context是fragment的话拿其宿主Activity的fragmentManager),向其中塞一个隐性fragment,用于监听生命周期的变化,并在暂停、停止、销毁时执行对应的行为

Load()

RequestManager.load()确定解码类型并构建requestBuilder

1
2
3
4
5
6
7
2 load(string)多个重载,以String为例(没有手动调用asXXX()的情况下)
2.1 调用asDrawable().load(string),先asDrawable设定resourceClass类型为Drawable.class,再调用
load(String) {
this.model = model;
isModelSet = true;
}
设定load模型 (model)

Into()

image-20241017165028811

RequestBuilder.into()确定transformations、构建ViewTarget、主线程Handler的Executor后,构建SingleRequest对象,将request对象保存到ViewTarget中,之后调用requestManager.track(target, request)中执行request.begin()开始正式加载流程。

Into(ImageView)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
3 into(ImageView) 多个重载,以ImageView为例
3.0.1 clone一份当前的requestOption,根据scaleType(默认为FIT_CENTER)通过requestOptions.clone().optionalFitCenter()保存一些transformations和Options

3.0.2
into(
glideContext.buildImageViewTarget(view, transcodeClass), // transcodeClass为Drawable.class(默认),构建一个DrawableImageViewTarget对象(extends ImageViewTarget extends ViewTarget),ViewTarget会持有ImageView
/*targetListener=*/ null,
requestOptions, // 3.0.1中clone出来的requestOptions对象,其中保存了一些transformation配置
Executors.mainThreadExecutor()); // 主线程Handler的Executor,传递给engine使用,在job完成时callback回主线程

3.1 buildRequest(target, targetListener, options, callbackExecutor); //根据3.0.2构建的Target、requestOptions、targetListener为null、主线程回调的Executor,构建SingleRequest对象

3.2 target.setRequest(request); // target.setTag() 调用View.Tag,将3.1构建的request存到View中

3.3 requestManager.track(target, request); // 主要是调用requestTracker.runRequest(request) -> request.begin();开始加载流程

3.4 request.begin()
3.4.0 如果model为空,直接set error(优先)或者Placeholder返回
3.4.1 如果这时候能拿到target(ImageView)的宽高就走onSizeReady()开始engine.load()
3.4.2 如果这时候宽高还未测量完成,则走target.getSize(this)注册一个ViewTreeObserver,在addOnPreDrawListener回调回来后再掉onSizeReady()
3.4.3 置状态为RUNNING,status = Status.RUNNING;
3.4.4 engine.load()启动 尝试读取缓存、下载、解码等步骤,见4详解
3.5 target.onLoadStarted(getPlaceholderDrawable()); 加载占位图

Engine.load()

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
4 engine.load()

4.1 先读内存缓存
内存缓存:先读 activeResource,再读memoryCache

4.2 内存缓存都没有,就走Engine job启动decode job的流程:engineJob.start(decodeJob);
4.2.1 engineJob和decodeJob都是通过池化技术(实现Poolable接口)获取,减少创建带来的内存和时间消耗

4.3 engineJob.start会在磁盘线程池diskCacheExecutor中执行decodeJob的run()方法(允许磁盘缓存情况下RESOURCE_CACHE/DATA_CACHE)
4.3.1 run()->runWrapped()中通过runGenerators()执行状态机
将根据缓存策略依次执行
ResourceCacheGenerator (缓存策略为ALL、AUTOMATIC、RESOURCE,即该策略下decodeCachedResource()返回为true)
-> DataCacheGenerator (缓存策略为ALL、AUTOMATIC、DATA,即该策略下decodeCachedData()返回为true)
-> SourceGenerator (若当前禁止磁盘缓存或磁盘缓存不存在)

private void runGenerators() {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
while (!isCancelled
&& currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();

if (stage == Stage.SOURCE) {
reschedule();
return;
}
}
// We've run out of stages and generators, give up.
if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
notifyFailed();
}

// Otherwise a generator started a new load and we expect to be called back in
// onDataFetcherReady.
}
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE
: getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE
: getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
// Skip loading from source if the user opted to only retrieve the resource from cache.
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE:
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}


4.3.2 DataFetcherGenerator的执行流程

没有缓存的情况举例:
4.3.2.1 通过状态机初始状态下,第一次进到DataCacheGenerator,由于不存在缓存,decodeJob会被重新加到sourceExecutor中执行,来到SourceGenerator.

4.3.2.2 SourceGenerator.startNext()中遍历当前注册表已注册的loader,glideContext.getRegistry().getModelLoaders,依次尝试寻找对应的ModelLoader:
匹配到的是GlideUrl.class对应的 OkHttpUrlLoader -> OkHttpStreamFetcher,调用okhttp3
(默认其实是 HttpGlideUrlLoader -> HttpUrlFetcher,调用HttpURLConnection的API)

4.3.2.2.1 请求接口返回回来之后,赋值变量 dataToCache 同时调用cb(DecodeJob).reschedule();

4.3.2.2.2 赋值runReason为SWITCH_TO_SOURCE_SERVICE 同时调用callback(EngineJob).reschedule(this)//decodeJob,将解析任务decodejob加入getActiveSourceExecutor中执行。

4.3.2.3 之后就又走到了SourceGenerator.startNext(),
区别是这次runWrapped()中runReason是SWITCH_TO_SOURCE_SERVICE同时SourceGenerator的dataToCache不为空。
于是又走SourceGenerator.startNext()中cacheData(data);的逻辑,将接口返回源数据后写入磁盘缓存

4.3.2.4 之后主动执行DataCacheGenerator.startNext()进行源数据磁盘缓存读取操作,此时通过匹配到的 ByteBufferFileLoader.loadData进行文件随机读取操作从磁盘文件中读取出数据。

4.3.2.5 读取到源数据之后通过DecodeJob.onDataFetcherReady将源数据回到到DecodeJob中执行Decode
解码操作
4.3.2.6 解码后又通过EngineJob的回调,执行activeResouce缓存写入和任务资源之类的回收操作
4.3.2.7 同时通过SingleRequest的回调,调用Target的setResource即最后调用ImageView.setImageDrawable

Generator具体执行流程

在DecodeJob的getNextGenerator被执行到时,Generator会被初始化,初始化时,Generator会通过调用decodeHelper.getCacheKeys -> decodeHelper.getLoadData -> glideContext.getRegistry().getModelLoaders(model);
对 MultiModelLoaderFactory 中对 Register 注册表中已注册的所有entries的遍历,寻找与model类型匹配的Entry(包含modelClass、dataClass、具体loader的factory类)。
//注:此处的Register中可以是自己注册 model -> factory 的映射,也可能是Glide默认注册的那些。默认的model为String,启用okhttp的情况下映射到的是 OkHttpUrlLoader 的factory类。最后走OkHttpStreamFetcher请求网络
//注: String类型的model 在 StringLoader 中会先被解析成Uri,然后包装成 GlideUrl 类然后重新寻找对应的loader,也就是OkHttpUrlLoader
//注: 注册表 Register中,注册时 Key 为model的类型,value为 ModelLoaderFactory,ModelLoaderFactory build -> ModelLoader buildLoadData -> LoadData

以磁盘缓存模式为DATA_CACHE为例:
generator的执行顺序是 ResourceCacheGenerator(若有) -> DataCacheGenerator(若有) -> SourceGenerator(若不存在磁盘缓存)

第一次 decodejob 执行是在 diskCacheExecutor 线程池中
此时,首次进入runWrapped -> case INITIALIZE -> runGenerators() 时,执行的是 DataCacheGenerator.startNext(),由于此时磁盘缓存还不存在(所以也没法helper.getModelLoaders(cacheFile)),startNext返回false,走到下一个stage和generator,也就是 stageStage.SOURCE 并currentGenerator为SourceGenerator,当 stage == stageStage.SOURCE ,走到decodeJob的 reschedule(RunReason.SWITCH_TO_SOURCE_SERVICE),此时decodejob会被加入到 sourceExecutor 线程池中重新执行。

第二次 decodejob 执行是在 sourceExecutor 线程池中
此时,重新进入runWrapped -> case SWITCH_TO_SOURCE_SERVICE -> runGenerators() 时,执行的是 SourceGenerator.startNext(),遍历当前符合的所有 ModelLoader.LoadData(其实就是一个,fetcher为OkHttpStreamFetcher的LoadData对象),最后执行loadData.fetcher.loadData() 走到 OkHttpStreamFetcher.loadData() 调用okhttp下载数据,并监听 onDataReady() 和 onLoadFaild() 回调。
onDataReady()下载完成后,数据赋值给 SourceGenerator.dataToCache ,之后又 reschedule(RunReason.SWITCH_TO_SOURCE_SERVICE) 到sourceExecutor线程池中重新执行。

第三次 decodejob 执行依然是在 sourceExecutor 线程池中
此时,依然是runWrapped -> case SWITCH_TO_SOURCE_SERVICE -> runGenerators() ,SourceGenerator.startNext(),由于此时dataToCache不为空,走 SourceGenerator.startNext() 中先Encode数据然后存储磁盘缓存数据的逻辑,即调用 SourceGenerator.cacheData() 将源数据通过decodeHelper.getDiskCache.put(key = new DataCacheKey(loadData.sourceKey, helper.getSignature(), value = new DataCacheWriter<>(encoder, data, helper.getOptions()))写入磁盘缓存。同时构建一个 DataCacheGenerator执行其 startNext()。
DataCacheGenerator.startNext()中,此时由于已经写过磁盘缓存了,cacheFile不为空,于是走到helper.getModelLoaders(cacheFile)得到缓存文件的读取modelLoaders(此时有四个ByteBufferFileLoader,FileLoader$StreamFactory、 FileLoader$FileDescriptorFactory、UnitModelLoader)
根据当前的resourceClass和transcodeClass,以及loadData的dataClass(ByteBuffer)确定为由ByteBufferFileLoader处理,执行ByteBufferFileLoader.loadData。之后借助ByteBufferUtil类随机读取的方式,从磁盘文件中读取源数据的ByteBuffer,之后数据回调到 DecodeJob.onDataFetcherReady(),然后执行 decodeFromRetrievedData()解码源数据 (注,此时还在sourceExecutor线程池中)。
通过 20241009145727 这一串调用之后,走到遍历 decodePaths 尝试解析的步骤,此时一般有(AnimatedImageDecoder, ByteBufferGifDecoder, BitmapDrawableDecoder以及自定义的比如AvifBufferBitmapDecoder),任何一个decoder解码成功后即结束这段逻辑(decode时会执行Downsampler类的逻辑进行采样缩放并且解码),

image-20241009174652194

重新走到 DecodeJob: decodeFromRetrievedData() -> notifyEncodeAndRelease() -> notifyComplete() ->

  1. EngineJob: onResourceReady() -> notifyCallbacksOfResult() -> onEngineJobComplete()
    此处执行活动缓存的写入逻辑,activeResources.activate(key, resource); 之后就是资源回收任务结束移除之类的收尾工作。

  2. CallResourceReady.run() -> engineResource.acquire();callCallbackOnResourceReady(cb); -> SingleRequest.onResourceReady() -> ImageViewTarget.onResourceReady() -> ImageViewTarget.setResourceInternal() -> DrawableImageViewTarget.setResource() -> view.setImageDrawable();
    此处是将解码后的图片资源直接通过view.setImageDrawable显示出来。

Tips

  • Glide是先将图片下载到磁盘,然后再从磁盘中读取。之后decode使用
  • 不存在磁盘缓存需要fetch的情况下:存储和读取磁盘数据,包括后续的磁盘取出后解码操作 是在sourceExecutor线程池中进行的(即是都在上述的第三步中完成)
  • 存在磁盘缓存的情况下:读取磁盘数据和解码数据 是在diskCacheExecutor线程池中进行的。

整体流程参考:

img

Glide缓存

三级缓存:

磁盘(DiskLruCache) -> LRUCache(不活跃资源) -> ActiveResources(使用中资源WeakReference)

内存缓存分为 ActiveResources弱引用 的和 LruCache ,其中正在使用的图片使用弱引用缓存,暂时不使用的图片用 LruCache缓存,这一点是通过 图片引用计数器(acquired变量)来实现的

内存缓存

内存缓存的key是由model, signature, width, height, transformations, resourceClass, transcodeClass, options等因素共同组成的,因此,不同的宽高、变换之类的因素会生成不同的内存缓存。

活动缓存

活动缓存(activeResources: Map<Key, ResourceWeakReference>)

key为EngineKey对象,value为弱引用的图片缓存ResourceWeakReference实例

正在使用的图片缓存(被ImageView或Activity引用的),在gc或所在Activity(fragment宿主activity)销毁时被移到内存缓存

(为什么这一层缓存要用弱引用缓存的方式:一个是这一层缓存因为没有限制缓存大小,用弱引用如果遇到gc即可降级;一个是)

  • 资源被包装成带引用计数的EngineResource,标记引用资源的次数(当引用数acquired不为0时阻止被回收或降级,降级即是存储到LruCache中)
  • 这一级缓存没有大小限制,所以使用了资源的弱引用
  • 存:每当下载资源后会在onEngineJobComplete()中存入ActiveResource,或者LruCache命中后,将资源从中LruCache移除并存入ActiveResource。
  • 取:每当资源释放时,会降级到LruCache中(请求对应的context onDestroy了或者被gc了)
  • 开一个后台线程,监听ReferenceQueue,不停地从中获取被gc的缓存,将其从ActiveResource中移除,并重新构建一个新资源将其降级为LruCache(这一步并不会影响页面展示,图片内存没有销毁,只是被降级到有大小限制的LruCache中)
  • ActiveResource是为了缓解LruCache中缓存造成压力,因为LruCache中没有命中的缓存只有等到容量超限时才会被清除,强引用即使内存吃紧也不会被gc,现在当LruCache命中后移到ActiveResource,弱引用持有,当内存吃紧时能被回收。

活动缓存是感知生命周期的

​ 当绑定的context销毁onDestory时,RequestManager会将该事件传递给RequestTracker,然后触发该请求Resource的clear,再调用Engine.release,将改activiteResource降级到LruCache

LRU缓存LRUCache

LRU缓存LRUCache(memoryCache: LruCache<Key, Resource<?>>)

key为EngineKey对象,value为图片缓存Resource实例

当图片不再显示时,图片会从活动缓存移到内存缓存。存储的是暂时不用的图片缓存(依靠图片引用计数器acquired变量实现,当acquired大于0时存在于活动缓存中,为0是移到内存缓存中

  • 使用 LinkedHashMap实现, 存储从活跃图片降级的资源,使用Lru算法淘汰最近最少使用的

    Map<T, Y> cache = new LinkedHashMap<>(100, 0.75f, true);

  • 存:从活跃图片降级的资源(退出当前界面,或者ActiveResource资源被回收)

  • 取:网络请求资源之前,从缓存中取,如没命中活动资源则取活动资源,没命中活动资源则取内存缓存LRUCache,若命中则直接从LruCache中移除了,没命中则走磁盘或网络。

  • 内存缓存最大空间(maxSize) = 每个进程可用的最大内存(activityManager.getMemoryClass() * 0.4

    (低配手机的话是: 每个进程可用的最大内存 * 0.33)

    activityManager.getMemoryClass值:

    • 低端设备可能在 16-32 MB 范围内。
    • 中档设备通常在 64-128 MB 范围内。
    • 高端设备和新款设备可能在 256 MB 或更高。//当设置largeHeap时,最多可申请512M

磁盘缓存(已证)

对于远程资源:DATA/AUTOMATIC 策略下的磁盘缓存的key只由url(和signature,但一般没有)决定。

RESOURCE 策略下的磁盘缓存的key决定因素还要包括 宽高、变换等多种因素

磁盘缓存(diskLruCache: LinkedHashMap<String, Entry>)

  • 缓存Key Value:

key为 经过Sha256算法对 DataCacheKey(GlidUrl和signature)或ResourceCacheKey(GlideUrl、singnature、width、

height、transformation等多个因素) 摘要后 的64位字符串,value为File实例

  1. 在内存中用LinkedHashMap实现的LruCache记录一组Entry,Entry内部包含一组文件,文件名即是key,并且有开启后台线程执行删除文件操作以控制磁盘缓存大小
  2. 写磁盘缓存即是触发Writer将数据写入磁盘,并在内存构建对应的File缓存在LinkedHashMap中
  3. 磁盘缓存的存储顺序记录在journal文件中,有时该文件过大会导致glide初始过慢。
  • 磁盘缓存大小默认250MB,根据缓存策略的不同可能存储原始图片或解码后的图片。

DiskCacheStrategy.ALL : //表示既缓存原始图片,也缓存decode过后的图片。

DiskCacheStrategy.NONE: //表示不缓存任何内容。

DiskCacheStrategy.RESOURCE: //表示只缓存decode过后的图片,依然编码为jpeg/png等格式存储

DiskCacheStrategy.DATA: //表示只缓存原图片(decode前)。 如webp/avif

DiskCacheStrategy.AUTOMATIC //(默认选项)Remote资源下走DiskCacheStrategy.DATA,Local资源下走DiskCacheStrategy.RESOURCE。

磁盘-RESOURCE

实际是内存中解码后的Bitmap对象,编码成磁盘需要的文件格式,jpeg/png,之后再存磁盘

解码后的资源的磁盘缓存经过转换,解压之后的体积较大,解码速度较快的,由bitmap直接编码成的JPEG/PNG等未压缩格式

此时存储的文件是文件头为ffd8 ffe0 0010 4a46 4946 0001 0100 0001的jpeg格式。

Resource 代表了解码后的资源,即已经从原始数据(如网络图片的字节流)解码并转换为可直接使用的数据形式,之后还是需要编码成文件格式jpeg/png等文件存储格式。Resource 封装了解码后的资源,并提供了一些管理功能,如引用计数和资源回收。

磁盘-DATA

原资源的直接数据缓存(未解码,转换,磁盘体积较小,解码速度较慢)(AVIF/WEBP等压缩格式)

此时存储的文件是文件头为0000 0020 6674 7970 6176 6966 0000 0000的avif格式。

DataCache 是指数据缓存,主要用于缓存原始数据(例如,通过网络请求获取的图片字节流)。数据缓存可以加速后续的解码过程,因为原始数据已经被缓存下来,不需要再次从网络或其他源获取。

磁盘-AUTOMATIC

Remote资源下走DiskCacheStrategy.DATA,Local资源下走DiskCacheStrategy.RESOURCE

Tips

  • 对于远程的资源,DATA和AUTOMATIC是一样的。

  • 想缩短解码时间可以考虑使用RESOURCE模式,本质上是用磁盘空间 换 解码时间:decode后的资源存储空间更大,但解码时间更短。

  • 对于webp,avif等模式,RESOURCE缓存的是.jpeg格式,其内容是decode后内存中使用的bitmap对象直接,不带压缩的二进制内容。DATA缓存的则是原始的图片格式

  • **那么对于.jpg文件,RESOURCE跟DATA模式的区别是啥:jpg本身也是有压缩的,RESOURCE模式下存储的文件格式仍为jpg,但是其内容是decode后的,尺寸更大而解码时间更短 **

  • 那么对于.gif文件,不管什么模式都一样,就是缓存gif图原始文件

  • 互联网早期图片现在看到很多事绿色且模糊,其实就是由于jpeg的压缩是有损的,每次存到磁盘中可能经过一次压缩,并且上传时会再压缩一次,重复多次后就会质量下降

Glide缓存Key

Glide生成key(两级内存缓存Key类型为同一个EngineKey对象,磁盘缓存Key类型为DataCacheKey或ResourceCacheKey摘要的哈希字符串)的方式涉及的参数有8种,其中都包括图片URL、签名。可能包括宽高、变换等

1
2
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);

宽高一定是明确有值的。即使wrap_content也会用屏幕最长的一边作为兜底宽高。

Glide的缓存大小默认是多少

内存缓存大小

内存缓存最大空间(maxSize) = 每个进程可用的最大内存(activityManager.getMemoryClass() * 0.4

(低配手机的话是: 每个进程可用的最大内存 * 0.33)

activityManager.getMemoryClass值:

  • 低端设备可能在 16-32 MB 范围内。
  • 中档设备通常在 64-128 MB 范围内。
  • 高端设备和新款设备可能在 256 MB 或更高。//当设置largeHeap时,最多可申请512M

磁盘缓存大小

磁盘缓存大小: 默认250MB

磁盘缓存目录: 项目/cache/image_manager_disk_cache

image-20240815114741925

这里有个点:随着图片磁盘缓存的存取,由于每次存取磁盘图片glide都会将存取操作记录到日志文件(journal文件),日志文件会逐渐增大到可能几兆大小,导致glide初始化延迟(glide的初始化中,磁盘缓存初始化时会涉及到将日志文件读取到内存中操作)。

这里可以增加一个首页模块的磁盘缓存目录,比如项目/cache/image_manager_disk_home_cache,启动时先初始化首页的磁盘缓存(现为10m)再初始化主体的磁盘缓存,避免日志文件过大导致的初始化时间太长阻塞App启动。

Other

感知生命周期

通过链式调用中with(xxx)传入的activity或Fragment,Glide实现了对所属activity或者fragment生命周期监听:通过new一个隐形的fragment(SupportRequestManagerFragment.class),嵌入到所要监听的fragment或者activity所在的Activity中,这样当宿主Acitivity的生命周期变化时,可以通过嵌入的fragment监听回调,并在RequestManager中执行:

**onStart(): 继续请求resumeRequests() **

onStop(): 暂停请求pauseRequests()

onDestory(): 销毁请求、对应页面的活动缓存降级到内存缓存LRUCache,移除监听等操作

// ps:当 Activity时,Glide 会清理相关资源,移除 Fragment 不会立即触发 ActiveResources 缓存降级,只有宿主 Activity 销毁时才会。

//com/bumptech/glide/request/target/CustomViewTarget.java 中保留了一个没打开的接口clearOnDetach(),可以实现当ImageView detachedFromWindow的时候释放图片缓存。但由于考虑到太激进的释放可能导致缓存复用效率

glide是怎么拿到的width,height(view未测绘前拿不到宽高)

如果view的布局宽高有值,或view本身的宽高有值,会直接返回布局宽高或view宽高。如果没有:

ViewTarget会为所持有的View注册view树绘制回调,待到经过测量布局之后回调onSizeReady()了,才会发起engine.load。(如果wrap_content则会用getMaxDisplayLength()屏幕长的一边作为宽高返回)

addOnPreDrawListener(OnPreDrawListener listener)

image-20240709154704558

如果是Wrap_content?

那么会返回 屏幕高(长的一边) * 屏幕高,比如 1080 * 1920 屏幕会返回 1920 * 1920兜底,在后续流程比如内存缓存Key生成时候,也是用的这个兜底宽高。

具体见ViewTarget.getMaxDisplayLength()

感知内存吃紧

注册ComponentCallbacks2,实现细粒度内存管理:

  1. onLowMemory(){清除内存}
  2. onTrimMemory(){修剪内存}
1
2
3
memoryCache.trimMemory(level); // 内存缓存
bitmapPool.trimMemory(level); // bitmap池
arrayPool.trimMemory(level); // 字节数组池

可以设置在onTrimMemory时,取消所有正在进行的请求。

Glide线程池

源线程池sourceExecutor

(触发下载时存取磁盘和解析使用的线程池,非下载线程池,下载包给okhttp/httpurlconnection了):

定长为 cpu数量(最大4)(核心线程和工作线程数量都最大为4)的线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private GlideExecutor getActiveSourceExecutor() {
return useUnlimitedSourceGeneratorPool
? sourceUnlimitedExecutor
: (useAnimationPool ? animationExecutor : sourceExecutor);
}

//bestThreadCount = Math.min(MAXIMUM_AUTOMATIC_THREAD_COUNT, RuntimeCompat.availableProcessors());

public static GlideExecutor newSourceExecutor() {
return newSourceExecutor(
calculateBestThreadCount(),
DEFAULT_SOURCE_EXECUTOR_NAME,
UncaughtThrowableStrategy.DEFAULT);
}

磁盘缓存diskCacheExecutor

(从Resource或DataCache中加载图片)线程池:定长为1(核心线程和工作线程数量都为1)的线程池

    //diskCacheExecutor

    //DEFAULT_DISK_CACHE_EXECUTOR_THREADS = 1

    public static GlideExecutor newDiskCacheExecutor() {
    return newDiskCacheExecutor(
        DEFAULT_DISK_CACHE_EXECUTOR_THREADS,
        DEFAULT_DISK_CACHE_EXECUTOR_NAME,
        UncaughtThrowableStrategy.DEFAULT);
    }

加载Gif流程

读取流的前三个字节,若判断是gif,则会命中gif解码器-将资源解码成GifDrawable,它持有GifFrameLoader会将资源解码成一张张Bitmap并且传递给DelayTarget的对象,该对象每次资源加载完毕都会通过handler发送延迟消息回调 onFrameReady() 以触发GifDrawable.invalidataSelf()重绘。加载下一帧时会重新构建DelayTarget

QA

ActiveResources弱引用问题

Q: glide 当前页面的图片缓存是存在activiteResource的弱引用的,也就是说当gc时当前页面的图片缓存会被回收?那岂不是当前页面会空白

A: 不会啊,因为实际的资源被页面的view持有着,而ActiveResources只是一层弱引用而已。而已经不被view持有(没显示出来的图片)但仍在活动缓存中的弱引用会被降级到LRU内存缓存中。

活动资源缓存的存在以较小的代价减小Lru缓存的压力,提升Lru缓存的效率。

原因是活动资源缓存通过缓存的对象本身就是在内存中进行使用,缓存是只是建立一个弱引用关系。如果过没有活动资源缓存,每一次使用的资源都加入内存缓存,极有可能因为放入Lru缓存的数据过多,导致正在使用资源从Lru缓存中移除,等到下次来进行加载的时候因为没有对应的引用关系,找不到原来内存中正在使用的那个资源,从而需要再次从文件或者网络进行数据加载。这样同一份资源需要使用两处或者多处内存。大大的提高了内存消耗。总而言之,活动资源缓存以较小的代价提高了Lru缓存的使用效率,防止加载中的资源被lru回收。

字节数组的复用池

BitmapPool

  • BitmatPool 是 Glide 维护了一个图片复用池,LruBitmapPool 使用 Lru 算法保留最近使用的尺寸的 Bitmap。
  • api19 后使用bitmap的字节数和config作为key,而之前使用宽高和congif,所以19以后复用度更高
  • 用类似LinkedHashMap存储,键值对中的值是一组Bitmap,相同字节数的Bitmap 存在一个List中(这样设计的目的是,将Lru策略运用在Bitmap大小上,而不是单个Bitmap上),控制BitmapPool大小通过删除数据组中最后一个Bitmap。
  • BitmapPool 大部分用于Bitmap变换和gif加载时

ArrayPool

  • 是一个采用Lru策略的数组池,用于解码时候的字节数组的复用。
  • 清理内存意味着清理MemoryCache,BitmapPool,ArrayPool

相关源码

load可加载多种model的图片

image-20240815203503827

1
2
3
4
5
6
7
8
9
10
11
RequestBuilder.java
public RequestBuilder<TranscodeType> load(@Nullable Object model) {
return loadGeneric(model);
}

@NonNull
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}

获得View宽高

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
SingleRequest.java
public void onSizeReady(int width, int height) {
//...
//加载图片的View在viewTreeObserver的onPreDrawListener回调回来后拿到确定的View宽高
loadStatus =
engine.load(
glideContext,
model,
requestOptions.getSignature(),
this.width,
this.height,
requestOptions.getResourceClass(),
transcodeClass,
priority,
requestOptions.getDiskCacheStrategy(),
requestOptions.getTransformations(),
requestOptions.isTransformationRequired(),
requestOptions.isScaleOnlyOrNoTransform(),
requestOptions.getOptions(),
requestOptions.isMemoryCacheable(),
requestOptions.getUseUnlimitedSourceGeneratorsPool(),
requestOptions.getUseAnimationPool(),
requestOptions.getOnlyRetrieveFromCache(),
this,
callbackExecutor);
//...
}

内存缓存Key的生成

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
Engine.java
public <R> LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class<?> resourceClass,
Class<R> transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map<Class<?>, Transformation<?>> transformations,
boolean isTransformationRequired,
boolean isScaleOnlyOrNoTransform,
Options options,
boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool,
boolean useAnimationPool,
boolean onlyRetrieveFromCache,
ResourceCallback cb,
Executor callbackExecutor) {

//内存缓存Key的生成:由model(所加载的数据的模型:Uri/String/Bitmap等)、singature(请求SingleRequest的签名、ImageView的宽高、请求变换(Transformations)、resourceClass(资源解码类型,asBitmap是Bitmap.class,asGif是GifDrawable.class,没有指定是Drawable.class),transcodeClass(链式中调用.transcode()传入,一般为Drawable.class,是最后ImageView实际),options(一组存储如压缩质量、压缩格式、网络超时时间等选项的类)
//这八者任意一个改变都会引起key的hashcode变化
EngineKey key =
keyFactory.buildKey(
model,
signature,
width,
height,
transformations,
resourceClass,
transcodeClass,
options);


EngineResource<?> memoryResource;
synchronized (this) {
//是否存在内存缓存(包括 activiteResource 和 cache)
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);

//没有内存缓存,就进行网络或者磁盘缓存解析
if (memoryResource == null) {
return waitForExistingOrStartNewJob(
glideContext,
model,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
options,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache,
cb,
callbackExecutor,
key,
startTime);
}
}

//只有命中内存缓存才从这里返回
cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
return null;
}

请求缓存流程

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
Engine.java {
@Nullable
private EngineResource<?> loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime) {
if (!isMemoryCacheable) {
return null;
}

//该缓存key存在于activiteResource,是弱引用存在于内存中的
EngineResource<?> active = loadFromActiveResources(key);
if (active != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return active;
}

//该缓存存在于cache
EngineResource<?> cached = loadFromCache(key);
if (cached != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return cached;
}
return null;
}


//如果该缓存key存在于activiteResource,对应的对象引用加1
@Nullable
private EngineResource<?> loadFromActiveResources(Key key) {
EngineResource<?> active = activeResources.get(key);
if (active != null) {
active.acquire();
}

return active;
}

//如果该缓存存在于cache,将其中cache中移除并存到activiteResource中并引用计数加1
private EngineResource<?> loadFromCache(Key key) {
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached);
}
return cached;
}

private EngineResource<?> getEngineResourceFromCache(Key key) {
Resource<?> cached = cache.remove(key);

final EngineResource<?> result;
if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
//一般情况都是从缓存cache中直接返回
// Save an object allocation if we've cached an EngineResource (the typical case).
result = (EngineResource<?>) cached;
} else {
//兜底
result =
new EngineResource<>(
cached, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
}
return result;
}
}

相关概念

  1. Resource

Resource 代表了解码后的资源,即已经从原始数据(如网络图片的字节流,一般为AVIF、WEBP)解码并转换为可直接使用的数据形式(如 Bitmap、Drawable 等,如果存到磁盘会encode成jpeg/png格式)。Resource 封装了解码后的资源,并提供了一些管理功能,如引用计数和资源回收。

  1. DataCache

DataCache 是指数据缓存,主要用于缓存原始数据(例如,通过网络请求获取的图片字节流)。数据缓存可以加速后续的解码过程,因为原始数据已经被缓存下来,不需要再次从网络或其他源获取。

  1. Source

Source 代表数据的源头,即原始数据的来源。常见的源包括网络(主要)、文件系统、资源文件、内容提供者等。
网络源:从 URL 或网络地址加载数据。
文件源:从本地文件系统加载数据。
资源源:从应用的资源文件中加载数据。
内容提供者:通过内容提供者(ContentProvider)加载数据。

  • URL:主要用于标识网络上的资源。示例:https://example.com/image.jpg
  • URI:是一个更广泛的概念,可以标识任意一种资源,包括本地文件和网络资源。示例:file:///storage/emulated/0/Download/image.jpg 和 content://media/external/images/media/12345

resourceClass //图片以什么类型解码图片资源

resourceClass 是指图像加载过程中,Glide 从源(如网络或本地存储)加载并解码后的资源类型。它表示 Glide 获取图像数据后将其解码成什么类型的资源。

常见的 resourceClass 类型包括:

  • Bitmap:用于静态图像,如 JPEG、PNG。
  • GifDrawable:用于 GIF 动图。
  • Drawable:一个通用类型,可以表示 BitmapDrawable、GifDrawable 等。

指定 resourceClass 可以确保 Glide 以指定的方式解码和处理图像资源。例如:

1
2
3
4
5
6
java
Copy
Glide.with(context)
.asBitmap() // 指定resourceClass为 Bitmap。不指定时默认asDrawable为drawable
.load("https://example.com/image.jpg")
.into(imageView);

transcodeClass // 最终提供给 Target(如 ImageView)的数据类型

transcodeClass 是指从 resourceClass 类型转换后的最终数据类型,它表示图像加载和解码后,最终提供给 Target(如 ImageView)的数据类型。

常见的 transcodeClass 类型包括:

  • Drawable:Glide 默认使用 Drawable 作为最终显示的类型,因为它可以表示不同类型的图像资源。
  • Bitmap:如果你需要直接处理 Bitmap 对象,可以指定 transcodeClass 为 Bitmap。
  • 自定义类型:你可以定义自己的转换器,将资源转换为自定义类型。

通过自定义转换器(ResourceTranscoder),你可以将 resourceClass 类型转换为所需的 transcodeClass 类型。例如,如果你想将 GIF 转换为静态图像或者其他类型,可以使用自定义转换器。

总结

  • resourceClass:表示 Glide 从源加载并解码后的资源类型(如 Bitmap、GifDrawable)。
  • transcodeClass:表示从 resourceClass 类型转换后的最终数据类型,可以是 Drawable、Bitmap 或其他自定义类型。

相关参考https://juejin.cn/post/7129306281650683935#heading-92

Author

white crow

Posted on

2021-09-30

Updated on

2024-10-22

Licensed under