美文网首页
Glide缓存机制浅析

Glide缓存机制浅析

作者: leenpong | 来源:发表于2018-11-25 16:46 被阅读0次

前言

由于最近项目加载图片这一块用到了Glide框架,以前也用到过,其最核心的部分应该是属于缓存的机制,
最近花了点时间分析其缓存原理

最简单的Glide的用法:

    Glide.with(context)  .asBitmap()  .load(imageUrl) .into(imageView);

最终都是要调用into方法:

RequestBuilder : 

    return into(
        glideContext.buildImageViewTarget(view, transcodeClass),
        /*targetListener=*/ null,
        requestOptions);
  }

该方法最终会返回一个ThumbnailRequestCoordinator对象,coordinator.setRequests(fullRequest, thumbRequest);从这个可以看出最终是会根据Option返回两个request

这Request是一个SingleRequest对象 
SingleRequest.obtain(....);


这边直接跳到SingleRequest的begin方法:

  @Override
  public void begin() {
    assertNotCallingCallbacks();
    stateVerifier.throwIfRecycled();
    startTime = LogTime.getLogTime();
    if (model == null) {
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        width = overrideWidth;
        height = overrideHeight;
      }
      // Only log at more verbose log levels if the user has set a fallback drawable, because
      // fallback Drawables indicate the user expects null models occasionally.
      int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
      onLoadFailed(new GlideException("Received null model"), logLevel);
      return;
    }

    if (status == Status.RUNNING) {
      throw new IllegalArgumentException("Cannot restart a running request");
    }

    // If we're restarted after we're complete (usually via something like a notifyDataSetChanged
    // that starts an identical request into the same Target or View), we can simply use the
    // resource and size we retrieved the last time around and skip obtaining a new size, starting a
    // new load etc. This does mean that users who want to restart a load because they expect that
    // the view size has changed will need to explicitly clear the View or Target before starting
    // the new load.
    if (status == Status.COMPLETE) {
      onResourceReady(resource, DataSource.MEMORY_CACHE);
      return;
    }

    // Restarts for requests that are neither complete nor running can be treated as new requests
    // and can run again from the beginning.

     //代码1
    status = Status.WAITING_FOR_SIZE;
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
      onSizeReady(overrideWidth, overrideHeight);
    } else {
      target.getSize(this);
    }


    if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
        && canNotifyStatusChanged()) {
      target.onLoadStarted(getPlaceholderDrawable());
    }
    if (IS_VERBOSE_LOGGABLE) {
      logV("finished run method in " + LogTime.getElapsedMillis(startTime));
    }
  }

其中重要的是代码1

     //代码1
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
      onSizeReady(overrideWidth, overrideHeight);
    } else {
      target.getSize(this);
    }

这个地方需要注意的就是这个地方 的overrideWidth和overrideHeight,因为这两个宽高会用于生成缓存的key的参数,从这里可以看出,Glide的缓存是根据给定的宽高或者view的宽高缓存当前的图片,这个是Picaso等图片加载框架不同的地方。

  target.getSize(this);
  如果没有设定特定的宽高,会去拿当前view的宽高


void getSize(@NonNull SizeReadyCallback cb) {
      int currentWidth = getTargetWidth();
      int currentHeight = getTargetHeight();
      if (isViewStateAndSizeValid(currentWidth, currentHeight)) {
        cb.onSizeReady(currentWidth, currentHeight);
        return;
      }

      // We want to notify callbacks in the order they were added and we only expect one or two
      // callbacks to be added a time, so a List is a reasonable choice.
      if (!cbs.contains(cb)) {
        cbs.add(cb);
      }
      if (layoutListener == null) {
        ViewTreeObserver observer = view.getViewTreeObserver();
        layoutListener = new SizeDeterminerLayoutListener(this);
        observer.addOnPreDrawListener(layoutListener);
      }

上面的方法最终会调用到onSizeReady(SingleRequest):

public void onSizeReady(int width, int height) {
    stateVerifier.throwIfRecycled();
    if (IS_VERBOSE_LOGGABLE) {
      logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
    if (status != Status.WAITING_FOR_SIZE) {
      return;
    }
    status = Status.RUNNING;

    float sizeMultiplier = requestOptions.getSizeMultiplier();
    this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
    this.height = maybeApplySizeMultiplier(height, sizeMultiplier);

    if (IS_VERBOSE_LOGGABLE) {
      logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
    }
    loadStatus = engine.load(
        glideContext,
        model,
        requestOptions.getSignature(),
        this.width,
        this.height,
        requestOptions.getResourceClass(),
        transcodeClass,
        priority,
        requestOptions.getDiskCacheStrategy(),
        requestOptions.getTransformations(),
        requestOptions.isTransformationRequired(),
        requestOptions.isScaleOnlyOrNoTransform(),
        requestOptions.getOptions(),
        requestOptions.isMemoryCacheable(),
        requestOptions.getUseUnlimitedSourceGeneratorsPool(),
        requestOptions.getUseAnimationPool(),
        requestOptions.getOnlyRetrieveFromCache(),
        this);

    // This is a hack that's only useful for testing right now where loads complete synchronously
    // even though under any executor running on any thread but the main thread, the load would
    // have completed asynchronously.
    if (status != Status.RUNNING) {
      loadStatus = null;
    }
    if (IS_VERBOSE_LOGGABLE) {
      logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
  }

其中调用Engine的load方法,才是真正去拿图片的过程。
备注:严格来讲,Glide的缓存方式是三级缓存的

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

    //一级缓存,用一个虚引用数组保存数据
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return null;
    }

    //二级缓存,用LruCahce做内存缓存处理
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return null;
    }

    //下面的去decode数据的时候,还会更加相应的key去硬盘缓存拿数据(DiskLruCache)
    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
      current.addCallback(cb);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      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);
    engineJob.start(decodeJob);

    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
  }

先分析一下用去作为换成的的生成:

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

通过相应的参数生成一个EngineKey ,里面对hashCode和equals分别做了重写,也就是说,如果一张
图片的宽高,图片路劲等参数不变,但是做了旋转或者其他不改变参数的操作时,
拿到的会将是缓存的图片,这个时候往往是定义新的singature,让在路径不变的情况下,也可以改变加载新的图片

一级缓存:

  @Nullable
  private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
      active.acquire();
    }

    return active;
  }
其中activeResources是一个ActiveResources对象,里面维护了一Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();这样的HashMap,用ResourceWeakReference弱应用保存
当前的资源,如果拿到直接调用cb.onResourceReady(active, DataSource.MEMORY_CACHE)

如何在弱引用缓存(一级缓存)拿不到数据的时候,拿将会从二级缓存(LruCache)拿数据,并且将数据再
放到弱引用缓存(一级缓存)

二级缓存(LruCache)

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

  private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }

    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      cached.acquire();
      activeResources.activate(key, cached);//放到一级缓存
    }
    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 /*isMemoryCacheable*/, true /*isRecyclable*/);
    }
    return result;
  }

  private final MemoryCache cache 这个cache对象其实就是一个LruResourceCache对象。
从内存换成拿到资源后,放入一级缓存
    if (cached != null) {
      cached.acquire();
      activeResources.activate(key, cached);
    }

  void activate(Key key, EngineResource<?> resource) {
    ResourceWeakReference toPut =
        new ResourceWeakReference(
            key,
            resource,
            getReferenceQueue(),
            isActiveResourceRetentionAllowed);

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

如果从上面一级缓存(弱引用)二级缓存(LruCache)拿不到数据,会去硬盘缓存(DiskLruCache)拿数据
直接跳转到DecodeJob方法里面的run方法:

三级缓存

  @Override
  public void run() {
    DataFetcher<?> localFetcher = currentFetcher;
    try {
      if (isCancelled) {
        notifyFailed();
        return;
      }
      runWrapped();
    } catch (Throwable t) {
     
    } finally {
  
    }
  }


  private void runWrapped() {
    switch (runReason) {
      case INITIALIZE:
        stage = getNextStage(Stage.INITIALIZE);
        currentGenerator = getNextGenerator();
        runGenerators();
        break;
      case SWITCH_TO_SOURCE_SERVICE:
        runGenerators();
        break;
      case DECODE_DATA:
        decodeFromRetrievedData();
        break;
      default:
        throw new IllegalStateException("Unrecognized run reason: " + runReason);
    }
  }

private DataFetcherGenerator getNextGenerator() {
    switch (stage) {
      case RESOURCE_CACHE:
        return new ResourceCacheGenerator(decodeHelper, this);
      case DATA_CACHE:
        return new DataCacheGenerator(decodeHelper, this);
      case SOURCE:
        return new SourceGenerator(decodeHelper, this);
      case FINISHED:
        return null;
      default:
        throw new IllegalStateException("Unrecognized stage: " + stage);
    }
  }


  private void runGenerators() {
    currentThread = Thread.currentThread();
    startFetchTime = LogTime.getLogTime();
    boolean isStarted = false;
    while (!isCancelled && currentGenerator != null
        && !(isStarted = currentGenerator.startNext())) {
      stage = getNextStage(stage);
      currentGenerator = getNextGenerator();

      if (stage == Stage.SOURCE) {
        reschedule();
        return;
      }
    }
    // We've run out of stages and generators, give up.
    if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
      notifyFailed();
    }

    // Otherwise a generator started a new load and we expect to be called back in
    // onDataFetcherReady.
  }

从上面代码可以知道,最重要的方法其实是runGenerators,另外两个方法其实就是最终拿到要使用的
DataFetcherGenerator。
其中currentGenerator.startNext():
分析DataCacheGenerator里面的startNext:

  @Override
  public boolean startNext() {
    while (modelLoaders == null || !hasNextModelLoader()) {
      sourceIdIndex++;
      if (sourceIdIndex >= cacheKeys.size()) {
        return false;
      }

      Key sourceId = cacheKeys.get(sourceIdIndex);
      // PMD.AvoidInstantiatingObjectsInLoops The loop iterates a limited number of times
      // and the actions it performs are much more expensive than a single allocation.
      @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
      Key originalKey = new DataCacheKey(sourceId, helper.getSignature());//(三级缓存)
      cacheFile = helper.getDiskCache().get(originalKey);
      if (cacheFile != null) {
        this.sourceKey = sourceId;
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }

    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
      loadData =
          modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(),
              helper.getOptions());
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
        started = true;
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
    return started;
  }



cacheFile = helper.getDiskCache().get(originalKey); 这个代码就是去硬盘缓存(三级缓存)看能否拿到对应的数据,
这里通过生成一个新的key去cache里面拿数据.


  DiskCache getDiskCache() {
    return diskCacheProvider.getDiskCache();
  }

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

  其中factory正是初始化Glide对象生成的
    if (diskCacheFactory == null) {
      diskCacheFactory = new InternalCacheDiskCacheFactory(context);
    }

  @Override
  public DiskCache build() {
    File cacheDir = cacheDirectoryGetter.getCacheDirectory();

    if (cacheDir == null) {
      return null;
    }

    if (!cacheDir.mkdirs() && (!cacheDir.exists() || !cacheDir.isDirectory())) {
      return null;
    }

    return DiskLruCacheWrapper.create(cacheDir, diskCacheSize);
  }
}

从上面可以知道  cacheFile = helper.getDiskCache().get(originalKey);其实是从
DiskLruCacheWrapper拿到的缓存,而DiskLruCacheWrapper正是对DiskLruCache的一个包装类

分析到上面,知道这个时候已经是从三级缓存拿数据,如果没拿到就去decode相应的数据?
但是这还有一个非常值得注意的地方就是去拿三级缓存数据用到的key,先看一下key的源码:

public interface Key {
  String STRING_CHARSET_NAME = "UTF-8";
  Charset CHARSET = Charset.forName(STRING_CHARSET_NAME);

  /**
   * Adds all uniquely identifying information to the given digest.
   *
   * <p> Note - Using {@link java.security.MessageDigest#reset()} inside of this method will result
   * in undefined behavior. </p>
   */
  void updateDiskCacheKey(@NonNull MessageDigest messageDigest);

  /**
   * For caching to work correctly, implementations <em>must</em> implement this method and
   * {@link #hashCode()}.
   */
  @Override
  boolean equals(Object o);

  /**
   * For caching to work correctly, implementations <em>must</em> implement this method and
   * {@link #equals(Object)}.
   */
  @Override
  int hashCode();
}

从上面发现了一个方法:

 void updateDiskCacheKey(@NonNull MessageDigest messageDigest);

用于在一级缓存和二级缓存拿数据的key, 都是通过key去HashMap数据源拿数据,而HashMa判断一个key是否是同一个,只是用到hashCodeequal是否相同,所以并没有用到上面那个方法,而三级缓存就在获取一个key的时候,比较特殊,会用到上面那个方法去拿到相应的key,所以如果一个图片在一级缓存,二级缓存都没有,但是在三级缓存有,是通过 updateDiskCacheKey去生成key的.
看一下如何用updateDiskCacheKey这个方法的

@Override
  public File get(Key key) {
    String safeKey = safeKeyGenerator.getSafeKey(key);
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      Log.v(TAG, "Get: Obtained: " + safeKey + " for for Key: " + key);
    }
    File result = null;
    try {
      // It is possible that the there will be a put in between these two gets. If so that shouldn't
      // be a problem because we will always put the same value at the same key so our input streams
      // will still represent the same data.
      final DiskLruCache.Value value = getDiskCache().get(safeKey);
      if (value != null) {
        result = value.getFile(0);
      }
    } catch (IOException e) {
      if (Log.isLoggable(TAG, Log.WARN)) {
        Log.w(TAG, "Unable to get from disk cache", e);
      }
    }
    return result;
  }

  String safeKey = safeKeyGenerator.getSafeKey(key);生成对应的String类型的key

  public String getSafeKey(Key key) {
    String safeKey;
    synchronized (loadIdToSafeHash) {
      safeKey = loadIdToSafeHash.get(key);
    }
    if (safeKey == null) {
      safeKey = calculateHexStringDigest(key);
    }
    synchronized (loadIdToSafeHash) {
      loadIdToSafeHash.put(key, safeKey);
    }
    return safeKey;
  }

  private String calculateHexStringDigest(Key key) {
    PoolableDigestContainer container = Preconditions.checkNotNull(digestPool.acquire());
    try {
      key.updateDiskCacheKey(container.messageDigest);//生成相应的key
      // calling digest() will automatically reset()
      return Util.sha256BytesToHex(container.messageDigest.digest());
    } finally {
      digestPool.release(container);
    }
  }

从上面可以知道,是通过key.updateDiskCacheKey(container.messageDigest)去更新messageDigest
然后在将messageDigest里面的数据digest()返回

如果你想修改一个宽高,路径一样的图片,但是又在一级和二级缓存里面没有的图片,如果没有实现
updateDiskCacheKey该方法根据相应的数据更新key,也可能会拿到三级缓存的图片而返回不是想要的
图片

上面就是分析完从三级缓存拿数据

以上如有说的不对的地方,请指正
TXH

相关文章

  • Glide缓存机制浅析

    前言 由于最近项目加载图片这一块用到了Glide框架,以前也用到过,其最核心的部分应该是属于缓存的机制,最近花了点...

  • Glide缓存机制浅析

    into调用链: 来到Engine的load Glide的三级缓存: 活动缓存:使用弱引用缓存图片,表正在使用的图...

  • Glide 加载相同的URL导致无法更新图片问题

    简介:Glide在加载图片的时候默认使用了缓存机制。Glide的缓存机制分为二级:内存缓存、磁盘缓存。缓存的过程是...

  • Glide实现原理

    一.Glide缓存机制 Glide采取的多级缓存机制,能够较为友好地实现图片、动图的加载。其主要有 内存缓存+磁盘...

  • Glide源码分析

    一、源码分析:1、with()2、load()3、into()二、缓存机制1、Glide缓存机制简介1.1缓存的图...

  • Android 【手撕Glide】--Glide缓存机制(面试)

    本文源码解析基于Glide 4.6.1 系列文章Android 【手撕Glide】--Glide缓存机制Andro...

  • Glide 面试题

    1. 简单介绍一下Glide缓存 Glide 缓存机制主要分为2种:内存缓存和磁盘缓存使用内存缓存的原因是:防止应...

  • glide4.7.1解析

    本文章整理:Android源码分析:手把手带你分析 Glide的缓存功能 1. Glide缓存机制简介 1.1 缓...

  • Glide->02Bitmap复用

    参考文章: Glide源码分析之缓存处理 Glide缓存机制 一、源码解析: 如果是第一次加载图片, 即不存在缓存...

  • Android图片框架之Glide

    Gilde缓存机制 Glide缓存分为内存缓存和磁盘缓存,其中内存缓存是由弱引用+LruCache组成。 取的顺序...

网友评论

      本文标题:Glide缓存机制浅析

      本文链接:https://www.haomeiwen.com/subject/evspqqtx.html