Glide图片缓存机制

105 阅读7分钟

这里以5.0.0-rc1版本为例,缓存基本离不开池化的概念,了解此概念有助于理解本篇文章

一、BitmapRecycle

bitmap回收池:

这里着重看BitmapPool,LruPoolStrategy,GroupedLinkedMap这三个以及其实现类即可,感觉设计理念和精髓就在其中!其它的有兴趣可以自己研究下;

1.1BitmapPool接口,以及其实现类LruBitmapPool

1.1.1 BitmapResource

采用obtain(类比handler)方式重用bitmap

public class BitmapResource implements Resource<Bitmap>, Initializable {
  private final Bitmap bitmap;
  private final BitmapPool bitmapPool;

  /**
* Returns a new { @link BitmapResource} wrapping the given { @link Bitmap} if the Bitmap is
* non-null or null if the given Bitmap is null.
*
* @param bitmap A Bitmap.
* @param bitmapPool A non-null { @link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool}.
*/
@Nullable
  public static BitmapResource obtain(@Nullable Bitmap bitmap, @NonNull BitmapPool bitmapPool) {
    if (bitmap == null) {
      return null;
    } else {
      return new BitmapResource(bitmap, bitmapPool);
    }
  }

  public BitmapResource(@NonNull Bitmap bitmap, @NonNull BitmapPool bitmapPool) {
    this.bitmap = Preconditions.checkNotNull(bitmap, "Bitmap must not be null");
    this.bitmapPool = Preconditions.checkNotNull(bitmapPool, "BitmapPool must not be null");
  }

  @NonNull
  @Override
  public Class<Bitmap> getResourceClass() {
    return Bitmap.class;
  }

  @NonNull
  @Override
  public Bitmap get() {
    return bitmap;
  }

  @Override
  public int getSize() {
    return Util.getBitmapByteSize(bitmap);
  }

  @Override
  public void recycle() {
    bitmapPool.put(bitmap);
  }

  @Override
  public void initialize() {
    bitmap.prepareToDraw();
  }
}

1.1.2 BitmapPool

//返回当前bitmap pool的大小
long getMaxSize();
//动态扩大pool大小乘积
void setSizeMultiplier(float sizeMultiplier);
//存bitmap
void put(Bitmap bitmap);
//取bitmap,下方是具体实现方式之一,但是如果遇到非空但是key相同,需要擦除,否则会出现图片跳动问题
Bitmap get(int width, int height, Bitmap.Config config);
//是上面的具体实现
Bitmap getDirty(int width, int height, Bitmap.Config config);
//清缓存
void clearMemory();
void trimMemory(int level);

1.1.3 LruBitmapPool

实现类(config代码以及策略选择代码直接贴进去了,后面主要看策略):

LruBitmapPool(long maxSize, LruPoolStrategy strategy, Set<Bitmap.Config> allowedConfigs) {
  this.initialMaxSize = maxSize;//bitmap池大初始大小
  this.maxSize = maxSize;//bitmap池最大
  this.strategy = strategy;//缓存策略
  this.allowedConfigs = allowedConfigs;//缓存配置
  this.tracker = new NullBitmapTracker();//啥都没干,空实现,根据其定义接口应该是调试时统计数据判断算法的优劣与否
}

//根据版本的不同具体设置缓存策略的配置,在android O之上移除了位图
private static Set<Bitmap.Config> getDefaultAllowedConfigs() {
  Set<Bitmap.Config> configs = new HashSet<>(Arrays.asList(Bitmap.Config.values()));
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    // GIFs, among other types, end up with a native Bitmap config that doesn't map to a java
    // config and is treated as null in java code. On KitKat+ these Bitmaps can be reconfigured
    // and are suitable for re-use.
    configs.add(null);
  }
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    configs.remove(Bitmap.Config.HARDWARE);
  }
  return Collections.unmodifiableSet(configs);
}

//缓存策略选取,这里根据安卓版本号进行了具体的策略区分
private static LruPoolStrategy getDefaultStrategy() {
  final LruPoolStrategy strategy;
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    strategy = new SizeConfigStrategy();
  } else {
    strategy = new AttributeStrategy();
  }
  return strategy;
}

1.1.4 SizeConfigStrategy和AttributeStrategy的区别

核心代码区别(GroupedLinkedMap数据结构后面说):

//SizeStrategy @RequiresApi(Build.VERSION_CODES.KITKAT)
@Override
public void put(Bitmap bitmap) {
  int size = Util.getBitmapByteSize(bitmap);
  final Key key = keyPool.get(size);

  groupedMap.put(key, bitmap);

  Integer current = sortedSizes.get(key.size);
  sortedSizes.put(key.size, current == null ? 1 : current + 1);
}

//另附:Util.getBitmapByteSize(bitmap),也就是为什么4.4是分界
@TargetApi(Build.VERSION_CODES.KITKAT)
public static int getBitmapByteSize(@NonNull Bitmap bitmap) {
  // The return value of getAllocationByteCount silently changes for recycled bitmaps from the
  // internal buffer size to row bytes * height. To avoid random inconsistencies in caches, we
  // instead assert here.
  if (bitmap.isRecycled()) {
    throw new IllegalStateException(
        "Cannot obtain size for recycled Bitmap: "
            + bitmap
            + "["
            + bitmap.getWidth()
            + "x"
            + bitmap.getHeight()
            + "] "
            + bitmap.getConfig());
  }
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    // Workaround for KitKat initial release NPE in Bitmap, fixed in MR1. See issue #148.
    try {
      return bitmap.getAllocationByteCount();
    } catch (
        @SuppressWarnings("PMD.AvoidCatchingNPE")
        NullPointerException e) {
      // Do nothing.
    }
  }
  return bitmap.getHeight() * bitmap.getRowBytes();
}

//AttributeStrategy
@Override
public void put(Bitmap bitmap) {
  final Key key = keyPool.get(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());

  groupedMap.put(key, bitmap);
}

1.1.5 GroupedLinkedMap

代码就不放了,简单画个图看一下意思吧:

image.png

1.1.6 put操作

@Override
public synchronized void put(Bitmap bitmap) {
  if (bitmap == null) {//判空
    throw new NullPointerException("Bitmap must not be null");
  }
  if (bitmap.isRecycled()) {//是否被回收
    throw new IllegalStateException("Cannot pool recycled bitmap");
  }
  if (!bitmap.isMutable()//可变位图
      || strategy.getSize(bitmap) > maxSize
      || !allowedConfigs.contains(bitmap.getConfig())) {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      Log.v(
          TAG,
          "Reject bitmap from pool"
              + ", bitmap: "
              + strategy.logBitmap(bitmap)
              + ", is mutable: "
              + bitmap.isMutable()
              + ", is allowed config: "
              + allowedConfigs.contains(bitmap.getConfig()));
    }
    bitmap.recycle();//不符合条件,回收bitmap
    return;
  }

  final int size = strategy.getSize(bitmap);//根据系统版本获取bitmap策略
  strategy.put(bitmap);//存储bitmap
  tracker.add(bitmap);

  puts++;
  currentSize += size;

  if (Log.isLoggable(TAG, Log.VERBOSE)) {
    Log.v(TAG, "Put bitmap in pool=" + strategy.logBitmap(bitmap));
  }
  dump();//记录信息,调试打印用的

  evict();//调整大小
}

1.1.7 get操作

@Override
@NonNull
public Bitmap get(int width, int height, Bitmap.Config config) {
  Bitmap result = getDirtyOrNull(width, height, config);
  if (result != null) {
    // Bitmaps in the pool contain random data that in some cases must be cleared for an image
    // to be rendered correctly. we shouldn't force all consumers to independently erase the
    // contents individually, so we do so here. See issue #131.
    result.eraseColor(Color.TRANSPARENT);//如果有可用的脏数据进行图像擦除,防止跳动
  } else {
    result = createBitmap(width, height, config);//创建新的bitmap
  }

  return result;
}

@NonNull
@Override
public Bitmap getDirty(int width, int height, Bitmap.Config config) {
  Bitmap result = getDirtyOrNull(width, height, config);
  if (result == null) {
    result = createBitmap(width, height, config);
  }
  return result;
}

@Nullable
private synchronized Bitmap getDirtyOrNull(
    int width, int height, @Nullable Bitmap.Config config) {
  assertNotHardwareConfig(config);
  // Config will be null for non public config types, which can lead to transformations naively
  // passing in null as the requested config here. See issue #194.
  //就这行代码是关键,后面可以忽略
  final Bitmap result = strategy.get(width, height, config != null ? config : DEFAULT_CONFIG);
  //省略部分代码
  return result;
}

二、DiskLruCache

三、MemoryCache

这里内存就比较简单了,说明复杂的点在后面。。。

3.1MemoryCache

3.2实现类:

四、缓存调用(重点来了)

需要从load方法入手,不过还是老样子,还得看相应的接口以及实现类,后面再把整个流程串起来。

4.1一些工具类的介绍

4.1.1 Engine

顾名思义就是图片处理引擎:

再来看一下重要的参数:

private static final String TAG = "Engine";
private static final int JOB_POOL_SIZE = 150;
private static final boolean VERBOSE_IS_LOGGABLE = Log.isLoggable(TAG, Log.VERBOSE);
private final Jobs jobs;//管理EngineJob的集合,是个大工程,后面细说
private final EngineKeyFactory keyFactory;//资源key工厂
private final MemoryCache cache;//内存缓存
private final EngineJobFactory engineJobFactory;//引擎工作工厂
private final ResourceRecycler resourceRecycler;//资源回收者
private final LazyDiskCacheProvider diskCacheProvider;//硬盘缓存策略提供者
private final DecodeJobFactory decodeJobFactory;//解码工厂,这个东西比较多,后面会单独说这个
private final ActiveResources activeResources;//活跃资源管理类,主要存放的是弱引用资源,其中会有一个专门的线程根据key释放跃资源

4.1.2 ActiveResources

用来管理弱引用的活跃资源的工具类

4.1.3MemoryCache

这次从MemoryCache获取之后同时让其处于活跃状态,MemoryCache存取上面做了具体说明,这里不再赘述。

private EngineResource<?> loadFromCache(Key key) {
  EngineResource<?> cached = getEngineResourceFromCache(key);
  if (cached != null) {
    cached.acquire();
    activeResources.activate(key, cached);
  }
  return cached;
}

4.1.4 LazyDiskCacheProvider

获取硬盘缓存策略,也就是真正实现硬盘缓存的提供者

private static class LazyDiskCacheProvider implements DecodeJob.DiskCacheProvider {

  private final DiskCache.Factory factory;
  private volatile DiskCache diskCache;

  LazyDiskCacheProvider(DiskCache.Factory factory) {
    this.factory = factory;
  }

  @VisibleForTesting
  synchronized void clearDiskCacheIfCreated() {
    if (diskCache == null) {
      return;
    }
    diskCache.clear();
  }

  @Override
  public DiskCache getDiskCache() {
    if (diskCache == null) {
      synchronized (this) {
        if (diskCache == null) {
          diskCache = factory.build();
        }
        if (diskCache == null) {
          diskCache = new DiskCacheAdapter();
        }
      }
    }
    return diskCache;
  }
}

public static GlideExecutor.Builder newDiskCacheBuilder() {
  return new GlideExecutor.Builder(/* preventNetworkOperations= */ true)
      .setThreadCount(DEFAULT_DISK_CACHE_EXECUTOR_THREADS /*1*/ )
      .setName(DEFAULT_DISK_CACHE_EXECUTOR_NAME /*disk-cache*/ );
}

4.1.5Jobs

处理任务的job集合

final class Jobs {
  private final Map<Key, EngineJob<?>> jobs = new HashMap<>();
  private final Map<Key, EngineJob<?>> onlyCacheJobs = new HashMap<>();

  @VisibleForTesting
  Map<Key, EngineJob<?>> getAll() {
    return Collections.unmodifiableMap(jobs);
  }

  EngineJob<?> get(Key key, boolean onlyRetrieveFromCache) {
    return getJobMap(onlyRetrieveFromCache).get(key);
  }

  void put(Key key, EngineJob<?> job) {
    getJobMap(job.onlyRetrieveFromCache()).put(key, job);
  }

  void removeIfCurrent(Key key, EngineJob<?> expected) {
    Map<Key, EngineJob<?>> jobMap = getJobMap(expected.onlyRetrieveFromCache());
    if (expected.equals(jobMap.get(key))) {
      jobMap.remove(key);
    }
  }

  private Map<Key, EngineJob<?>> getJobMap(boolean onlyRetrieveFromCache) {
    return onlyRetrieveFromCache ? onlyCacheJobs : jobs;
  }
}

4.1.6 GlideExecutor

所有job的executor都是从这个build中fork出来的

public GlideExecutor build() {
  if (TextUtils.isEmpty(name)) {
    throw new IllegalArgumentException(
        "Name must be non-null and non-empty, but given: " + name);
  }
  ThreadPoolExecutor executor =
      new ThreadPoolExecutor(
          corePoolSize,
          maximumPoolSize,
          /* keepAliveTime= */ threadTimeoutMillis,
          TimeUnit.MILLISECONDS,
          new PriorityBlockingQueue<Runnable>(),
          new DefaultThreadFactory(
              threadFactory, name, uncaughtThrowableStrategy, preventNetworkOperations));

  if (threadTimeoutMillis != NO_THREAD_TIMEOUT) {
    executor.allowCoreThreadTimeOut(true);
  }

  return new GlideExecutor(executor);
}

4.2具体调用

中间会省略大部分代码

4.2.1优先从活跃资源或硬盘缓存中取

EngineResource<?> memoryResource;
synchronized (this) {
  memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
    if (memoryResource == null) {
      return waitForExistingOrStartNewJob(/*省略*/)
     }
}

private EngineResource<?> loadFromMemory(
    EngineKey key, boolean isMemoryCacheable, long startTime) {
  if (!isMemoryCacheable) {
    return null;
  }

  EngineResource<?> active = loadFromActiveResources(key);
  if (active != null) {
    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Loaded resource from active resources", startTime, key);
    }
    return active;
  }

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

  return null;
  }
}

4.2.2都没有取到开始根据资源类型进行匹配引擎和解码job

waitForExistingOrStartNewJob方法:

private <R> LoadStatus waitForExistingOrStartNewJob(/*省略*/) {

  EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
  if (current != null) {
    current.addCallback(cb, callbackExecutor);
    return new LoadStatus(cb, current);
  }

  EngineJob<R> engineJob =
      engineJobFactory.build(/*省略*/);

  DecodeJob<R> decodeJob =
      decodeJobFactory.build(/*省略*/);

  jobs.put(key, engineJob);

  engineJob.addCallback(cb, callbackExecutor);
  engineJob.start(decodeJob);
  return new LoadStatus(cb, engineJob);
}

4.3.3关于BitmapPool

public void run() {
//...
  DataFetcher<?> localFetcher = currentFetcher;
  try {
    if (isCancelled) {
      notifyFailed();
      return;
    }
    runWrapped();
  } catch (CallbackException e) {
//...
}

private void runWrapped() {
  switch (runReason) {
//...
    case DECODE_DATA:
      decodeFromRetrievedData();
      break;
//...
}

private void decodeFromRetrievedData() {
//...
  Resource<R> resource = null;
  try {
    resource = decodeFromData(currentFetcher, currentData, currentDataSource);
  } catch (GlideException e) {
 //...
  }
}

private <Data> Resource<R> decodeFromData(
    DataFetcher<?> fetcher, Data data, DataSource dataSource) throws GlideException {
  try {
    if (data == null) {
      return null;
    }
    long startTime = LogTime.getLogTime();
    Resource<R> result = decodeFromFetcher(data, dataSource);
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey("Decoded result " + result, startTime);
    }
    return result;
  } finally {
    fetcher.cleanup();
  }
}

private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)
    throws GlideException {
  LoadPath<Data, ?, R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());
  return runLoadPath(data, dataSource, path);
}

<Data> LoadPath<Data, ?, Transcode> getLoadPath(Class<Data> dataClass) {
  return glideContext.getRegistry().getLoadPath(dataClass, resourceClass, transcodeClass);
}

public <Data, TResource, Transcode> LoadPath<Data, TResource, Transcode> getLoadPath(
    @NonNull Class<Data> dataClass,
    @NonNull Class<TResource> resourceClass,
    @NonNull Class<Transcode> transcodeClass) {
  LoadPath<Data, TResource, Transcode> result =
      loadPathCache.get(dataClass, resourceClass, transcodeClass);
  if (loadPathCache.isEmptyLoadPath(result)) {
    return null;
  } else if (result == null) {
    List<DecodePath<Data, TResource, Transcode>> decodePaths =
        getDecodePaths(dataClass, resourceClass, transcodeClass);
    // It's possible there is no way to decode or transcode to the desired types from a given
    // data class.
    if (decodePaths.isEmpty()) {
      result = null;
    } else {
      result =
          new LoadPath<>(
              dataClass, resourceClass, transcodeClass, decodePaths, throwableListPool);
    }
    loadPathCache.put(dataClass, resourceClass, transcodeClass, result);
  }
  return result;
}

@NonNull
private <Data, TResource, Transcode> List<DecodePath<Data, TResource, Transcode>> getDecodePaths(
    @NonNull Class<Data> dataClass,
    @NonNull Class<TResource> resourceClass,
    @NonNull Class<Transcode> transcodeClass) {
  List<DecodePath<Data, TResource, Transcode>> decodePaths = new ArrayList<>();
  List<Class<TResource>> registeredResourceClasses =
      decoderRegistry.getResourceClasses(dataClass, resourceClass);

  for (Class<TResource> registeredResourceClass : registeredResourceClasses) {
    List<Class<Transcode>> registeredTranscodeClasses =
        transcoderRegistry.getTranscodeClasses(registeredResourceClass, transcodeClass);

    for (Class<Transcode> registeredTranscodeClass : registeredTranscodeClasses) {

      List<ResourceDecoder<Data, TResource>> decoders =
          decoderRegistry.getDecoders(dataClass, registeredResourceClass);
      ResourceTranscoder<TResource, Transcode> transcoder =
          transcoderRegistry.get(registeredResourceClass, registeredTranscodeClass);
      @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
      DecodePath<Data, TResource, Transcode> path =
          new DecodePath<>(
              dataClass,
              registeredResourceClass,
              registeredTranscodeClass,
              decoders,
              transcoder,
              throwableListPool);
      decodePaths.add(path);
    }
  }
  return decodePaths;
}

五、调用过程

image.png