天天看點

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緩存原理源碼分析

Android 圖檔加載架構Glide主流程源碼分析