Glide最新源码解析(五)-缓存策略-内存缓存

2019-10-08  本文已影响0人  烧伤的火柴

介绍

内存缓存我的理解就是在内存中持久化一份数据,当下次需要的时候直接取出来使用。对于Android设备就两块内存,一块是磁盘,一块是虚拟机中的堆内存,因为堆内存空间大而且是线程共享的。Glide中利用这两块内存做了极大化的缓存,提高数据的访问速度。所以总结缓存策略就是:内存缓存和磁盘缓存。不过在两种缓存的基础上又做出了优化方法。
说明一点:这里缓存管理的都是Resource<T>对象,该对象是对原生资源(baitmap,drawable,gif)的一种包装
EngineResource<T>实现Resource接口,对资源的引用计算,采用引用计数法,当资源被引用的时候计数器+1,当引用不在使用的时候计数器-1,当计数=0的时候,回调释放资源接口,高层模块决定释放的资源怎么处理。EngineResource是一个代理模式(智能引用)代理Resource对象。

内存缓存

Glide在内存缓存中做出的优化是,加入了弱引用缓存,内存缓存和复用池(bitmapArrayPool,byteArrayPool)

弱引用缓存-ActiveResources

ActiveResources缓存ResourceWeakReference对象,该对象弱引用EngineResource对象。
既然是内存缓存(map管理)就一定离不开增,删,查动作,我们看一下这几个动作的处理方式。
先看一下ActiveResources的结构

final class ActiveResources {
private final boolean isActiveResourceRetentionAllowed;
  private final Executor monitorClearedResourcesExecutor;//监视被清除资源的线程池
  @VisibleForTesting final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
  private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();

  private ResourceListener listener;

  private volatile boolean isShutdown;//是否终止线程

1.在构造的时候会启动一个核心线程,一直运行监视弱引用队列中的引用对象被gc回收掉,被回收掉的对象就调用cleanupActiveReference方法,从缓存中移除引用,并且调用 listener.onResourceReleased(ref.key, newResource);

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

    monitorClearedResourcesExecutor.execute(
        new Runnable() {
          @Override
          public void run() {
            cleanReferenceQueue();
          }
        });
  }
  void cleanReferenceQueue() {
    while (!isShutdown) {//1
      try {
        ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
        cleanupActiveReference(ref);
        ...
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
      }
    }
  }

  void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
    synchronized (this) {
      activeEngineResources.remove(ref.key);

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

    EngineResource<?> newResource =
        new EngineResource<>(
            ref.resource, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ false, ref.key, listener);
    listener.onResourceReleased(ref.key, newResource);//1.释放资源
  }

这里有一个疑问:resourceReferenceQueue是一个阻塞队列,remove方法的时候,没有数据的时候会调用
lock.wait(timeout);挂起线程等待唤醒,所以我感觉不需要while死循环(注释1)。后来请教了Alan老师,才恍然大悟,这个队列是所有ResourceWeakReference对象共享的一个队列,所以需要一直循环监听对象回收的情况。 我们看一下释放的资源怎么处理?实现的地方在Engine中

public class Engine
    implements EngineJobListener,
    MemoryCache.ResourceRemovedListener,
    EngineResource.ResourceListener {
...
    @Override
  public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    activeResources.deactivate(cacheKey);
    if (resource.isMemoryCacheable()) {//支持缓存,放入缓存中
      cache.put(cacheKey, resource);
    } else {//不支持缓存就回收掉
      resourceRecycler.recycle(resource);
    }
  }
...

释放掉的资源放入内存缓存中。

2.添加资源

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

注意这里new ResourceWeakReference的时候,传入的是公用的一个resourceReferenceQueue队列。我们看一下这个类的结构

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;
      clear();
    }
  }

这里使用强引用引用了resource对象,以供后续资源释放的时候使用

2.移除资源

 synchronized void deactivate(Key key) {
    ResourceWeakReference removed = activeEngineResources.remove(key);
    if (removed != null) {
      removed.reset();
    }
  }

3.获取资源

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

总结

ActiveResources缓存的是Resource的弱引用,并且监听Resource被GC回收掉,被回收掉的弱引用使用listener.onResourceReleased(ref.key, newResource);处理,从map主动移除的弱引用都调用reset()释放掉引用的resource

内存缓存

先看一下内存缓存的接口结构

/** 内存缓存中添加和移除资源的接口 */
public interface MemoryCache {
  /** 无论什么时候当bitmap从缓存中移出去的时候回调的接口(这指的移除指的是当内存溢出的时候,要溢出资源来容纳其他资源) */
  interface ResourceRemovedListener {
    void onResourceRemoved(@NonNull Resource<?> removed);
  }

  /** 查询缓存中所有内容的大小,单位是byte */
  long getCurrentSize();

  /** 内存最大的容量 */
  long getMaxSize();

  /**

   * 调整内存大小
   * <p>If the size multiplier causes the size of the cache to be decreased, items will be evicted
   * until the cache is smaller than the new size.
   *
   * @param multiplier A size multiplier >= 0.
   */
  void setSizeMultiplier(float multiplier);

  /**

   * 根据key移除资源,返回移除的资源
   * @param key The key.
   */
  @Nullable
  Resource<?> remove(@NonNull Key key);

  /**
   * 添加资源
   * @param key The key to retrieve the bitmap.
   * @param resource The {@link com.bumptech.glide.load.engine.EngineResource} to store.
   * @return The old value of key (null if key is not in map).
   */
  @Nullable
  Resource<?> put(@NonNull Key key, @Nullable Resource<?> resource);

  /**
   * 资源移除回调
   * @param listener The listener.
   */
  void setResourceRemovedListener(@NonNull ResourceRemovedListener listener);

  /** 清空内存中所有的数据 */
  void clearMemory();

  /**
   * 根据不同的级别调整内存大小
   * @param level This integer represents a trim level as specified in {@link
   *     android.content.ComponentCallbacks2}.
   */
  void trimMemory(int level);
}

接口的实现类有两个一个是MemoryCacheAdapter 适配不适用内存缓存的时候,一个是LruResourceCache
我们主要分析LruResourceCache.java,顾名思义采用的是LRU算法缓存的资源。

 public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {
          private ResourceRemovedListener listener;
          ....
     @Override
    public void setResourceRemovedListener(@NonNull ResourceRemovedListener listener) {
      this.listener = listener;
    }

      @Override
    protected void onItemEvicted(@NonNull Key key, @Nullable Resource<?> item) {
        if (listener != null && item != null) {
        listener.onResourceRemoved(item);//1资源从内存中移除
      }
    }
....
 }

这里采用的是类适配器模式,LruCache中的put get方法去适配MemoryCache 接口中定义的抽象方法。Glide中的LruCache没有采用Android系统的LruCache类,而是自己实现了一个LruCache类,其中主要区别是Glide中的onItemEvicted方法只有在put和trimToSize时候调用。
注释1处资源从内存中移出去的回调,实现该回调的地方是Engine类。

public class Engine
    implements EngineJobListener,
    MemoryCache.ResourceRemovedListener,
    EngineResource.ResourceListener {
 
   ...
   @Override
  public void onResourceRemoved(@NonNull final Resource<?> resource) {
    //回收资源
    resourceRecycler.recycle(resource);
  }
  ...
}

ResourceRecycler类中会按照顺序安全的回收每一个资源,即调用resource.recycle();方法,我们默认用的是BitmapResuource实例,我们看一下BitmapResuource中的recycle方法。

public class BitmapResource implements Resource<Bitmap>, Initializable {
...
@Override
  public void recycle() {
    bitmapPool.put(bitmap);
  }
...
}

被回收掉的bitmap放到了复用池中。小记:内存缓存中当内存溢出的时候,会清理资源腾出空间,以满足其他资源的加入,清理掉的资源会被放入复用池中。

上一篇下一篇

猜你喜欢

热点阅读