Glide源码解析3 -- 缓存功能

一 前言

我们知道,Glide的一大优势就是它的缓存功能是它的一大优势之一,虽然其他图片框架也有缓存功能比如Picasso,但是Picasso只能缓存原始图片,而Glide却可以缓存多种规格的图片,也就是说可以通过ImageView的大小来缓存相应的图片大小;同时通过API灵活的制定缓存策略,实现高效缓存。
福利:这里附上各个图片框架的对比图
在这里插入图片描述

二 缓存机制简介

2.1 缓存对象

Glide缓存对象分为两种,也就是上面提到的分别是原始图片和经过转换后的图片:
原始图片:就是图片源的初始大小和分辨率
转换后的图片:尺寸经过缩放或者大小压缩后的图片

Glide在加载图片的时候,会根据ImageView 的大小进行压缩或者转换,契合View即可,不需要将原始图片加载进来,其次通过缓存策略的设置,可以选择缓存原始图片或者转换后的图片;这也是Glide加载速度高于Picasso的原因

2.2 缓存策略设置

设置内存缓存开关:

skipMemoryCache(true)

设置磁盘缓存模式:

diskCacheStrategy(DiskCacheStrategy.NONE)

可以设置4种模式:

  • DiskCacheStrategy.NONE:表示不缓存任何内容。
  • DiskCacheStrategy.SOURCE:表示只缓存原始图片。
  • DiskCacheStrategy.RESULT:表示只缓存转换过后的图片(默认选项)。
  • DiskCacheStrategy.ALL :表示既缓存原始图片,也缓存转换过后的图片。

2.3 缓存机制设计

  • Glide缓存功能设置为二级缓存:内存缓存和磁盘缓存

    注意:并不是三级缓存,因为 从网络加载 不属于缓存

  • 读取缓存顺序:内存缓存 --> 磁盘缓存 --> 网络缓存
    注意:内存缓存是默认开启的

2.4 二级缓存有何作用

内存缓存:避免重复将图片加载到内存里(只缓存转换过后的图片)
磁盘缓存:避免重复在网络上下载图片,可以缓存原始和转换后的图片,使用者可以自行设置缓存对象

三 Glide 缓存功能介绍

3.1 内存缓存

  1. 具体使用
 // 可通过 API 禁用 内存缓存功能
Glide.with(this)
     .load(url)
     .skipMemoryCache(true) // 禁用 内存缓存
     .into(imageView);

2.实现原理

Glide的内存缓存实现是基于:LruCache 算法(Least Recently Used) & 弱引用机制

  • LruCache算法原理:将 最近使用的对象 用强引用的方式 存储在LinkedHashMap中 ;当缓存满时,将最近最少使用的对象从内存中移除
  • 弱引用:弱引用的对象具备更短生命周期,因为 当JVM进行垃圾回收时,一旦发现弱引用对象,都会进行回收(无论内存充足否)

3.2 磁盘缓存

  1. 具体使用
	Glide.with(this)
     .load(url)
     .diskCacheStrategy(DiskCacheStrategy.NONE)
     .into(imageView);

// 缓存参数说明
// DiskCacheStrategy.NONE:不缓存任何图片,即禁用磁盘缓存
// DiskCacheStrategy.ALL :缓存原始图片 & 转换后的图片
// DiskCacheStrategy.SOURCE:只缓存原始图片(原来的全分辨率的图像,即不缓存转换后的图片)
// DiskCacheStrategy.RESULT:(默认)只缓存转换后的图片(即最终的图像:降低分辨率后 / 或者转换后 ,不缓存原始图片
  1. 实现原理
    使用Glide 自定义的DiskLruCache算法

    该算法基于 Lru 算法中的DiskLruCache算法,具体应用在磁盘缓存的需求场景中
    该算法被封装到Glide自定义的工具类中(该工具类基于Android 提供的DiskLruCache 工具类

3.3 缓存流程图

在这里插入图片描述

四 源码剖析

4.1 生成缓存key

找到缓存里的图片的唯一标识是图片的缓存key,那么这个key是在什么时候创建的呢?
生成缓存 Key 的代码发生在Engine类的 load()中,我们来看一下这个方法的源码:
GenericRequest --》 onSizeReady() -->engine.load()

Engine.java

   public <T, Z, R> LoadStatus load(args[]) {
    
    
        Util.assertMainThread();
      

        //分析1:创建缓存key
        
             // Glide的缓存Key生成规则复杂:根据10多个参数生成
        // 将该id 和 signature、width、height等10个参数一起传入到缓存Key的工厂方法里,最终创建出一个EngineKey对象
        // 创建原理:通过重写equals() 和 hashCode(),保证只有传入EngineKey的所有参数都相同情况下才认为是同一个EngineKey对象
        // 该EngineKey 即Glide中图片的缓存Key
        final String id = fetcher.getId();
		     // Glide的缓存Key生成规则复杂:根据10多个参数生成
        // 将该id 和 signature、width、height等10个参数一起传入到缓存Key的工厂方法里,最终创建出一个EngineKey对象
        // 创建原理:通过重写equals() 和 hashCode(),保证只有传入EngineKey的所有参数都相同情况下才认为是同一个EngineKey对象
        // 该EngineKey 即Glide中图片的缓存Key
        EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
                loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
                transcoder, loadProvider.getSourceEncoder());
		//分析2:获取LruCache里的图片(内存缓存)
        EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
        if (cached != null) {
    
    
            cb.onResourceReady(cached);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
    
    
                logWithTimeAndKey("Loaded resource from cache", startTime, key);
            }
            return null;
        }
		//分析3:获取弱引用缓存
        EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
        if (active != null) {
    
    
            cb.onResourceReady(active);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
    
    
                logWithTimeAndKey("Loaded resource from active resources", startTime, key);
            }
            return null;
        }

    :

        EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
        DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
                transcoder, diskCacheProvider, diskCacheStrategy, priority);
        // 分析4:如果内存缓存没有找到图片,创建一个任务在其他线程跑
        EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
        jobs.put(key, engineJob);
        engineJob.addCallback(cb);
        engineJob.start(runnable);

        if (Log.isLoggable(TAG, Log.VERBOSE)) {
    
    
            logWithTimeAndKey("Started new load", startTime, key);
        }
        return new LoadStatus(cb, engineJob);
    }

快捷键目录标题文本样式列表链接代码片表格注脚注释自定义列表LaTeX 数学公式插入甘特图插入UML图插入Mermaid流程图插入Flowchart流程图插入类图
目录 复制

4.1.1 分析2:loadFromCache()

调用loadFromCache()获取缓存对象,我们来看看这个方法
Engine.java

//原理使用了LruCache算法
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    
    
    if (!isMemoryCacheable) {
    
    
        return null;
// 若isMemoryCacheable = false就返回null,即内存缓存被禁用
        // 即 内存缓存是否禁用的API skipMemoryCache() - 请回看内存缓存的具体使用
        // 若设置skipMemoryCache(true),此处的isMemoryCacheable就等于false,最终返回Null,表示内存缓存已被禁用
    }

    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
    
    
        cached.acquire();
        //这里将从LruCache提取的图片放入弱引用缓存,弱引用缓存用来缓存正在使用的
        //图片,避免被LruCache的算法给删除掉
        activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
    }
    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) {
    
    
            // Save an object allocation if we've cached an EngineResource (the typical case).
            result = (EngineResource) cached;
        } else {
    
    
            result = new EngineResource(cached, true /*isCacheable*/);
        }
        return result;
    }

这里直接的cache其实是MemoryCache,而这个MemoryCache是在Glide对象创建的时候创建的,也就是在load()方法里面,感兴趣的可以去翻看一下源码。这里添上关键代码:
// 作用:创建Glide对象

public class GlideBuilder {
    
    
    ...

    Glide createGlide() {
    
    
        MemorySizeCalculator calculator = new MemorySizeCalculator(context);
        if (bitmapPool == null) {
    
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    
    
                int size = calculator.getBitmapPoolSize();
                bitmapPool = new LruBitmapPool(size);
            } else {
    
    
                bitmapPool = new BitmapPoolAdapter();
            }
        }

        if (memoryCache == null) {
    
    
            memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());
            // 创建一个LruResourceCache对象 并 赋值到memoryCache对象
            // 该LruResourceCache对象 = Glide实现内存缓存的LruCache对象

        }
        
        return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);
    }
}

4.1.2 分析3:loadFromActiveResources()

loadFromActiveResources() 
// 原理:使用了 弱引用机制
// 具体过程:当在方法1中无法获取内存缓存中的缓存图片时,就会从activeResources中取值
// activeResources = 一个弱引用的HashMap:用于缓存正在使用中的图片
    private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    
    
        if (!isMemoryCacheable) {
    
    
            return null;
        }
        EngineResource<?> active = null;
        WeakReference<EngineResource<?>> activeRef = activeResources.get(key);
        if (activeRef != null) {
    
    
            active = activeRef.get();
            if (active != null) {
    
    
                active.acquire();
            } else {
    
    
                activeResources.remove(key);
            }
        }
        return active;
    }

    ...
}

若上述两个方法都没获取到缓存图片时(即内存缓存里没有该图片的缓存),就开启新线程加载图片(分析四)

4.1.3 总结

在这里插入图片描述

4.2 如果缓存没有图片,开启线程加载图片(分析4)

我们具体来看一下这个线程任务EngineRunnable:
EngineRunnable.java

@Override
public void run() {
    
    
    try {
    
    
        resource = decode();
    } catch (Exception e) {
    
    
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
    
    
            Log.v(TAG, "Exception decoding", e);
        }
        exception = e;
    }
}

4.2.1 磁盘缓存

当内存缓存里没找到图片的话,就会另开线程跑这个任务的run方法,这里会从磁盘缓存里找,具体逻辑在decode()这个方法里:

private Resource<?> decode() throws Exception {
    
    

// 在执行 加载图片 线程时(即加载图片时),分两种情况:
// 情况1:从磁盘缓存当中读取图片(默认情况下Glide会优先从缓存当中读取,没有才会去网络源读取图片)
// 情况2:不从磁盘缓存中读取图片

// 情况1:从磁盘缓存中读取缓存图片
    if (isDecodingFromCache()) {
    
    
    // 取决于在使用API时是否开启,若采用DiskCacheStrategy.NONE,即不缓存任何图片,即禁用磁盘缓存
        return decodeFromCache();
        // 读取磁盘缓存的入口就是这里,此处主要讲解 ->>直接看步骤4的分析9
    } else {
    
    

    // 情况2:不从磁盘缓存中读取图片        
    // 这里会把网络上的图片进行存储,具体看4.2.2
        return decodeFromSource();
    }
}

我们先来看一下decodeFromCache():

private Resource<?> decodeFromCache() throws Exception {
    
    
        Resource<?> result = null;
        try {
    
    
        //获取转换过的图片
        // 获取磁盘缓存时,会先获取 转换过后图片 的缓存
        // 即在使用磁盘缓存时设置的模式,如果设置成DiskCacheStrategy.RESULT 或DiskCacheStrategy.ALL就会有该缓存
   
            result = decodeJob.decodeResultFromCache();
        } catch (Exception e) {
    
    
            if (Log.isLoggable(TAG, Log.DEBUG)) {
    
    
                Log.d(TAG, "Exception decoding result from cache: " + e);
            }
        }

        if (result == null) {
    
    
        //获取原始图片
         // 如果获取不到 转换过后图片 的缓存,就获取 原始图片 的缓存
        // 即在使用磁盘缓存时设置的模式,如果设置成DiskCacheStrategy.SOURCE 或DiskCacheStrategy.ALL就会有该缓存
   
            result = decodeJob.decodeSourceFromCache();
        }
        return result;
    }




public Resource<Z> decodeResultFromCache() throws Exception {
    
    
    if (!diskCacheStrategy.cacheResult()) {
    
    
        return null;
    }
    // 1. 根据完整的缓存Key(由10个参数共同组成,包括width、height等)获取缓存图片
    Resource<T> transformed = loadFromCache(resultKey);

 
    // 2. 直接将获取到的图片 数据解码 并 返回
    // 因为图片已经转换过了,所以不需要再作处理
    Resource<Z> result = transcode(transformed);
    return result;   
}

private Resource<T> loadFromCache(Key key) throws IOException {
    
    
    File cacheFile = diskCacheProvider.getDiskCache().get(key);

    // 1. 调用getDiskCache()获取Glide自己编写的DiskLruCache工具类实例
    // 2. 调用上述实例的get() 并 传入完整的缓存Key,最终得到硬盘缓存的文件

    if (cacheFile == null) {
    
    
        return null;
        // 如果文件为空就返回null
    }
    Resource<T> result = null;
    try {
    
    
        result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
            } finally {
    
    
        if (result == null) {
    
    
            diskCacheProvider.getDiskCache().delete(key);
        }
    }
    // 如果文件不为空,则将它解码成Resource对象后返回
    return result;
}


public Resource<Z> decodeSourceFromCache() throws Exception {
    
    
    if (!diskCacheStrategy.cacheSource()) {
    
    
        return null;
    }

    Resource<T> decoded = loadFromCache(resultKey.getOriginalKey());
    // 1. 根据缓存Key的OriginalKey来获取缓存图片
    // 相比完整的缓存Key,OriginalKey只使用了id和signature两个参数,而忽略了大部分的参数
    // 而signature参数大多数情况下用不到,所以基本是由id(也就是图片url)来决定的Original缓存Key
    // 关于loadFromCache()同分析11,只是传入的缓存Key不一样

    return transformEncodeAndTranscode(decoded);
    // 2. 先将图片数据 转换 再 解码,最终返回
    
}

总结:
在这里插入图片描述

4.2.2 decodeFromSource()

这个方法负责将网络上的图片存储到缓存磁盘缓存当中:

public Resource<Z> decodeFromSource() throws Exception {
    
    
    // 写入原始图片 磁盘缓存的入口
    Resource<T> decoded = decodeSource();
	// 对图片进行转码
   // 写入 转换后图片 磁盘缓存的入口
    return transformEncodeAndTranscode(decoded);  
}
  1. 先来看存储原始图片decodeSource()
 	private Resource<T> decodeSource() throws Exception {
    
    
		 // 对图片进行解码 
        decoded = decodeFromSourceData(data);
       
    return decoded;
}

private Resource<T> decodeFromSourceData(A data) throws IOException {
    
    
    final Resource<T> decoded;
    // 判断是否允许缓存原始图片
    // 即在使用 硬盘缓存API时,是否采用DiskCacheStrategy.ALL 或 DiskCacheStrategy.SOURCE
    if (diskCacheStrategy.cacheSource()) {
    
    
    // 若允许缓存原始图片,则调用cacheAndDecodeSourceData()进行原始图片的缓存
        decoded = cacheAndDecodeSourceData(data);
    } else {
    
    
        long startTime = LogTime.getLogTime();
        decoded = loadProvider.getSourceDecoder().decode(data, width, height);
    }
    return decoded;
}

private Resource<T> cacheAndDecodeSourceData(A data) throws IOException {
    
    
    ...
     // 1. 调用getDiskCache()获取DiskLruCache实例
    // 2. 调用put()写入硬盘缓存
    // 注:原始图片的缓存Key是用的getOriginalKey(),即只有id & signature两个参数
    diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer);
}
  1. 再来看看存储转换后的图片transformEncodeAndTranscode()
 private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
    
    
    // 1. 对图片进行转换
    Resource<T> transformed = transform(decoded);
 	// 2. 将 转换过后的图片 写入到硬盘缓存中 
    writeTransformedToCache(transformed);
    Resource<Z> result = transcode(transformed);
    return result;
}

private void writeTransformedToCache(Resource<T> transformed) {
    
    
    // 1. 调用getDiskCache()获取DiskLruCache实例
    // 2. 调用put()写入硬盘缓存
    // 注:转换后图片的缓存Key是用的完整的resultKey,即含10多个参数
    diskCacheProvider.getDiskCache().put(resultKey, writer);

}

到这里,磁盘缓存的写入剖析完毕

总结:
在这里插入图片描述

4.2 内存缓存的写入

Glide的内存缓存的写入时机:图片加载完成,会在EngineJob中通过Handle发送消息将线程切换到主线程,执行handleResultOnMainThread()方法:

class EngineJob implements EngineRunnable.EngineRunnableManager {
    
    

    private final EngineResourceFactory engineResourceFactory;
    ...

    private void handleResultOnMainThread() {
    
    
        ...

        // 4.2.1:写入 弱引用缓存
        engineResource = engineResourceFactory.build(resource, isCacheable);
        listener.onEngineJobComplete(key, engineResource);

        // 4.2.2:写入 LruCache算法的缓存
        //acquire +1
        engineResource.acquire();

        for (ResourceCallback cb : cbs) {
    
    
            if (!isInIgnoredCallbacks(cb)) {
    
    
                engineResource.acquire();
                cb.onResourceReady(engineResource);
            }
        }
        //acquire -1
        engineResource.release();
    }

4.2.1 写入弱引用缓存

onEngineJobComplete():

public class Engine implements EngineJobListener,
        MemoryCache.ResourceRemovedListener,
        EngineResource.ResourceListener {
    
    
    ...    

    @Override
    public void onEngineJobComplete(Key key, EngineResource<?> resource) {
    
    
        Util.assertMainThread();

        if (resource != null) {
    
    
            resource.setResourceListener(key, this);
            if (resource.isCacheable()) {
    
    
             // 将 传进来的EngineResource对象 添加到activeResources()中
               // 即写入了弱引用 内存缓存
                activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
               
            }
        }
        jobs.remove(key);
    }

    ...
}

4.2.2 写入 LruCache算法 缓存

engineResource.acquire():

void acquire() {
    
    
    if (isRecycled) {
    
    
        throw new IllegalStateException("Cannot acquire a recycled resource");
    }
    if (!Looper.getMainLooper().equals(Looper.myLooper())) {
    
    
        throw new IllegalThreadStateException("Must call acquire on the main thread");
    }
     // 当调用acquire()时,acquired变量 +1
    ++acquired;
   
}

acquire是包含图片资源resource的EngineResource对象的一个引用机制,用来记录图片使用的次数,加载图片的时候,就会调用acquire(),让acquired+1
不加载的时候,就会调用release(),acquired-1

    void release() {
    
    
        if (acquired <= 0) {
    
    
            throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
        }
        if (!Looper.getMainLooper().equals(Looper.myLooper())) {
    
    
            throw new IllegalThreadStateException("Must call release on the main thread");
        }
        if (--acquired == 0) {
    
    
             // 当调用release()时,acquired变量 -1
            // 若acquired变量 = 0,即说明图片已经不再被使用
            // 调用listener.onResourceReleased()释放资源
            // 该listener = Engine对象,Engine.onResourceReleased()
            listener.onResourceReleased(key, this);

        }
    }
}

public class Engine implements EngineJobListener,
        MemoryCache.ResourceRemovedListener,
        EngineResource.ResourceListener {
    
    

    private final MemoryCache cache;
    private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
    ...    

    @Override
    public void onResourceReleased(Key cacheKey, EngineResource resource) {
    
    
        Util.assertMainThread();
        activeResources.remove(cacheKey);
        // 步骤1:将缓存图片从activeResources弱引用缓存中移除

        if (resource.isCacheable()) {
    
    
            cache.put(cacheKey, resource);
            // 步骤2:将该图片缓存放在LruResourceCache缓存中
        } else {
    
    
            resourceRecycler.recycle(resource);
        }
    }

    ...
}

所以:

  • 当 acquired 变量 >0 时,说明图片正在使用,即该图片缓存继续存放到activeResources弱引用缓存中
  • 当 acquired变量 = 0,即说明图片已经不再被使用,就将该图片的缓存Key从
    activeResources弱引用缓存中移除,并存放到LruResourceCache缓存中
    也就是说,正在使用的图片采用弱引用缓存,不再使用的就用LruCache算法的内存缓存
    在这里插入图片描述

五 总结

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_39431405/article/details/121489297