前言
Glide作为一个强大的图片加载框架,必然有着一套完整且优秀的缓存机制。本文将基于Glide-4.9.0版本,对Glide的缓存机制进行源码分析。
缓存相关的类
下面先介绍一下Glide缓存涉及到的一些类:
ActiveResources
ActiveResources是第一级缓存,表示当前正在活动的资源。当资源加载成功,或者通过其他缓存获得资源后都会将其添加到ActiveResources中。当资源被gc后,就会将其移除出ActiveResources。
ActiveResources通过Map来存储数据,数据保存在WeakReference中1
2//ResourceWeakReference继承于WeakReference<EngineResource<?>>
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
此外,还有一个引用队列1
private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();
当一个弱引用对象被gc掉之后,其对应的Reference对象会加入到引用队列中。从引用队列获取到被gc掉的弱引用对象后,就可以将该对象从ActiveResources中删除。
MemoryCache
MemoryCache(内存缓存)是第二级缓存,如果ActiveResources没有获取到资源就从这里获取。
它的实现类是LruResourceCache,继承于LruCahce。主要的实现在LruCache中,LruCache使用LRU算法进行缓存。它的内部使用LinkedHashMap存储数据,LinkedHashMap内部可以设置为按照访问顺序进行排序,最近最少访问的在前面,这很适合LRU算法,在清除缓存的时候,只要从前面的元素开始删除,一直删除到满足容量即可。
LruCache
看下LruCache中存取数据的几个方法:
get方法获取指定数据1
2
3
4 public synchronized Y get(@NonNull T key) {
//获取缓存数据
   return cache.get(key);
 }
remove方法删除指定数据1
2
3
4
5
6
7
8
9 public synchronized Y remove(@NonNull T key) {
//从缓存中删除指定数据
   final Y value = cache.remove(key);
//如果删除成功,就更新当前缓存容量,并返回删除数据
   if (value != null) {
     currentSize -= getSize(value);
   }
   return value;
 }
put方法添加缓存数据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 public synchronized Y put(@NonNull T key, @Nullable Y item) {
   //获取资源的大小
   final int itemSize = getSize(item);
//资源大小超出容量限制,不缓存该资源,并回调该资源的移除
   if (itemSize >= maxSize) {
     onItemEvicted(key, item);
     return null;
   }
//如果资源不为空,更新当前缓存容量
   if (item != null) {
     currentSize += itemSize;
   }
//将当前资源添加进缓存中
    final Y old = cache.put(key, item);
//如果该key之前有其他资源,更新当前缓存容量,并回调该资源的移除
   if (old != null) {
     currentSize -= getSize(old);
     if (!old.equals(item)) {
       onItemEvicted(key, old);
     }
   }
//从缓存中删除最近最少使用的数据,直到满足指定容量
   evict();
   return old;
 }
这几个方法还是比较好理解的,代码中都有注释。
MemorySizeCalculator
MemoryCache在创建Glide实例时初始化,内存缓存的初始化容量从MemorySizeCalculator中获得,代码如下:1
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
getMemoryCacheSize方法如下:1
2
3public int getMemoryCacheSize() {
  return memoryCacheSize;
}
其中memoryCacheSize在MemorySizeCalculator初始化时指定,MemorySizeCalculator的初始化也是发生在Glide实例创建时:1
2
3if (memorySizeCalculator == null) {
  memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
}
这里采用了Builder模式,在其内部类Builder的build方法中调用了MemorySizeCalculator的构造方法: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 MemorySizeCalculator(MemorySizeCalculator.Builder builder) {
 
//ArrayPool的容量默认为4MB,低内存设备为2MB
   arrayPoolSize =
       isLowMemoryDevice(builder.activityManager)
           ? builder.arrayPoolSizeBytes / LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR
           : builder.arrayPoolSizeBytes;
//最大容量为:当前进程可用内存 * 0.4;对低内存设备,为当前进程可以内存 * 0.33
   int maxSize =
       getMaxSize(
           builder.activityManager, builder.maxSizeMultiplier, builder.lowMemoryMaxSizeMultiplier);
//计算一张大小为屏幕大小,格式为ARGB_8888的图片占用的内存大小(包括BitmapPool和MemoryCache)
   int widthPixels = builder.screenDimensions.getWidthPixels();
   int heightPixels = builder.screenDimensions.getHeightPixels();
   int screenSize = widthPixels * heightPixels * BYTES_PER_ARGB_8888_PIXEL;
   int targetBitmapPoolSize = Math.round(screenSize * builder.bitmapPoolScreens);
   int targetMemoryCacheSize = Math.round(screenSize * builder.memoryCacheScreens);
//去掉ArrayPool占用的内存后,剩余的内存大小
   int availableSize = maxSize - arrayPoolSize;
//BitmapPool和MemoryCache的内存相加不超过可用内存大小
   if (targetMemoryCacheSize + targetBitmapPoolSize <= availableSize) {
     memoryCacheSize = targetMemoryCacheSize;
     bitmapPoolSize = targetBitmapPoolSize;
   }
//超出限制的话,两者按照比例平分可用大小
else {
     float part = availableSize / (builder.bitmapPoolScreens + builder.memoryCacheScreens);
     memoryCacheSize = Math.round(part * builder.memoryCacheScreens);
     bitmapPoolSize = Math.round(part * builder.bitmapPoolScreens);
   }
 }
可以看到,构造方法中指定了 BitmapPool 和 MemoryCache 可用的内存大小。 BitmapPool 是用来复用 Bitmap,从而避免重复创建 Bitmap 而带来的内存浪费。
小结一下计算BitmapPool和MemoryCache可用内存的步骤:
- 设置ArrayPool的容量,默认为4MB,低内存设备为2MB(安卓版本低于4.4默认为低内存,其它版本由系统判断)
 - 设置最大容量,默认为当前进程可用内存 0.4,低内存设备 0.3
 - 设置可用内存容量为最大容量减去ArrayPool的容量
 - 计算一张大小为屏幕大小,格式为ARGB_8888的图片占用的内存大小(包括BitmapPool和MemoryCache),如果两者内存相加不超过可用容量,那么计算得出的内存大小即为各自的可用内存。如果相加后超出限制的话,两者按照比例平分可用内存。
 
过程分析
在执行Request的时候,会调用SingleRequest的onSizeReady方法进行加载数据:
SingleRequest#onSizeReady
1  | 
  | 
在真正加载资源前,将Request的状态变为RUNNING。之后调用Engine的load方法真正加载资源:
Engine#load
该方法首先生成缓存key,该key是一个EngineKey对象,由传入的多个参数生成。1
2
3   //生成缓存key(该key是一个EngineKey对象)
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
       resourceClass, transcodeClass, options);
ActiveResources缓存
接着根据该缓存key获取缓存资源,首先从ActiveResources中获取缓存:1
2
3
4
5
6
7
8//从ActiveResources(弱引用)中获取缓存
   EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
   if (active != null) {
     //获取到缓存资源,回调onResourceReady方法
     cb.onResourceReady(active, DataSource.MEMORY_CACHE);
     return null;
   }
loadFromActiveResources方法:1
2
3
4
5
6
7
8
9
10
11
12
13 private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
   if (!isMemoryCacheable) {
     return null;
   }
//获取缓存资源
   EngineResource<?> active = activeResources.get(key);
//如果获取到缓存资源,就更新资源获取次数
   if (active != null) {
     active.acquire();
   }
   return active;
 }
内存缓存
如果ActiveResources中获取不到,就从内存中获取缓存,如果成功获取到缓存,就将缓存从内存中删除并将添加到ActiceResources中:1
2
3
4
5
6
7
8//如果ActiveResources中获取不到,就从内存中获取缓存
   EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
   if (cached != null) {
     //获取到缓存资源,回调onResourceReady方法
     cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
     return null;
   }
loadFromCache方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
   if (!isMemoryCacheable) {
     return null;
   }
   //从内存中获取缓存,获取到缓存后,将缓存从内存中删除
   EngineResource<?> cached = getEngineResourceFromCache(key);
//如果获取到缓存
   if (cached != null) {
  //更新资源的获取次数
     cached.acquire();
  //将获取到的资源添加到ActiceResources中
     activeResources.activate(key, cached);
   }
   return cached;
 }
磁盘缓存
如果在内存中获取不到缓存,就要执行以下步骤1
2
3
4
5
6
7EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
  current.addCallback(cb, callbackExecutor);
  
  return new LoadStatus(cb, current);
}
其中jobs为Jobs对象,看Jobs的get方法:1
2
3EngineJob<?> get(Key key, boolean onlyRetrieveFromCache) {
  return getJobMap(onlyRetrieveFromCache).get(key);
}
继续看getJobMap方法:1
2
3private Map<Key, EngineJob<?>> getJobMap(boolean onlyRetrieveFromCache) {
  return onlyRetrieveFromCache ? onlyCacheJobs : jobs;
}
其中:onlyCacheJobs和jobs的定义如下:1
2private final Map<Key, EngineJob<?>> jobs = new HashMap<>();
private final Map<Key, EngineJob<?>> onlyCacheJobs = new HashMap<>();
最终,返回一个EngineJob对象,并且第一次加载的话返回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
33EngineJob<R> engineJob =
    engineJobFactory.build(
        key,
        isMemoryCacheable,
        useUnlimitedSourceExecutorPool,
        useAnimationPool,
        onlyRetrieveFromCache);
DecodeJob<R> decodeJob =
    decodeJobFactory.build(
        glideContext,
        model,
        key,
        signature,
        width,
        height,
        resourceClass,
        transcodeClass,
        priority,
        diskCacheStrategy,
        transformations,
        isTransformationRequired,
        isScaleOnlyOrNoTransform,
        onlyRetrieveFromCache,
        options,
        engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb, callbackExecutor);
engineJob.start(decodeJob);
return new LoadStatus(cb, engineJob);
先创建EngineJob和DecodeJob,然后将EngineJob加到Jobs的Map中保存起来。EngineJob主要用于执行DecodeJob以及管理加载完成的回调,DecodeJob是一个Runnable,负责从磁盘或网络中加载数据。
EngineJob通过start方法执行DecodeJob1
2
3
4
5
6
7public synchronized void start(DecodeJob<R> decodeJob) {
  this.decodeJob = decodeJob;
  GlideExecutor executor = decodeJob.willDecodeFromCache()
      ? diskCacheExecutor
      : getActiveSourceExecutor();
  executor.execute(decodeJob);
}
可以看到,DecodeJob是在线程池里执行的,DecodeJob的run中又调用了runWrapped方法:
DecodeJob#runWrapped
1  | private void runWrapped() {  | 
其中状态的变化如下:
DecodeJob#getNextStage
1  | private Stage getNextStage(Stage current) {  | 
注意区分两种状态:在runWrapped方法中的状态为RunReason,在getNextStage方法中的状态为Stage。
在获取下一State时,调用DiskCacheStrategy对象的decodeCachedResource方法,用户可以通过设置相应的DiskCacheStrategy对象来配置Glide的硬盘缓存,用法如下:1
2
3
4
5Glide.with(MainActivity.this)
        .applyDefaultRequestOptions(new RequestOptions()
            .diskCacheStrategy(DiskCacheStrategy.NONE))
        .load(url)
        .into(mPicIv);
调用diskCacheStrategy方法并传入DiskCacheStrategy.NONE后,就可以禁止掉硬盘缓存。硬盘缓存策略共有以下几种:
- DiskCacheStrategy.NONE:不缓存任何内容
 - DiskCacheStrategy.DATA:只缓存原始图片
 - DiskCacheStrategy.RESOURCE:只缓存转换后的图片
 - DiskCacheStrategy.ALL:既缓存原始图片,也缓存转换后的图片
 - DiskCacheStrategy.AUTOMATIC:让Glide根据图片资源智能选择策略(默认)
 
回到runWrapped方法,在确定了state后,根据state获得对应的DataFetcherGenerator,看getNextGenerator方法:
DecodeJob#getNextGenerator
1  | private DataFetcherGenerator getNextGenerator() {  | 
获得相应DataFetcherGenerator后,执行runGenerators方法:
DecodeJob#runGenerators
1  | private void runGenerators() {  | 
首先会执行对应Generator的startNext方法,假如state为RESOURCE_CACHE(磁盘缓存策略为DiskCacheStrategy.RESOURCE或DiskCacheStrategy.ALL),则执行ResourceCacheGenerator的startNext方法:
ResourceCacheGenerator#startNext
1  | public boolean startNext() {  | 
在该方法中,如果能够获取到缓存文件,就先得到能够加载这个缓存文件的ModelLoaders,之后通过ModelLoader构造出对应的LoadData,最后通过该LoadData的DataFetcher的loadData方法加载缓存文件的数据。
假如这里调用的ByteBufferFetcher的loadData方法:1
2
3
4
5
6
7
8
9
10
11
12
13
public void loadData(@NonNull Priority priority,
    @NonNull DataCallback<? super ByteBuffer> callback) {
  ByteBuffer result;
  try {
    result = ByteBufferUtil.fromFile(file);
  } catch (IOException e) {
    callback.onLoadFailed(e);
    return;
  }
  callback.onDataReady(result);
}
成功的话会回调onDataReady方法,将加载到的数据传出去,先存放在DecodeJob中,之后进行相关操作:1
2
3
4
5
6
7
8
9
public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
    DataSource dataSource, Key attemptedKey) {
  //...
  
  this.currentData = data;
  //...
}
而失败的话会回调onLoadFailed方法,也是在DecodeJob中处理1
2
3
4
5
6
7
8
9
10
11
12
public void onDataFetcherFailed(Key attemptedKey, Exception e, DataFetcher<?> fetcher,
    DataSource dataSource) {
  //...
  
  if (Thread.currentThread() != currentThread) {
    runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
    callback.reschedule(this);
  } else {
    runGenerators();
  }
}
可以看到,在加载数据失败后不会就此结束,而是重新执行runGenerators方法再次尝试获取数据。
对于第二种情况,假如state为DATA_CACHE(磁盘缓存策略为DiskCacheStrategy.DATA或DiskCacheStrategy.ALL),则执行DataCacheGenerator的startNext方法,该方法的过程ResourceCacheGenerator类似,就不多说了。
现在看第三种情况,当state为SOURCE时,对应的Generator为SourceGenerator,同样调用SourceGenerator的startNext方法:
SourceGenerator#startNext
1  | 
  | 
该方法中,如果已经获取到了数据,就将数据添加到磁盘缓存中,之后如果可以从磁盘缓存加载数据就不再执行后面操作。否则的话,同样是先通过相应的ModelLoader获得LoadData对象,然后通过LoadData中的DataFetcher的loadData方法加载数据(例如通过url从网络加载图片资源)。
如果加载数据成功,会先在SourceGenerator中回调onDataReady方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14 
 public void onDataReady(Object data) {
   DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
//判断是否要缓存到磁盘中
   if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
  //如果要缓存到磁盘中,会先把数据保存起来,之后重新进入startNext方法(可能在其他子线程),将数据缓存到磁盘。
     dataToCache = data;
     cb.reschedule();
   } else {
  //不缓存到磁盘的话,就将数据传递出去,存放在DecodeJob中
     cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
         loadData.fetcher.getDataSource(), originalKey);
   }
 }
加载成功时,会判断是否要缓存到磁盘(如果是从网络加载的资源,当磁盘缓存策略为DiskCacheStrategy.DATA和DiskCacheStrategy.ALL时,会缓存起来),要缓存到磁盘时,会重新进入startNext方法并将数据缓存到磁盘。如果不缓存到硬盘就将数据传递出去。
如果加载数据失败,同样会重新执行runGenerators方法再次尝试获取数据。
分析到这里,Engine的load方法算是分析完毕了,这个方法是真正用于加载数据的,通过该方法可以了解到Glide的缓存机制。这里小结一下Engine的load方法的执行步骤:
小结
Engine#load的执行步骤
- 生成缓存key:该key是一个EngineKey对象,由传入的多个参数生成。之后通过缓存key获取缓存资源
 - 首先从ActiveResources中获取缓存,ActiveResources是第一级缓存,它通过HashMap来存储数据,数据保存在WeakReference中,此外还有一个引用队列,当某个数据的弱引用对象被gc掉之后,其对应的Reference对象会加入到引用队列中。从引用队列获取到被gc掉的弱引用对象后就可以将这些对象从ActiveResources中删除。
 - 如果ActiveResources中获取不到,就从内存中获取,内存缓存是第二级缓存。内存缓存使用了LRU算法,内部使用LinkedHashMap存储数据。LinkedHashMap内部可以设置为按照访问顺序进行排序,最近最少访问的在前面,很适合LRU算法。如果成功获取到缓存,就将缓存从内存中删除并将添加到ActiceResources中。
 - 如果内存中也获取不到缓存,就会创建EngineJob和DecodeJob,DecodeJob是一个Runnable。EngineJob将DecodeJob提交到线程池中执行。DecodeJob根据资源的磁盘缓存策略,是缓存原图还是缓存转换后的图片,从磁盘中获取不同的缓存文件,获取缓存文件成功后,先得到能够加载该缓存文件的ModelLoader,之后通过ModelLoader构造出对应的LoadData,最后通过该LoadData的DataFetcher加载缓存文件的数据。加载数据成功会将数据传递出去,失败的话会重新尝试获取缓存文件并加载数据。
 - 如果不能从磁盘中得到缓存,会继续在DecodeJob中通过相应的ModelLoader获得LoadData对象并加载数据,不过这次是从数据源加载数据,例如从通过url从网络加载数据。加载数据成功后,会判断是否要缓存到磁盘要缓存到磁盘时,会重新进入startNext方法并将数据缓存到磁盘。如果不缓存到硬盘就将数据传递出去。加载数据失败的话会重新尝试加载,因为可能其他的ModelLoader可以加载成功,所以要不断尝试,直到所有ModelLoader都尝试完。