Android 图片加载框架Glide缓存原理源码分析

上一篇 Android 图片加载框架Glide主流程源码分析


一个好使的图片加载框架,与它的缓存设计关系密切,这里来通过源码看看Glide是怎么设计它的缓存的吧。


根据加载主流程我们可以知道Glide的使用是要先初始化Glide单例,进入到GlideBuilder

public final class GlideBuilder {
  ...
  public Glide build(Context context) {
    // 开始初始化glide
    if (sourceExecutor == null) {
      sourceExecutor = GlideExecutor.newSourceExecutor();
    }

    if (diskCacheExecutor == null) {
      diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
    }

    if (animationExecutor == null) {
      animationExecutor = GlideExecutor.newAnimationExecutor();
    }

    if (memorySizeCalculator == null) {
      memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
    }

    if (connectivityMonitorFactory == null) {
      connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();
    }

    if (bitmapPool == null) {
      int size = memorySizeCalculator.getBitmapPoolSize();
      if (size > 0) {
        bitmapPool = new LruBitmapPool(size);
      } else {
        bitmapPool = new BitmapPoolAdapter();
      }
    }

    if (arrayPool == null) {
      arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
    }

    if (memoryCache == null) {
      memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
    }

    if (diskCacheFactory == null) {
      diskCacheFactory = new InternalCacheDiskCacheFactory(context);
    }

    if (engine == null) {
      engine =
          new Engine(
              memoryCache,
              diskCacheFactory,
              diskCacheExecutor,
              sourceExecutor,
              GlideExecutor.newUnlimitedSourceExecutor(),
              GlideExecutor.newAnimationExecutor(),
              isActiveResourceRetentionAllowed);
    }

    RequestManagerRetriever requestManagerRetriever =
        new RequestManagerRetriever(requestManagerFactory);

    return new Glide(
        context,
        engine,
        memoryCache,
        bitmapPool,
        arrayPool,
        requestManagerRetriever,
        connectivityMonitorFactory,
        logLevel,
        defaultRequestOptions.lock(),
        defaultTransitionOptions);
  }  
  ...
}

这个方法挺长的,第10行,初始化操作磁盘缓存的线程池,

第18行,初始化内存大小,接着得到的memoryCache、bitmapPool、arrayPool分别传入Engine、Glide实例中,

跟踪看下18行,看下内存缓存大小怎么计算的

public final class MemorySizeCalculator {
  ...
 // Package private to avoid PMD warning.
  MemorySizeCalculator(MemorySizeCalculator.Builder builder) {
    this.context = builder.context;

    arrayPoolSize =
        isLowMemoryDevice(builder.activityManager)
            ? builder.arrayPoolSizeBytes / LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR
            : builder.arrayPoolSizeBytes;
    // 计算APP可申请最大使用内存,再乘以乘数因子,内存过低时乘以0.33,一般情况乘以0.4
    int maxSize =
        getMaxSize(
            builder.activityManager, builder.maxSizeMultiplier, builder.lowMemoryMaxSizeMultiplier);

    int widthPixels = builder.screenDimensions.getWidthPixels();
    int heightPixels = builder.screenDimensions.getHeightPixels();
    // ARGB_8888 ,每个像素占用4个字节内存
    // 计算屏幕这么大尺寸的图片占用内存大小
    int screenSize = widthPixels * heightPixels * BYTES_PER_ARGB_8888_PIXEL;
    // 计算目标位图池内存大小
    int targetBitmapPoolSize = Math.round(screenSize * builder.bitmapPoolScreens);
    // 计算目标Lrucache内存大小,也就是屏幕尺寸图片大小乘以2
    int targetMemoryCacheSize = Math.round(screenSize * builder.memoryCacheScreens);
    // 最终APP可用内存大小
    int availableSize = maxSize - arrayPoolSize;
    if (targetMemoryCacheSize + targetBitmapPoolSize <= availableSize) {
      // 如果目标位图内存大小+目标Lurcache内存大小小于APP可用内存大小,则OK
      memoryCacheSize = targetMemoryCacheSize;
      bitmapPoolSize = targetBitmapPoolSize;
    } else {
      // 否则用APP可用内存大小等比分别赋值
      float part = availableSize / (builder.bitmapPoolScreens + builder.memoryCacheScreens);
      memoryCacheSize = Math.round(part * builder.memoryCacheScreens);
      bitmapPoolSize = Math.round(part * builder.bitmapPoolScreens);
    }

    if (Log.isLoggable(TAG, Log.DEBUG)) {
      Log.d(
          TAG,
          "Calculation complete"
              + ", Calculated memory cache size: "
              + toMb(memoryCacheSize)
              + ", pool size: "
              + toMb(bitmapPoolSize)
              + ", byte array size: "
              + toMb(arrayPoolSize)
              + ", memory class limited? "
              + (targetMemoryCacheSize + targetBitmapPoolSize > maxSize)
              + ", max size: "
              + toMb(maxSize)
              + ", memoryClass: "
              + builder.activityManager.getMemoryClass()
              + ", isLowMemoryDevice: "
              + isLowMemoryDevice(builder.activityManager));
    }
  } 
  ...
}


注释写得比较清楚了,就不解释了,那么Glide在哪开始使用缓存的呢?


public class Engine implements EngineJobListener,
    MemoryCache.ResourceRemovedListener,
    EngineResource.ResourceListener {
  ...
   public <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) {
    Util.assertMainThread();
    long startTime = LogTime.getLogTime();

    // 根据各种参数创建图片key
    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 (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return null;
    }

    // 检查内存中Lrucache是否有目标图片
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return null;
    }

    // 内存中没有图片构建任务往下执行
    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
      current.addCallback(cb);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        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 (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
  }
  ...
}

Engine的load()方法还熟悉吧,很长一串

28-29,创建图片URL、宽、高等一系列参数创建key

31-39,从内存缓存弱引用中根据Key获取图片资源,有就返回,没有往下执行

41-49,从内存缓存Lrucache中根绝key获取图片资源,有就返回,没有往下执行

通过以上代码可以看出Glide内存缓存采用了2级,第一级是弱引用,第二级才是Lrucache,如果软引用中没有

对应的图片缓存,就从Lrucache中获取,如果还是没有才去检查磁盘缓存,如果还是没有最后才去网络下载


这里来看下获取内存缓存图片资源的方法loadFromActiveResources()和loadFromCache(),

首先跟踪loadFromActiveResources()

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

final class ActiveResources {
  ...
  @Nullable
  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;
  }
  ...
}

这里是从内存缓存的弱引用中获取的相应图片资源

第7行,判断缓存是否开启,如果可用才继续进行,否则返回null,默认是开启的,我们也可以使用的时候关闭,如下

      ImgurGlide.with(vh.imageView)
          .load(image.link)
          .skipMemoryCache(true)

          .into(vh.imageView);

第10行,根据图片key获取软引用图片资源active,如果不为null,则调用active.acquire(),返回相应资源

这里要注意2个方法acquire()和release()

class EngineResource<Z> implements Resource<Z> {
  ...
  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");
    }
    ++acquired;
  }

  /**
   * Decrements the number of consumers using the wrapped resource. Must be called on the main
   * thread.
   *
   * <p>This must only be called when a consumer that called the {@link #acquire()} method is now
   * done with the resource. Generally external users should never call this method, the framework
   * will take care of this for you.
   */
  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) {
      listener.onResourceReleased(key, this);
    }
  }
  ...
}

第10行和第28行,可以看到当调用acquire(),acquired这个成员变量会自增1,当调用release(),acquired这个

成员变量会自减1,当acquired数量大于0,说明当前EngineResource实例被使用中


全局搜索可以知道当前资源加载结束后,会调用release(),看下29行,一个回调,找到实现的类Engine

public class Engine implements EngineJobListener,
    MemoryCache.ResourceRemovedListener,
    EngineResource.ResourceListener {
  ...
  @Override
  public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    Util.assertMainThread();
    // 从内存弱引用中移除图片
    activeResources.deactivate(cacheKey);
    if (resource.isCacheable()) {
      // 内存Lrucache中添加图片
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
  }
  ...
}

第9行,从内存弱引用中移除图片

第12行,内存Lrucache中添加此图片


接下来看下loadFromCache()方法

public class Engine implements EngineJobListener,
    MemoryCache.ResourceRemovedListener,
    EngineResource.ResourceListener {
  ...
  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;
  }

  @SuppressWarnings("unchecked")
  private EngineResource<?> getEngineResourceFromCache(Key key) {
    //cache 是LruResourceCache实例
    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;
  }
  ...
}

第6行,判断是否开启内存缓存,默认是开启的

10-15行,从内存Lrucache缓存中获取key对应的图片资源并返回

第12行,调用acquire(),同理当前资源调用数+1

第13行,跟踪进入ActiveResources类

final class ActiveResources {
  ...
  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缓存中的资源存入弱引用缓存

到这里其实应该比较清晰了,Glide加载的内存缓存的图片资源始终是软引用的,当引用中有则返回资源,且资源存入

Lrucache中,如果没有,从Lrucache中返回存入弱引用中,如果Lrucache中也没有呢?


以上就是Glide内存缓存的源码分析,接下来开始磁盘缓存的分析


public abstract class DiskCacheStrategy {
  ...
  public static final DiskCacheStrategy ALL = new DiskCacheStrategy() {
    @Override
    public boolean isDataCacheable(DataSource dataSource) {
      return dataSource == DataSource.REMOTE;
    }

    @Override
    public boolean isResourceCacheable(boolean isFromAlternateCacheKey, DataSource dataSource,
        EncodeStrategy encodeStrategy) {
      return dataSource != DataSource.RESOURCE_DISK_CACHE && dataSource != DataSource.MEMORY_CACHE;
    }

    @Override
    public boolean decodeCachedResource() {
      return true;
    }

    @Override
    public boolean decodeCachedData() {
      return true;
    }
  };
  ...
}

磁盘缓存策略,DiskCacheStrategy这个类定义了几种策略类型

DiskCacheStrategy.ALL : 

表示既缓存原始图片,也缓存转换过后的图片。对于远程图片,缓存DATA和RESOURCE。对于本地图片,只缓存RESOURCE。


DiskCacheStrategy.AUTOMATIC (默认策略):

它会尝试对本地和远程图片使用最佳的策略。当你加载远程数据(比如,从URL下载)时,AUTOMATIC 策略仅会存储未被你的加载过程修改过(比如,变换,裁剪–译者注)的原始数据(DATA),因为下载远程数据相比调整磁盘上已经存在的数据要昂贵得多。对于本地数据,AUTOMATIC 策略则会仅存储变换过的缩略图(RESOURCE),因为即使你需要再次生成另一个尺寸或类型的图片,取回原始数据也很容易。


DiskCacheStrategy.DATA:

表示只缓存未被处理的文件。我的理解就是我们获得的stream。它是不会被展示出来的,需要经过装载decode,对图片进行压缩和转换,等等操作,得到最终的图片才能被展示。


DiskCacheStrategy.NONE: 

表示不缓存任何内容。


DiskCacheStrategy.RESOURCE:

表示只缓存转换过后的图片。(也就是经过decode,转化裁剪的图片)


默认的策略为DiskCacheStrategy.AUTOMATIC,改变策略也很简单, 比如

    ImgurGlide.with(vh.imageView)
          .load(image.link)
          .diskCacheStrategy(DiskCacheStrategy.ALL)

          .into(vh.imageView);


我们这里就用默认磁盘缓存策略分析


通过上一篇的加载流程,我们可以直接找到DecodeJob类,获取磁盘缓存的步骤就在里面

class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,
    Runnable,
    Comparable<DecodeJob<?>>,
    Poolable {
  ...
  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 Stage getNextStage(Stage current) {
    switch (current) {
      case INITIALIZE:
        // 检查磁盘缓存策略,是否解码缓存的转换图片
        return diskCacheStrategy.decodeCachedResource()
            ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
      case RESOURCE_CACHE:
        // 检查磁盘缓存策略,是否解码缓存的原始数据,这里理解为流
        return diskCacheStrategy.decodeCachedData()
            ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
      case DATA_CACHE:
        // Skip loading from source if the user opted to only retrieve the resource from cache.
        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        return Stage.FINISHED;
      default:
        throw new IllegalArgumentException("Unrecognized stage: " + current);
    }
  }
  ...
}

这3个方法,非常绕,

30-58行,这段代码的逻辑就是根据磁盘缓存策略把检查步骤往下推,第一步检查转换过的图片是否有缓存,

第二步检查未被转化过的图片资源缓存,第三步检查数据源,比如本地相册原始图片,网络URL原始图片。

如果目标图片已经加载过缓存在磁盘了,但是内存缓存中还没有,那么其实在第一步检查中就会获取到,然后加载显示。

这也是大多数的情况加载磁盘缓存过的图片资源,这里以这种情况为例继续分析


class ResourceCacheGenerator implements DataFetcherGenerator,
    DataFetcher.DataCallback<Object> {
  ...
  @Override
  public boolean startNext() {
    List<Key> sourceIds = helper.getCacheKeys();
    if (sourceIds.isEmpty()) {
      return false;
    }
    List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();
    while (modelLoaders == null || !hasNextModelLoader()) {
      resourceClassIndex++;
      if (resourceClassIndex >= resourceClasses.size()) {
        sourceIdIndex++;
        if (sourceIdIndex >= sourceIds.size()) {
          return false;
        }
        resourceClassIndex = 0;
      }

      Key sourceId = sourceIds.get(sourceIdIndex);
      Class<?> resourceClass = resourceClasses.get(resourceClassIndex);
      Transformation<?> transformation = helper.getTransformation(resourceClass);

      currentKey =
          new ResourceCacheKey(
              helper.getArrayPool(),
              sourceId,
              helper.getSignature(),
              helper.getWidth(),
              helper.getHeight(),
              transformation,
              resourceClass,
              helper.getOptions());
      cacheFile = helper.getDiskCache().get(currentKey);
      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;
  }
  ...
}

直接跟踪类ResourceCacheGenerator

25-34,根据图片URL生成一个磁盘缓存ResourceCacheKey

第35行,根据这个key获取磁盘缓存图片File

第38行,根据这个File获取初始化Glide时注册的ModelLoaders,猜也猜得到大概是哪几个了,必定是其中一个

        .append(File.class, ByteBuffer.class, new ByteBufferFileLoader.Factory())
        .append(File.class, InputStream.class, new FileLoader.StreamFactory())
        .append(File.class, File.class, new FileDecoder())
        .append(File.class, ParcelFileDescriptor.class, new FileLoader.FileDescriptorFactory())
        // Compilation with Gradle requires the type to be specified for UnitModelLoader here.
        .append(File.class, File.class, UnitModelLoader.Factory.<File>getInstance())

这里公布下答案,ByteBufferFileLoader,

接着第52行,根据这个ByteBufferFileLoader去加载磁盘缓存图片文件然后一层一层回调回去显示


其实到这里Glide磁盘缓存就差不多了,接下来继续分析缓存图片在哪存入和获取的呢

第35行,这里就是根据ResourceCacheKey获取磁盘缓存图片File,


在哪存储的呢?


class SourceGenerator implements DataFetcherGenerator,
    DataFetcher.DataCallback<Object>,
    DataFetcherGenerator.FetcherReadyCallback {
  ...
  @Override
  public boolean startNext() {
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      cacheData(data);
    }

    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
      return true;
    }
    sourceCacheGenerator = null;

    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      loadData = helper.getLoadData().get(loadDataListIndex++);
      if (loadData != null
          && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
          || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
        started = true;
        // 实际发起请求的地方
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
    return started;
  }
  ...
}

第10行,当从网络下载后会执行到这里,这里就是存储到磁盘的起点

跟踪进入,会发现获取磁盘文件和存储都会调用helper.getDiskCache(),接着就先弄清楚它

final class DecodeHelper<Transcode> {
   ...
   DiskCache getDiskCache() {
      return diskCacheProvider.getDiskCache();
   }
   ...
}
class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,
    Runnable,
    Comparable<DecodeJob<?>>,
    Poolable {
  ...
  interface DiskCacheProvider {
    DiskCache getDiskCache();
  }
  ...
}

这里首先要知道diskCacheProvider是哪个类的实例,从上面的代码可知是DiskcacheProvider接口的一个实现类,

通过全局搜索可以知道只有一个实现类LazyDiskCacheProvider,那么diskCacheProvider肯定就是

LazyDiskCacheProvider的实例了

public class Engine implements EngineJobListener,
    MemoryCache.ResourceRemovedListener,
    EngineResource.ResourceListener {
  ...
  private static class LazyDiskCacheProvider implements DecodeJob.DiskCacheProvider {
      @Override
    ...
    public DiskCache getDiskCache() {
      if (diskCache == null) {
        synchronized (this) {
          if (diskCache == null) {
            diskCache = factory.build();
          }
          if (diskCache == null) {
            diskCache = new DiskCacheAdapter();
          }
        }
      }
      return diskCache;
    }
  }
  ...
}

第12行,可以知道从外面传了一个factory创建了一个DiskCache的实例,DiskCache是一个接口,全局搜索可以知道DiskCache

有2个实现类DiskLruCacheWrapper、DiskCacheAdapter,从上面的代码分析肯定不是DiskCacheAdapter的实例,如果

是就不会有15行的创建DiskCacheAdapter实例,因此,可以推断第12行创建的是DiskLruCacheWrapper实例,这里可以透露下

其实磁盘缓存的读写都是这个实例里面实现的,不过先不看这个类,我们先要知道这个factory从哪传入的


public final class GlideBuilder {
  ...
  public Glide build(Context context) {
    // 开始初始化glide

    ...
    if (diskCacheFactory == null) {
      diskCacheFactory = new InternalCacheDiskCacheFactory(context);
    }

    if (engine == null) {
      engine =
          new Engine(
              memoryCache,
              diskCacheFactory,
              diskCacheExecutor,
              sourceExecutor,
              GlideExecutor.newUnlimitedSourceExecutor(),
              GlideExecutor.newAnimationExecutor(),
              isActiveResourceRetentionAllowed);
    }
   ...
  }
  ...
}

还记得初始Glide实例那里吗,第8行,可以看到默认情况下初始化磁盘存储,然后InternalCacheDiskCacheFactory实例一级一级

往下传InternalCacheDiskCacheFactory

public final class InternalCacheDiskCacheFactory extends DiskLruCacheFactory {

  public InternalCacheDiskCacheFactory(Context context) {
    this(context, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR,
        DiskCache.Factory.DEFAULT_DISK_CACHE_SIZE);
  }

  public InternalCacheDiskCacheFactory(Context context, long diskCacheSize) {
    this(context, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR, diskCacheSize);
  }

  public InternalCacheDiskCacheFactory(final Context context, final String diskCacheName,
                                       long diskCacheSize) {
    super(new CacheDirectoryGetter() {
      @Override
      public File getCacheDirectory() {
        File cacheDirectory = context.getCacheDir();
        if (cacheDirectory == null) {
          return null;
        }
        if (diskCacheName != null) {
          return new File(cacheDirectory, diskCacheName);
        }
        return cacheDirectory;
      }
    }, diskCacheSize);
  }
}

第4-5 行,这里的2个参数默认是磁盘内部存储缓存目录、磁盘缓存大小为250M,构造方法最后调用了super构造方法

16-25行,重写了getCacheDirectory(),这个方法的作用就是创建磁盘内部缓存目录,接着跟踪super

public class DiskLruCacheFactory implements DiskCache.Factory {
  ...
  public DiskLruCacheFactory(CacheDirectoryGetter cacheDirectoryGetter, long        
    diskCacheSize) {
    this.diskCacheSize = diskCacheSize;
    this.cacheDirectoryGetter = cacheDirectoryGetter;
  }

  @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);
  }
  ...
}

第10-21行,有个build()方法,用于创建DiskLruCacheWrapper实例,与之前的呼应了吧,跟踪第21行,

可以知道创建DiskLruCacheWrapper实例

public class DiskLruCacheWrapper implements DiskCache {
  ...
  public static DiskCache create(File directory, long maxSize) {
    return new DiskLruCacheWrapper(directory, maxSize);
  }
  ...
}

到这里应该已经清楚了当存储或者获取磁盘缓存图片资源时用到的DiskCache就是DiskLruCacheWrapper实例,

接下来分析DiskLruCacheWrapper类

public class DiskLruCacheWrapper implements DiskCache {
  ...
  protected DiskLruCacheWrapper(File directory, long maxSize) { 
    this.directory = directory;                                 
    this.maxSize = maxSize;                                     
    this.safeKeyGenerator = new SafeKeyGenerator();             
  }                                                             
  ...
}

这是DiskLruCacheWrapper的构造方法,把缓存目录和大小传入作为成员变量

第6行,构造SafeKeyGenerator实例,跟踪

public class SafeKeyGenerator {
  ...
  private final LruCache<Key, String> loadIdToSafeHash = new LruCache<>(1000);
  private final Pools.Pool<PoolableDigestContainer> digestPool =      
  FactoryPools.threadSafe(10,
      new FactoryPools.Factory<PoolableDigestContainer>() {
        @Override
        public PoolableDigestContainer create() {
          try {
            return new PoolableDigestContainer(MessageDigest.getInstance("SHA-256"));
          } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
          }
        }
      });                                                          
  ...
}

SafeKeyGenerator是一个使用LruCache算法保存磁盘缓存图片加密名称的一个工具,这里就不深入分析它了,

接下来看DiskLruCacheWrapper的put()和get(),先跟踪get()

public class DiskLruCacheWrapper implements DiskCache {   
  ...
    @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;
  }                                                                                                        
  ...
}

第5行,加密key,比如,httpxxx 加密后为 32fgdg44r2xx,

第14行,获取磁盘缓存图片文件,接着返回,这里又是难点的开始了,跟踪进去

public class DiskLruCacheWrapper implements DiskCache {   
  ...
  private synchronized DiskLruCache getDiskCache() throws IOException {                 
    if (diskLruCache == null) {                                                         
      diskLruCache = DiskLruCache.open(directory, APP_VERSION, VALUE_COUNT, maxSize);   
    }                                                                                   
    return diskLruCache;                                                                
  }                                                          
  ...
}

public final class DiskLruCache implements Closeable { 
  ...
  public static DiskLruCache open(File directory, int appVersion, int valueCount, long   
    maxSize)
      throws IOException {
    if (maxSize <= 0) {
      throw new IllegalArgumentException("maxSize <= 0");
    }
    if (valueCount <= 0) {
      throw new IllegalArgumentException("valueCount <= 0");
    }

    // If a bkp file exists, use it instead.
    File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
    if (backupFile.exists()) {
      File journalFile = new File(directory, JOURNAL_FILE);
      // If journal file also exists just delete backup file.
      if (journalFile.exists()) {
        backupFile.delete();
      } else {
        renameTo(backupFile, journalFile, false);
      }
    }

    // Prefer to pick up where we left off.
    DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
    if (cache.journalFile.exists()) {
      try {
        cache.readJournal();
        cache.processJournal();
        return cache;
      } catch (IOException journalIsCorrupt) {
        System.out
            .println("DiskLruCache "
                + directory
                + " is corrupt: "
                + journalIsCorrupt.getMessage()
                + ", removing");
        cache.delete();
      }
    }

    // Create a new empty cache.
    directory.mkdirs();
    cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
    cache.rebuildJournal();
    return cache;
  }                                                                   
  ...
}

第5行,DiskLruCache.open()构建DiskLruCache实例

24-34行,创建日志文件和备份,

第37行,创建DiskLruCache实例

第40行,如果日志文件存在,开始读取日志文件内容,跟踪

public final class DiskLruCache implements Closeable { 
  ...
   private void readJournal() throws IOException {
    StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
    try {
      String magic = reader.readLine();
      String version = reader.readLine();
      String appVersionString = reader.readLine();
      String valueCountString = reader.readLine();
      String blank = reader.readLine();
      if (!MAGIC.equals(magic)
          || !VERSION_1.equals(version)
          || !Integer.toString(appVersion).equals(appVersionString)
          || !Integer.toString(valueCount).equals(valueCountString)
          || !"".equals(blank)) {
        throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
            + valueCountString + ", " + blank + "]");
      }

      int lineCount = 0;
      while (true) {
        try {
          readJournalLine(reader.readLine());
          lineCount++;
        } catch (EOFException endOfJournal) {
          break;
        }
      }
      redundantOpCount = lineCount - lruEntries.size();

      // If we ended on a truncated line, rebuild the journal before appending to it.
      if (reader.hasUnterminatedLine()) {
        rebuildJournal();
      } else {
        journalWriter = new BufferedWriter(new OutputStreamWriter(
            new FileOutputStream(journalFile, true), Util.US_ASCII));
      }
    } finally {
      Util.closeQuietly(reader);
    }
  }

  private void readJournalLine(String line) throws IOException {
    int firstSpace = line.indexOf(' ');
    if (firstSpace == -1) {
      throw new IOException("unexpected journal line: " + line);
    }

    int keyBegin = firstSpace + 1;
    int secondSpace = line.indexOf(' ', keyBegin);
    final String key;
    if (secondSpace == -1) {
      key = line.substring(keyBegin);
      if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
        lruEntries.remove(key);
        return;
      }
    } else {
      key = line.substring(keyBegin, secondSpace);
    }

    Entry entry = lruEntries.get(key);
    if (entry == null) {
      entry = new Entry(key);
      lruEntries.put(key, entry);
    }

    if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
      String[] parts = line.substring(secondSpace + 1).split(" ");
      entry.readable = true;
      entry.currentEditor = null;
      entry.setLengths(parts);
    } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
      entry.currentEditor = new Editor(entry);
    } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
      // This work was already done by calling lruEntries.get().
    } else {
      throw new IOException("unexpected journal line: " + line);
    }
  }                                     
  ...
}

6-10行,从日志文件中读取介绍信息,版本号啊神马的

20-28行,循环读取剩下的日志信息,当读取到末尾退出循环,读取到的信息放入lruEntries中

44-60行,读取日志信息,从读取到的一行信息截取空格间的字符串,也就是加密key

62-65行,lruEntries开始存Entry对象,Entry对象又包含上面读取出来的加密key,lruEntries很多地方都会用到

第64行,跟踪

public final class DiskLruCache implements Closeable { 
  ...
  private final class Entry {
    private Entry(String key) {
      this.key = key;
      this.lengths = new long[valueCount];
      cleanFiles = new File[valueCount];
      dirtyFiles = new File[valueCount];

      // The names are repetitive so re-use the same builder to avoid allocations.
      StringBuilder fileBuilder = new StringBuilder(key).append('.');
      int truncateTo = fileBuilder.length();
      for (int i = 0; i < valueCount; i++) {
          fileBuilder.append(i);
          cleanFiles[i] = new File(directory, fileBuilder.toString());
          fileBuilder.append(".tmp");
          dirtyFiles[i] = new File(directory, fileBuilder.toString());
          fileBuilder.setLength(truncateTo);
      }
    }   
  }                   
  ...
}

可以知道构造Entry实体的时候,根据读取日志中的加密key,其实也就是磁盘缓存图片文件名,生成file对象作为Entry实体

成员变量

接着看DiskLruCache的get()方法

public final class DiskLruCache implements Closeable { 
  ...
 public synchronized Value get(String key) throws IOException {
    checkNotClosed();
    Entry entry = lruEntries.get(key);
    if (entry == null) {
      return null;
    }

    if (!entry.readable) {
      return null;
    }

    for (File file : entry.cleanFiles) {
        // A file must have been deleted manually!
        if (!file.exists()) {
            return null;
        }
    }

    redundantOpCount++;
    journalWriter.append(READ);
    journalWriter.append(' ');
    journalWriter.append(key);
    journalWriter.append('\n');
    if (journalRebuildRequired()) {
      executorService.submit(cleanupCallable);
    }

    return new Value(key, entry.sequenceNumber, entry.cleanFiles, entry.lengths);
  }                            
  ...
}

第5行,从lruEntries中根据加密key获取Entry对象,如果有,往下执行

14-19行,从Entry对象获取缓存文件,如果存在往下执行

第30行,如果缓存文件存在构建一个Value实体对象,包含缓存文件返回

到这里获取磁盘缓存图片就差不多了,接下来看下DiskLruCacheWrapper.put(),存储磁盘缓存图片文件

public class DiskLruCacheWrapper implements DiskCache {
  ...
  @Override
  public void put(Key key, Writer writer) {
    // We want to make sure that puts block so that data is available when put completes. We may
    // actually not write any data if we find that data is written by the time we acquire the lock.
    // 返回加密后的key,采用Lrucache算法
    String safeKey = safeKeyGenerator.getSafeKey(key);
    writeLocker.acquire(safeKey);
    try {
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Put: Obtained: " + safeKey + " for for Key: " + key);
      }
      try {
        // We assume we only need to put once, so if data was written while we were trying to get
        // the lock, we can simply abort.
        DiskLruCache diskCache = getDiskCache();
        Value current = diskCache.get(safeKey);
        if (current != null) {
          return;
        }

        DiskLruCache.Editor editor = diskCache.edit(safeKey);
        if (editor == null) {
          throw new IllegalStateException("Had two simultaneous puts for: " + safeKey);
        }
        try {
          File file = editor.getFile(0);
          if (writer.write(file)) {
            editor.commit();
          }
        } finally {
          editor.abortUnlessCommitted();
        }
      } catch (IOException e) {
        if (Log.isLoggable(TAG, Log.WARN)) {
          Log.w(TAG, "Unable to put to disk cache", e);
        }
      }
    } finally {
      writeLocker.release(safeKey);
    }
  }                                                                                                                               
  ...
}

17-21行,跟get()差不多,先获取DiskLruCache实例,然后get(key)获取缓存文件,如果存在,就返回null,表示不需要存储,

              如果没有就继续执行

第23行,很重要,先跟踪,一会再回到这里

public final class DiskLruCache implements Closeable {
  ...
  /**
   * Returns an editor for the entry named {@code key}, or null if another
   * edit is in progress.
   */
  public Editor edit(String key) throws IOException {
    return edit(key, ANY_SEQUENCE_NUMBER);
  }

  private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
    checkNotClosed();
    Entry entry = lruEntries.get(key);
    if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
        || entry.sequenceNumber != expectedSequenceNumber)) {
      return null; // Value is stale.
    }
    if (entry == null) {
      entry = new Entry(key);
      lruEntries.put(key, entry);
    } else if (entry.currentEditor != null) {
      return null; // Another edit is in progress.
    }

    Editor editor = new Editor(entry);
    entry.currentEditor = editor;

    // Flush the journal before creating files to prevent file leaks.
    journalWriter.append(DIRTY);
    journalWriter.append(' ');
    journalWriter.append(key);
    journalWriter.append('\n');
    journalWriter.flush();
    return editor;
  }                                                                                                                  
  ...
}

13-23行,lruEntrie熟悉了吧,又来了哦,判断存储有加密key没有,没有就构建Entry实体实例把加密key存入,跟前面

get()逻辑一样的

25-34行,根据加密key构建一串特定字符串,然后写入磁盘日志文件中,这个日志用于记录缓存图片文件名称的,下次获取

缓存文件就从这个日志读取名称,前面get()部分分析过了

接着回到DiskLruCacheWrapper.put()

public class DiskLruCacheWrapper implements DiskCache {
  ...
  @Override
  public void put(Key key, Writer writer) {
    // We want to make sure that puts block so that data is available when put completes. We may
    // actually not write any data if we find that data is written by the time we acquire the lock.
    // 返回加密后的key,采用Lrucache算法
    String safeKey = safeKeyGenerator.getSafeKey(key);
    writeLocker.acquire(safeKey);
    try {
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Put: Obtained: " + safeKey + " for for Key: " + key);
      }
      try {
        // We assume we only need to put once, so if data was written while we were trying to get
        // the lock, we can simply abort.
        DiskLruCache diskCache = getDiskCache();
        Value current = diskCache.get(safeKey);
        if (current != null) {
          return;
        }

        DiskLruCache.Editor editor = diskCache.edit(safeKey);
        if (editor == null) {
          throw new IllegalStateException("Had two simultaneous puts for: " + safeKey);
        }
        try {
          File file = editor.getFile(0);
          if (writer.write(file)) {
            editor.commit();
          }
        } finally {
          editor.abortUnlessCommitted();
        }
      } catch (IOException e) {
        if (Log.isLoggable(TAG, Log.WARN)) {
          Log.w(TAG, "Unable to put to disk cache", e);
        }
      }
    } finally {
      writeLocker.release(safeKey);
    }
  }                                                                                                                           
  ...
}

27-32行,根据之前返回的DiskLruCache.Editor实例,获取需要磁盘缓存的图片文件名称,

然后写入磁盘好了,到这里磁盘的存和取都分析了一遍,相信也比较清楚了,

最后几句话总结下原理


根据传入的URL和相关参数组成一个key,然后从内存缓存的弱引用中查找是否相应的图片资源,如果有

就返回,没有就从内存缓存的Lrucache缓存中查找,如果有就返回并存入弱引用缓存,如果没有就去查看

磁盘缓存,磁盘缓存有几种策略,常用的策略还是缓存转换后的图片,先加密key,然后去磁盘日志文件中

查找有没有记录,如果有就根据记录的文件名称获取图片文件并返回,如果没有记录,就从网络获取图片

资源流,根据加密key创建一份记录写入日志文件中,并把图片资源流写入磁盘缓存中


欢迎大家指教,其中还是有很多不是很明白,好了,到这里又可以愉快的玩耍了大笑


Android 图片加载框架Glide主流程源码分析


猜你喜欢

转载自blog.csdn.net/msn465780/article/details/79441177