Glide缓存模块源码分析

2019-05-06  本文已影响0人  有没有口罩给我一个

在开始之前,我们先了解Java中的四种引用和ReferenceQueue,为什么要了解这些知识呢?大家都知道Glide的缓存使用三级缓存,分别是磁盘缓存和两级内存缓存,而Glide的两级内存缓存就是用WeakReference+ReferenceQueue监控GC回收,这里的回收是指JVM在合适的时间就会回收该对象。

Java的四种引用

熟悉Java的同学都知道Java内存管理分为内存分配和内存回收,都不需要我们负责,垃圾回收的机制主要是看对象是否有引用指向该对象。java对象的引用包括 强引用、软引用、弱引用和虚引用

这里就举个弱引用的栗子,其他就不展开说明了?

 //引用队列
 private ReferenceQueue queue = new ReferenceQueue<Person>();

/**
 * 监控对象被回收,因为如果被回收就会就如与之关联的队列中
 */
private void monitorClearedResources() {
    Log.e("tag", "start monitor");
    try {
        int n = 0;
        WeakReference k;
        while ((k = (WeakReference) queue.remove()) != null) {
            Log.e("tag", (++n) + "回收了:" + k + "   object: " + k.get());
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}



    new Thread() {
        @Override
        public void run() {
            monitorClearedResources();
        }
    }.start();


    new Thread() {
        @Override
        public void run() {
            while (true)
                new WeakReference<Person>(new Person("hahah"), queue);
        }
    }.start();

上面是一个监控对象回收,因为如果被回收就会就如与之关联的队列中,接着开启线程制造触发GC,并开启线程监控对象回收,没什么好说的,下面就开始介绍Glide缓存。

Glide缓存

Glide的缓存分为了内存缓存和磁盘缓存,而内存缓存又分了两个模块,Glide缓存的设计思想非常好,不知道人家是怎么想到。

内存缓存和磁盘缓存相互结合才构成了Glide极佳的图片缓存效果,那么接下来我们就分别来分析一下这两种缓存的使用方法以及它们的实现原理,看看我画的时序图,第一次画时序图,欢迎指教。


ClideCache.png
缓存Key

既然是缓存功能,那得有缓存的Key。那么Glide的缓存Key是怎么生成的呢?生成缓存Key的代码在Engine类的load()方法中:

 public synchronized <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) {

          //.....此处省略一万行代码

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

         //.....此处省略一万行代码
 }

实际上直接调用 keyFactory.buildKey方法并把一些资源表示相关的参数传进去构建EngineKey ,那我们来分析keyFactory.buildKey和EngineKey 的源码:

EngineKeyFactory .buildKey:

class EngineKeyFactory {
EngineKey buildKey(Object model, Key signature, int width, int height,
  Map<Class<?>, Transformation<?>> transformations, Class<?> resourceClass,
  Class<?> transcodeClass, Options options) {
return new EngineKey(model, signature, width, height, transformations, resourceClass,
    transcodeClass, options);
    }
}

可以看到EngineKeyFactory 的代码并不多,仅仅是创建EngineKey实例,工厂方法模式,说实在Glid中用了好多工厂方法模式。

EngineKey :

class EngineKey implements Key {
  EngineKey( Object model,Key signature,  int width, int height,
  Map<Class<?>, Transformation<?>> transformations, Class<?> resourceClass,
  Class<?> transcodeClass,Options options) {
// 此处省略一万行代码
}

  @Override
public boolean equals(Object o) {
if (o instanceof EngineKey) {
  EngineKey other = (EngineKey) o;
  return model.equals(other.model)
      && signature.equals(other.signature)
      && height == other.height
      && width == other.width
      && transformations.equals(other.transformations)
      && resourceClass.equals(other.resourceClass)
      && transcodeClass.equals(other.transcodeClass)
      && options.equals(other.options);
}
    return false;
  }
@Override
  public int hashCode() {
if (hashCode == 0) {
  hashCode = model.hashCode();
  hashCode = 31 * hashCode + signature.hashCode();
  hashCode = 31 * hashCode + width;
  hashCode = 31 * hashCode + height;
  hashCode = 31 * hashCode + transformations.hashCode();
  hashCode = 31 * hashCode + resourceClass.hashCode();
  hashCode = 31 * hashCode + transcodeClass.hashCode();
  hashCode = 31 * hashCode + options.hashCode();
    }
return hashCode;
  }
  @Override
  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
    throw new UnsupportedOperationException();
  }
}

可以看到,其实就是重写equals()和hashCode()方法,即只有传入EngineKey的所有参数都相同的情况下才认为是同一个EngineKey对象或者说是同一种资源。

内存缓存

对于内存缓存的实现,大家应该最先想到的是LruCache算法(Least Recently Used),也叫近期最少使用算法。它的主要算法原理就是把最近使用的对象用强引用存储在LinkedHashMap中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除,不必多说Glide也是使用了LruCache,但是Glide还结合WeakReference+ReferenceQueue机制。

对于Glide加载资源,那就从Engine.load()方法开始:

 public synchronized <R> LoadStatus load(...............) {
    //构建缓存Key
    EngineKey key = keyFactory.buildKey();

    // 第一级内存缓存,资源是否正在使用,里面使用WeakReference+ReferenceQueue机制
 1  EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
        cb.onResourceReady(active, DataSource.MEMORY_CACHE);
        return null;
    }
    // 第二级内存缓存,里面就是LruCache机制
 2   EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
        cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
        return null;
    }

    // 否则开始异步加载,磁盘缓存或者请求额昂罗
    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
        current.addCallback(cb, callbackExecutor);
        return new LoadStatus(cb, current);
    }

    EngineJob<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);
}

可以看到,我标志我1调用了 loadFromActiveResources()方法来获取第一级缓存图片,如果获取到就直接调用cb.onResourceReady()方法进行回调。如果没有获取到,则会在我标志我2调用loadFromCache()方法来获取LruCache缓存图片,获取到的话也直接进行回调。只有在两级缓存都没有获取到的情况下,才会继续向下执行,从而开启线程来加载图片的任务。

也就是说,Glide的图片加载过程中会调用loadFromActiveResources()方法和loadFromCache()来获取内存缓存。当然这两个方法中一个使用的就是LruCache算法,另一个使用的就是弱引用机制。我们来看一下它们的源码:

public class Engine implements EngineJobListener,
    MemoryCache.ResourceRemovedListener,
    EngineResource.ResourceListener {
  
 //.......此处省略一万行代码

  /**
    这个方法检测资源是否正在使用
*/
@Nullable
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
        return null;
    }
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
        active.acquire();
    }
    return active;
}
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
        return null;
    }

    // 在getEngineResourceFromCache方法中,会将已经存在内存缓存中的资源移除之后会加入activeResources缓存中
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
        cached.acquire();
        activeResources.activate(key, cached);
    }
    return cached;
}

private EngineResource<?> getEngineResourceFromCache(Key key) {

    // 从LruCache中获取并移除资源,可能会返回null
    Resource<?> cached = cache.remove(key);
    final EngineResource<?> result;

    //如果返回没有资源,直接返回null
    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, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
    }
    return result;
}


 /**
 * 这个方法是在当资源从LruCache、disLruCache和网络加载成功,将资源加入正在使用中
 *
 * @param engineJob
 * @param key
 * @param resource
 */
 public synchronized void onEngineJobComplete(
        EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
    if (resource != null && resource.isMemoryCacheable()) {
        //当资源加载成功,将资源加入正在使用中,如:LruCache、disLruCache和网络
        activeResources.activate(key, resource);
    }
}

 /**
 * 这个方法会在ActiveResource类中,正在使用的资源被清理时回调过来。
 * 然后重新把资源加入LruCache缓存中,后面会讲ActiveResource的实现
 *
 * @param cacheKey
 * @param resource
 */
 @Override
public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    activeResources.deactivate(cacheKey);
    if (resource.isMemoryCacheable()) {
        cache.put(cacheKey, resource);
    } else {
        resourceRecycler.recycle(resource);
    }
}
 //.......此处省略一万行代码

}

首先loadFromActiveResources方法检测当前Key资源是否正在使用,如果存在直接 cb.onResourceReady()方法资源加载完成,否则调用loadFromCache从LruCache中获取资源, 然后在getEngineResourceFromCache()方法中,会将已经存在内存缓存中的资源remove之后会加入activeResources缓存中.所以资源在内存中的缓存只能存在一份。

当我们从LruResourceCache中获取到缓存图片之后会将它从缓存中移除,然后将缓存图片存储到activeResources当中。
activeResources就是一个弱引用的HashMap,用来缓存正在使用中的图片,我们可以看到,loadFromActiveResources()方法就是从activeResources这个HashMap当中取值的。 使用activeResources来缓存正在使用中的图片,可以保护这些图片不会被LruCache算法回收掉。 为什么会说可以保护这些图片不会被LruCache算法回收掉呢? 因为在ActiveResources中使用WeakReference+ReferenceQueue机制,监控GC回收,如果GC把资源回收,那么ActiveResources 会被再次加入LruCache内存缓存中,从而起到了保护的作用。

当图片加载完成之后,会在EngineJob当中对调用onResourceReady()方法:

 @Override
  public void onResourceReady(Resource<R> resource, DataSource dataSource) {
synchronized (this) {
  this.resource = resource;
  this.dataSource = dataSource;
}
notifyCallbacksOfResult();
  }

接下来就是notifyCallbacksOfResult()方法

void notifyCallbacksOfResult() {
//......此处省略一万行代码

engineJobListener.onEngineJobComplete(this, localKey, localResource);

//......此处省略一万行代码
}

接着回调到Engine的onEngineJobComplete()方法当中,如下所示:

 public synchronized void onEngineJobComplete(
        EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
    // A null resource indicates that the load failed, usually due to an exception.
    if (resource != null && resource.isMemoryCacheable()) {
        //当资源加载成功,将资源加入正在使用中,如:LruCache、disLruCache和网络
   1     activeResources.activate(key, resource);
    }

    jobs.removeIfCurrent(key, engineJob);
}

可以看到我标志1处,调用activeResources的activate方法把回调过来的EngineResourceactivate和Key作为参数一起传入activeResources,就是这里写入缓存,当然在loadFromCache方法中也有写入缓存:
ActiveResources:

synchronized void activate(Key key, EngineResource<?> resource) {
    ResourceWeakReference toPut = new ResourceWeakReference(key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);
    ResourceWeakReference removed = activeEngineResources.put(key, toPut);
    if (removed != null) {
        removed.reset();
    }
}

ActiveResources

上面讲了,当资源加载完成,回调到Engine的onEngineJobComplete(),并调用activeResources的activate方法把回调过来的EngineResourceactivate和Key作为参数一起传入activeResources,那接下来分析ActiveResources:

final class ActiveResources {
private final boolean isActiveResourceRetentionAllowed;
// 监控GC回收资源线程池
private final Executor monitorClearedResourcesExecutor;


//使用强引用存储ResourceWeakReference
@VisibleForTesting
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();


//引用队列与ResourceWeakReference配合监听GC回收
private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();

private ResourceListener listener;

private volatile boolean isShutdown;
@Nullable
private volatile DequeuedResourceCallback cb;

ActiveResources(boolean isActiveResourceRetentionAllowed) {
    this(
            isActiveResourceRetentionAllowed,
            //启动监控线程
            java.util.concurrent.Executors.newSingleThreadExecutor(
                    new ThreadFactory() {
                        @Override
                        public Thread newThread(@NonNull final Runnable r) {
                            return new Thread(
                                    new Runnable() {
                                        @Override
                                        public void run() {
                                            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                                            r.run();
                                        }
                                    },
                                    "glide-active-resources");
                        }
                    }));
}

@VisibleForTesting
ActiveResources(boolean isActiveResourceRetentionAllowed, Executor monitorClearedResourcesExecutor) {
    this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed;
    this.monitorClearedResourcesExecutor = monitorClearedResourcesExecutor;

    monitorClearedResourcesExecutor.execute(
            new Runnable() {
                @Override
                public void run() {
                    cleanReferenceQueue();
                }
            });
}

void setListener(ResourceListener listener) {
    synchronized (listener) {
        synchronized (this) {
            this.listener = listener;
        }
    }
}

/**
 * 资源加载完成调用此方法写入缓存
 *
 * @param key
 * @param resource
 */
synchronized void activate(Key key, EngineResource<?> resource) {
    ResourceWeakReference toPut =
            new ResourceWeakReference(
                    key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);

    ResourceWeakReference removed = activeEngineResources.put(key, toPut);
    if (removed != null) {
        removed.reset();
    }
}

/**
 * 移除指定Key的缓存
 *
 * @param key
 */
synchronized void deactivate(Key key) {
    ResourceWeakReference removed = activeEngineResources.remove(key);
    if (removed != null) {
        removed.reset();
    }
}


/**
 * 通过Key获取缓存
 *
 * @param key
 * @return
 */
@Nullable
synchronized EngineResource<?> get(Key key) {
    ResourceWeakReference activeRef = activeEngineResources.get(key);
    if (activeRef == null) {
        return null;
    }

    EngineResource<?> active = activeRef.get();
    if (active == null) {
        cleanupActiveReference(activeRef);
    }
    return active;
}

@SuppressWarnings({"WeakerAccess", "SynchronizeOnNonFinalField"})
@Synthetic
void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
    // Fixes a deadlock where we normally acquire the Engine lock and then the ActiveResources lock
    // but reverse that order in this one particular test. This is definitely a bit of a hack...
    synchronized (listener) {
        synchronized (this) {
            //将Reference移除HashMap强引用
            activeEngineResources.remove(ref.key);

            if (!ref.isCacheable || ref.resource == null) {
                return;
            }

            //重新构建新的资源
            EngineResource<?> newResource = new EngineResource<>(ref.resource,
                    /*isMemoryCacheable=*/ true,
                    /*isRecyclable=*/ false,
                    ref.key,
                    listener);

            // 如果资源被回收,有可能会回调Engine资源会被再次加入LruCache内存缓存中
            listener.onResourceReleased(ref.key, newResource);
        }
    }
}

@SuppressWarnings("WeakerAccess")
@Synthetic
void cleanReferenceQueue() {
    while (!isShutdown) {
        try {
            //remove会阻塞当前线程,知道GC回收,将ResourceWeakReference放入队列
            ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
            cleanupActiveReference(ref);

            // This section for testing only.
            DequeuedResourceCallback current = cb;
            if (current != null) {
                current.onResourceDequeued();
            }
            // End for testing only.
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

@VisibleForTesting
void setDequeuedResourceCallback(DequeuedResourceCallback cb) {
    this.cb = cb;
}

@VisibleForTesting
interface DequeuedResourceCallback {
    void onResourceDequeued();
}

@VisibleForTesting
void shutdown() {
    isShutdown = true;
    if (monitorClearedResourcesExecutor instanceof ExecutorService) {
        ExecutorService service = (ExecutorService) monitorClearedResourcesExecutor;
        Executors.shutdownAndAwaitTermination(service);
    }
}

@VisibleForTesting
static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
    @SuppressWarnings("WeakerAccess")
    @Synthetic
    final Key key;
    @SuppressWarnings("WeakerAccess")
    @Synthetic
    final boolean isCacheable;

    @Nullable
    @SuppressWarnings("WeakerAccess")
    @Synthetic
    Resource<?> resource;

    @Synthetic
    @SuppressWarnings("WeakerAccess")
    ResourceWeakReference(@NonNull Key key, @NonNull EngineResource<?> referent, @NonNull ReferenceQueue<? super EngineResource<?>> queue, boolean isActiveResourceRetentionAllowed) {
        super(referent, queue);
        this.key = Preconditions.checkNotNull(key);
        this.resource = referent.isMemoryCacheable() && isActiveResourceRetentionAllowed ? Preconditions.checkNotNull(referent.getResource()) : null;
        isCacheable = referent.isMemoryCacheable();
    }

    void reset() {
        resource = null;
        /**
         * 调用此方法会将references加入与之关联的ReferencesQueue队列,当然这个方法有可能GC也会调用此方法清除references中的对象置为null,
         * 所以这里重写WeakReference,使用一个人域保存资源,如果你通过get方法获取对象必定为null
         *
         *
         */
        clear();
    }
}

}

可以看出在ActiveResources中使用Map保存ResourceWeakReference,即:使用强引用存储ResourceWeakReference,而ResourceWeakReference继承WeakReference,那么为什么要重新拓展WeakReference呢?

大家知道文章开头的时候我讲那些知识并不是多余的,在ActiveResources中使用WeakReference+ReferenceQueue机制,监控GC回收,如果GC把资源回收,那么会把WeakReference中的对象置为null,并加入与之关联的ReferenceQueue中,当你通过WeakReference.get()方法返回的是null,所以ResourceWeakReference拓展WeakReference的原因就很明显了,就是使用类的成员变量保存资源对象,如果你通过get方法获取对象必定为null。

磁盘缓存

前面讲过如果两级内存缓存都没有读取到数据,那么Glide就会开启线程去加载资源,不限于网络,也包括了磁盘:

public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
                               DataSource dataSource, Key attemptedKey) {
    this.currentSourceKey = sourceKey;// 保存数据的 key
    this.currentData = data;// 保存数据实体
    this.currentFetcher = fetcher; // 保存数据的获取器
    this.currentDataSource = dataSource;// 数据来源: url 为 REMOTE 类型的枚举, 表示从远程获取
    this.currentAttemptingKey = attemptedKey;
    if (Thread.currentThread() != currentThread) {
        runReason = RunReason.DECODE_DATA;
        callback.reschedule(this);
    } else {
            // 调用 decodeFromRetrievedData 解析获取的数据
            decodeFromRetrievedData();
    }
}

我们知道Glide在两级内存缓存没有获取到,那么会开启线程读取加载图片,当图片加载成功,必然会回调onDataFetcherReady方法,而后调用decodeFromRetrievedData方法解析数据:

private void decodeFromRetrievedData() {
    Resource<R> resource = null;
    try {
        // 1. 调用了 decodeFromData 获取资源,原始数据
        resource = decodeFromData(currentFetcher, currentData, currentDataSource);
    } catch (GlideException e) {
        e.setLoggingDetails(currentAttemptingKey, currentDataSource);
        throwables.add(e);
    }
    if (resource != null) {
        // 2. 通知外界资源获取成功了
        notifyEncodeAndRelease(resource, currentDataSource);
    } else {
        runGenerators();
    }
}

decodeFromRetrievedData方法则是从原始数据中检测是否是原始数据,如果不是则直接返回已经转换过后的资源数据,如果当前data是原始数据,那么Glide就会执行解码之后再返回数据,但是如果是已经做过变换的数据,那么直接调用notifyEncodeAndRelease方法通知上层资源获取成功,我们来看看notifyEncodeAndRelease方法:

 private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {

    Resource<R> result = resource;
    // 1. 回调上层资源准备好了,可以展示
    notifyComplete(result, dataSource);
    stage = Stage.ENCODE;
        // 2. 将数据缓存到磁盘
        if (deferredEncodeManager.hasResourceToEncode()) {
            deferredEncodeManager.encode(diskCacheProvider, options);
        }
    onEncodeComplete();
}

可以看到在notifyEncodeAndRelease方法中,首先就是直接调用 notifyComplete(result, dataSource)方法通知上层资源已经准备好了,接着调用onEngineJobComplete方法写入内存缓存,然后 调用DeferredEncodeManager的encode方法进行编码,将数据写入磁盘,所以这里应该就是磁盘缓存的地方:

    void encode(DiskCacheProvider diskCacheProvider, Options options) {
        GlideTrace.beginSection("DecodeJob.encode");
        try {
            // 写入磁盘缓存
            diskCacheProvider.getDiskCache().put(key, new DataCacheWriter<>(encoder, toEncode, options));
        } finally {
            toEncode.unlock();
            GlideTrace.endSection();
        }
    }

在encode方法中 diskCacheProvider.getDiskCache().put()写入磁盘。

上一篇 下一篇

猜你喜欢

热点阅读