Android框架源碼分析——Glide緩存機制源碼分析

1、Glide的緩存

  • Glide 在加載圖片時會依次訪問以下緩存:
  1. 活動資源 (Active Resources) - 現在是否有另一個 View 正在展示這張圖片?
  2. 內存緩存 (Memory cache) - 該圖片是否最近被加載過並仍存在於內存中?
  3. 資源類型(Resource) - 該圖片是否之前曾被解碼、轉換並寫入過磁盤緩存?
  4. 數據來源 (Data) - 構建這個圖片的資源是否之前曾被寫入過文件緩存?

上面的四個步驟就代表了Glide的緩存機制,當四步緩存中未查找對應的資源時再執行網絡請求加載資源,加載完成後也會按照這四步緩存資源;

2、緩存策略

2.1、磁盤緩存
  • DiskCacheStrategy: 設置Glide的緩存策略,通過設置緩存策略可選擇性地控制緩存原數據還是緩存轉換後的數據,或是二者都緩存
  • 默認的策略叫做 AUTOMATIC,它會嘗試對本地和遠程圖片使用最佳的策略
  1. AUTOMATIC : 當加載遠程數據(比如,從URL下載)時,AUTOMATIC 策略僅會存儲未被你的加載過程修改過(比如,變換,裁剪)的原始數據;
  2. AUTOMATIC : 對於加載本地數據,AUTOMATIC 策略則會僅存儲變換過的縮略圖,因爲圖片在本地比較容易直接獲取;
  • 修改指定的DiskCacheStrategy的緩存策略
GlideApp.with(fragment)
  .load(url)
  .diskCacheStrategy(DiskCacheStrategy.ALL)
  .into(imageView);

Glide提供以下

  1. ALL : 緩存數據和資源的遠程數據,以及僅用資源的本地數據。
  2. AUTOMATIC:默認的緩存策略
  3. DATA:在解碼之前將檢索到的數據直接寫入磁盤緩存。
  4. NONE:不緩存
  5. RESOURCE:在解碼後將資源寫入磁盤。
2.2、 僅從緩存加載
GlideApp.with(fragment)
  .load(url)
  .onlyRetrieveFromCache(true)
  .into(imageView);

當開啓僅從緩存加載去加載數據,除非加載的圖片已經在內存緩存或磁盤緩存中,否則加載失敗;

2.3、跳過緩存
  • skipMemoryCache():僅跳過內存緩存
GlideApp.with(fragment)
  .load(url)
  .skipMemoryCache(true)
  .into(view);
  • DiskCacheStrategy.NONE: 跳過磁盤緩存
.diskCacheStrategy(DiskCacheStrategy.NONE)
  • 兩者都跳過
.diskCacheStrategy(DiskCacheStrategy.NONE)
  .skipMemoryCache(true)

3、配置緩存

Glide 允許應用通過 AppGlideModule 實現來配置 Glide 的內存和磁盤緩存使用,可以配置緩存文件的位置和緩存控件的大小

  • 配置緩存位置
    (3)磁盤緩存
    Glide 使用 DiskLruCacheWrapper 作爲默認的 磁盤緩存 , DiskLruCacheWrapper 是一個使用 LRU 算法的固定大小的磁盤緩存,默認磁盤大小爲 250 MB ,位置在手機內存中應用的緩存文件夾 中的一個 特定目錄
  • 設置緩存存儲位置
builder.setDiskCache(new ExternalDiskCacheFactory(context)); 
builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context)); 

Glide內置3中設置存儲位置的Factory

  1. InternalCacheDiskCacheFactory:緩存在手機內存的緩存文件中
  2. ExternalDiskCacheFactory:設置緩存文件在外部存儲中
  3. ExternalPreferredCacheDiskCacheFactory:如果之前存儲在內部存儲中,則會維持不變繼續使用,否則使用外部緩存文件;
  • 應用程序都可以改變磁盤緩存的大小
 int diskCacheSizeBytes = 1024  1024  100;  100 MB
 builder.setDiskCache(new InternalDiskCacheFactory(context, diskCacheSizeBytes));
  • 應用程序還可以改變緩存文件夾在外存或內存上的名字:
 int diskCacheSizeBytes = 1024  1024  100;  100 MB
 builder.setDiskCache( new InternalDiskCacheFactory(context, cacheFolderName, diskCacheSizeBytes));
  • 自定義 MemoryCache的大小,具體是在它們的 AppGlideModule 中使用applyOptions(Context, GlideBuilder) 方法配置 MemorySizeCalculator
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
    super.applyOptions(context, builder);
    MemorySizeCalculator calculator = new MemorySizeCalculator.Builder(context) //Glide內部默認配置
            .setMemoryCacheScreens(2)
            .build();
    builder.setMemoryCache(new LruResourceCache(calculator.getBitmapPoolSize()));
}
  • 直接限定緩存的大小
int memoryCacheSizeBytes = 1024 * 1024 * 20;
builder.setMemoryCache(new LruResourceCache(memoryCacheSizeBytes));
3.1、Bitmap池
  • Glide 使用 LruBitmapPool 作爲默認的 BitmapPool
  • 在 AppGlideModule 中定製 BitmapPool的尺寸,使用 applyOptions(Context, GlideBuilder) 方法並配置 MemorySizeCalculator:
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
    super.applyOptions(context, builder);
    MemorySizeCalculator calculator = new MemorySizeCalculator.Builder(context)
            .setMemoryCacheScreens(2)
            .build();
    builder.setBitmapPool(new LruBitmapPool(calculator.getBitmapPoolSize()));
}
  • 直接限定大小
int bitmapPoolSizeBytes = 1024 * 1024 * 30; // 30mb
builder.setBitmapPool(new LruBitmapPool(bitmapPoolSizeBytes));

4、緩存的刷新

4.1、定製緩存刷新策略

Glide的內部緩存會根據多種條件創建最終緩存的Key,顯然我們如果要刷新請求而不實用緩存就必須改變獲取的鍵,使獲取的和儲存的鍵不一致,這樣緩存失效就會加載新的數據,通常改變標識符比較困難或者根本不可能,所以Glide也提供了 簽名 API 來混合額外數據到你的緩存鍵中,針對以下3種類型資源提供以下方案:

  • MediaStore 內容 - 對於媒體存儲內容,可以使用Glide的 MediaStoreSignature 類作爲簽名,MediaStoreSignature允許你混入修改時間、MIME類型,以及item的方向到緩存鍵中,這三個屬性能夠可靠地捕獲對圖片的編輯和更新,這可以允許緩存媒體存儲的縮略圖。
  • 文件 - 使用 ObjectKey 來混入文件的修改日期,改變日期時即可刷新緩存
  • Url - 針對Url有兩種有限方法:1、讓 server 保證在內容變更時對URL做出改變;2使用 ObjectKey 來混入任意數據來改變Url

例子:第一次加載,將緩存的鍵混入數字“123”:

GlideApp.with(this)
       .load("http://img.zcool.cn/community/[email protected]")
        .placeholder(R.mipmap.ic_launcher)
        .error(R.drawable.ic_launcher_background)
        .signature(new ObjectKey("123"))
        .transition(new DrawableTransitionOptions().crossFade())
        .into(imageView);
  1. 將手機斷開網絡重新進入程序,因爲有緩存的存在所以可以直接加載圖片,現在修改程序中鍵摻入的123爲456:
.signature(new ObjectKey("456"))
  1. 此時網絡爲斷開,且再次請求的鍵與之前緩存的並不相同,此時加載失敗,連接網絡後會重新拉取圖片並緩存,圖片加載成功
  2. 此時內存中是有兩個緩存圖片,分別對應兩次請求所生成的鍵
  • 也定義你自己的簽名,只要實現 Key 接口就好,並重寫其中的方法,還記得之前看過處理七牛雲圖片Url的文章就是自定義Key保證緩存有效
public class IntegerVersionSignature implements Key {
    private int currentVersion;
    public IntegerVersionSignature(int currentVersion) {
         this.currentVersion = currentVersion;
    }
   
    @Override
    public boolean equals(Object o) {
        if (o instanceof IntegerVersionSignature) {
            IntegerVersionSignature other = (IntegerVersionSignature) o;
            return currentVersion = other.currentVersion;
        }
        return false;
    }
    @Override
    public int hashCode() {
        return currentVersion;
    }
    @Override
    public void updateDiskCacheKey(MessageDigest md) {
        messageDigest.update(ByteBuffer.allocate(Integer.SIZE).putInt(signature).array());
    }}

5、清除緩存

  • 清理緩存:使用 clearDiskCache清理所有磁盤緩存條目
new AsyncTask<Void, Void, Void> {
  @Override
  protected Void doInBackground(Void... params) {
    Glide.get(applicationContext).clearDiskCache();
    return null;
  }}

6、緩存機制源碼分析

6.1、Glide中緩存的配置
Glide build(@NonNull Context context) {
  if (sourceExecutor == null) {
    sourceExecutor = GlideExecutor.newSourceExecutor(); //實例化執行網絡資源加載線程池
  }
  if (diskCacheExecutor == null) {
    diskCacheExecutor = GlideExecutor.newDiskCacheExecutor(); //實例化執行磁盤緩存的線程池
  }
  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); //初始化Bitmap池
    } 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
    engine = new Engine(
            memoryCache,
            diskCacheFactory,
            diskCacheExecutor,
            sourceExecutor,
            GlideExecutor.newUnlimitedSourceExecutor(),
            animationExecutor,
            isActiveResourceRetentionAllowed);
  }
  if (defaultRequestListeners == null) {
    defaultRequestListeners = Collections.emptyList(); //請求監聽器
  } else {
    defaultRequestListeners = Collections.unmodifiableList(defaultRequestListeners);
  }
  RequestManagerRetriever requestManagerRetriever =
      new RequestManagerRetriever(requestManagerFactory);
  return new Glide(
      context,
      engine,
      memoryCache,
      bitmapPool,
      arrayPool,
      );
}

Glide很多的功能都是在這初始化的,依次介紹上面實例化的對象:

  1. sourceExecutor:執行網絡資源加載的線程池——只有最多4個核心線程的線程池
  2. diskCacheExecutor:執行磁盤緩存線程池——只有一個核心線程的線程池
  3. bitmapPool:初始化BitmapPool緩存池
  4. diskCacheFactory:實例化磁盤工廠——實例化Glide存儲位置
  • memorySizeCalculator:自動計算緩存空間大小
MemorySizeCalculator(MemorySizeCalculator.Builder builder) {
  arrayPoolSize = isLowMemoryDevice(builder.activityManager)  //(1)判斷設備內存ArrayPool正常設備4M,低內存設備2M
          ? builder.arrayPoolSizeBytes / LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR
          : builder.arrayPoolSizeBytes;
  int maxSize = // 根據設備內存確定緩存大小,低內存設備*0.33f ,高內存設備 * 0.4f
      getMaxSize(  builder.activityManager, builder.maxSizeMultiplier, builder.lowMemoryMaxSizeMultiplier);
  int widthPixels = builder.screenDimensions.getWidthPixels(); //獲取設備的寬高像素
  int heightPixels = builder.screenDimensions.getHeightPixels();
  int screenSize = widthPixels * heightPixels * BYTES_PER_ARGB_8888_PIXEL;// 計算屏幕像素大小
  int targetBitmapPoolSize = Math.round(screenSize * builder.bitmapPoolScreens);
  int targetMemoryCacheSize = Math.round(screenSize * builder.memoryCacheScreens);
  int availableSize = maxSize - arrayPoolSize;
  if (targetMemoryCacheSize + targetBitmapPoolSize <= availableSize) {
    memoryCacheSize = targetMemoryCacheSize; // 設置內存緩存和Bitmap緩存池的大小
    bitmapPoolSize = targetBitmapPoolSize;
  } else {
    float part = availableSize / (builder.bitmapPoolScreens + builder.memoryCacheScreens);
    memoryCacheSize = Math.round(part * builder.memoryCacheScreens);
    bitmapPoolSize = Math.round(part * builder.bitmapPoolScreens);
  }
}

MemorySizeCalculator根據設備狀態和頁面的像素大小計算緩存大小,從上面計算可以知道計算結果如下:

  1. bitmapPool的緩存大小爲一張全屏圖片大小的4倍(8.0之後爲1倍)
  2. LrArray的緩存大小爲4M 或2M(低內存設備)
  3. Memory緩存大小爲一張全屏圖片大小的2倍(1080 * 1920 8M左右)
  4. 磁盤默認緩存爲250M
6.2、緩存源碼分析

由上一篇文章(Android進階知識樹——Glide源碼分析)的分析知道,真正執行加載數據的是在Engine.load()方法中,現在重新看一下這個方法:

public synchronized <R> LoadStatus load() {
  //創建緩存的Key
  EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, 
      resourceClass, transcodeClass, options);
  EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable); //(1)從活躍的引用中加載緩存信息
  if (active != null) {
    cb.onResourceReady(active, DataSource.MEMORY_CACHE);
  }
  EngineResource<?> cached = loadFromCache(key, isMemoryCacheable); //(2)從緩存中獲取緩存實例
  if (cached != null) { 
    cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
    return null;
  }
}

首先介紹下ActiveResource的作用,如果喲個圖片當前正在被引用會保存在ActiveResource中,總結一下上面查找緩存的過程:

  1. 首先根據請求的地址和條件生成緩存Key,影響Key的因素很多
  2. 首先從ActiveResource中獲取資源,獲取到則返回資源,否則繼續向下執行
  3. 從內存緩存中獲取,獲取到則返回資源
  • 緩存Key的生成
 EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, 
      resourceClass, transcodeClass, options); //傳入的參數都影響Key的生成,默認EngineKeyFactory

class EngineKeyFactory {
  EngineKey buildKey(Object model, Key signature, int width, int height,
      Map<Class<?>, Transformation<?>> transformations, Class<?> resourceClass,Class<?> transcodeClass, Options options) {
    return new EngineKey(model, signature, width, height, transformations, resourceClass,
        transcodeClass, options); 
  }
}
6.3、從內存緩存中加載資源
  • Glide的引用計數
    Glide中採用計數的方式統計資源的引用,在每個EngineResource內部都設置一個引用計數acquired,在加載資源時引用++,釋放資源時引用—:
class EngineResource<Z> implements Resource<Z> {
private int acquired;  //統計引用數
synchronized void acquire() {
  ++acquired;  //引用數 ++
}
void release() {
    synchronized (this) {
      if (--acquired == 0) {  //每次釋放時,引用數 --
        listener.onResourceReleased(key, this); //上面Cache添加
      }
    }
}
}
  1. 引用數++:從活躍引用資源中、內存緩存中、資源加載中使用獲得的資源時,資源的引用數 +1
  2. 引用數—:資源釋放時引用數–
  3. 當引用數爲0時,則將資源轉移到Cache緩存中
  • loadFromActiveResources(key, isMemoryCacheable)
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
  if (!isMemoryCacheable) { //(1)如果跳過內存緩存直接返回null
    return null;
  }
  EngineResource<?> active = activeResources.get(key); //(2)從activeResources中獲取緩存資源
  if (active != null) {
    active.acquire(); //(3)資源的引用 +1 (Glide採用技術引用)
  }
  return active;
}

loadFromActiveResources()從活躍的引用中加載緩存資源,Glide的內部採用計數的方式統計資源的引用,當資源被引用時計數+1,資源釋放是計數-1;

  • loadFromCache(key, isMemoryCacheable)
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
  if (!isMemoryCacheable) {//(1)是否跳過緩存
    return null;
  }
  EngineResource<?> cached = getEngineResourceFromCache(key);
  if (cached != null) {
    cached.acquire(); //(3)對資源的引用 +1
    activeResources.activate(key, cached); //(4)將引用的資源添加到activeResources中
  }
  return cached;
}
private EngineResource<?> getEngineResourceFromCache(Key key) {
  Resource<?> cached = cache.remove(key); // (2)從內存緩存中獲取數據,並從cache中移除
  return result;
}

loadFromCache從內存緩存資源中加載數據,具體執行流程:

  1. 首先判斷Glide加載是否跳過緩存,如果跳過則直接返回null
  2. 從cache緩存中獲取資源,對資源的應用數+1,並將資源放如activeResources緩存中
  • LruResourceCache
private final Map<T, Y> cache = new LinkedHashMap<>(100, 0.75f, true);  //內存緩存Map
@Nullable
public synchronized Y remove(@NonNull T key) {
  final Y value = cache.remove(key); //從Map中獲取數據
  if (value != null) {
    currentSize -= getSize(value); //緩存數減1
  }
  return value;
}

上面流程中的cache實際是LruResourceCache的實例,在初始化Glide過程中傳入,LruResourceCache繼承了LruCache也實現了MemoryCache(適配器模式),在LruCache中使用可LinknHashMap緩存數據,在獲取時從cache中根據key查找資源

6.4、 內存緩存寫入
  • ActiveResources的寫入

由上面知道ActiveResources中保存當前正在引用的資源,ActiveResources中的資源的寫入主要從Cache中獲取資源後添加的

EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
  cached.acquire();
  activeResources.activate(key, cached);
}
public synchronized void onEngineJobComplete( EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
  if (resource != null && resource.isMemoryCacheable()) {
    activeResources.activate(key, resource);
  }
  jobs.removeIfCurrent(key, engineJob);
}
  • Cache的緩存寫入
    在內存中緩存使用過但此時未使用的資源緩存到Cache中,這句話簡單的理解就是當活躍資源沒有引用時即資源引用數爲0時,添加到Cache緩存中
@Override
public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
  activeResources.deactivate(cacheKey); //從活躍資源中移除
  if (resource.isMemoryCacheable()) {
    cache.put(cacheKey, resource); //添加到Cache緩存中
  } else {
    resourceRecycler.recycle(resource);
  }
}
6.5、磁盤緩存讀取

磁盤緩存和獲取的執行流程

  • EngineJob、DecodeJob:開啓任務去加載(磁盤或網絡)

從上面分析知道,創建EngineJob、DecodeJob實例後,調用EngineJob.start執行資源加載,在上一篇文章中我們直接分析了網絡請求,本篇看一下如何從緩存文件中獲取資源的:

public synchronized void start(DecodeJob<R> decodeJob) {
  GlideExecutor executor = decodeJob.willDecodeFromCache() 
      ? diskCacheExecutor
      : getActiveSourceExecutor();
  executor.execute(decodeJob);
}
boolean willDecodeFromCache() {
  Stage firstStage = getNextStage(Stage.INITIALIZE);  //獲取下一步執行的狀態,
  return firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE; //返回次兩種狀態表示存在原文件緩存或存在轉換後的文件緩存
}
private Stage getNextStage(Stage current) { 
 }

關於getNextStage()具體流程見上一篇文章,這裏首先根據有無緩存情況,獲取對應執行的線程池,上面設置Stage.RESOURCE_CACHE後,如果有轉換後的緩存文件存在,則會執行的就是ResourceCacheGenerator.start()

public boolean startNext() {
  List<Key> sourceIds = helper.getCacheKeys(); //
  while (modelLoaders == null || !hasNextModelLoader()) {
    Key sourceId = sourceIds.get(sourceIdIndex);   //獲取緩存的KeyId
    Class<?> resourceClass = resourceClasses.get(resourceClassIndex);
    Transformation<?> transformation = helper.getTransformation(resourceClass);
    currentKey =  new ResourceCacheKey( //將數據封裝到ResourceCacheKey中
            helper.getArrayPool(),
            sourceId,
            helper.getSignature(),
            helper.getWidth(),
            helper.getHeight(),
            transformation,
            resourceClass,
            helper.getOptions());
    
    cacheFile = helper.getDiskCache().get(currentKey); 
    if (cacheFile != null) {
      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;
}
DiskCache getDiskCache() {
  return diskCacheProvider.getDiskCache();
}
//LazyDiskCacheProvider:
@Override
public DiskCache getDiskCache() {
  if (diskCache == null) {
    synchronized (this) {
      if (diskCache == null) {
//調用InternalCacheDiskCacheFactory 配置緩存目錄,並創建DiskLruCacheWrapper.create(cacheDir, diskCacheSize);
        diskCache = factory.build(); 
      }
    }
  }
  return diskCache;
}
@Override
public File get(Key key) { //獲取緩存資源File
  File result = null;
  try {
    final DiskLruCache.Value value = getDiskCache().get(safeKey); //從DiskLruCache磁盤緩存中獲取緩存文件
    if (value != null) {
      result = value.getFile(0);
    }
  }
  return result;
}

上面的步驟就是整個獲取緩存資源的過程,具體流程如下:

  1. 調用DecodeHelper方法獲取緩存文件的Key列表獲取緩存資源的Key,創建ResourceCacheKey實例封裝Key和請求信息
  2. 調用getDiskCache()通過diskCacheProvider最終調用設置的DiskCacheFactory配置緩存目錄,並創建DiskLruCacheWrapper.create(cacheDir, diskCacheSize)
  3. 根據ResourceCacheKey加載緩存數據,調用DiskLruCacheWrapper.get()從磁盤緩存中獲取緩存文件
  4. 調用 loadData.fetcher.loadData(helper.getPriority(), this);從緩存的文件中讀取資源
  • loadData.fetcher.loadData(helper.getPriority(), this):從緩存文件中讀取資源,要分析這裏的loadData就要從開始說起,創建Glide時根據獲取資源的方式不同註冊了許多ModelLoadfactory分別處理不同的數據類型
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())

此處從File中獲取所以獲取的是FileLoader.StreamFactory(),最終創建FileLoader實例及modelLoader,調用buildLoadData創建loadData,傳入FileFetcher實例,所以最終加載數據的是FileFetcher.loadData(),loadData()中直接使用FileOpener讀取文件資源並回調onDataReady()

@Override
public LoadData<Data> buildLoadData(@NonNull File model, int width, int height,
    @NonNull Options options) {
  return new LoadData<>(new ObjectKey(model), new FileFetcher<>(model, fileOpener));
}
//FileFetcher
@Override
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super Data> callback) {
  try {
    data = opener.open(file); //執行FileOpener中方法直接從文件File中獲取數據:return new FileInputStream(file);
  } 
  callback.onDataReady(data); //回調ResourceCacheGenerator.此時從文件中獲取資源
}

上面的流程是獲取轉換後的資源過程,如果獲取成功則進行執行成功後的回調並顯示圖片,若獲取失敗或資源不存在則繼續執行,這裏看一下上面的判斷邏輯

while (!isCancelled && currentGenerator != null
    && !(isStarted = currentGenerator.startNext())) { // 如果加載轉換後的資源失敗,此處會返回false
  stage = getNextStage(stage); // 此時的State爲RESOURCE_CACHE,再次獲取會返回DATA_CACHE
  currentGenerator = getNextGenerator(); //執行DataCacheGenerator()獲取源緩存文件;
  if (stage == Stage.SOURCE) {
    reschedule();
    return;
  }
}
Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
cacheFile = helper.getDiskCache().get(originalKey);

從上面的While循環知道,當處理後的緩存資源獲取失敗時則執行下一個Stage,此時State爲RESOURCE_CACHE執行next後獲取就是DATA_CACHE,則執行的是DataCacheGenerator加載原始緩存數據,整個獲取流程和上面一致,只是此時傳遞的是DataCacheKey即originalKey即根據originalKey獲取緩存的原始資源

6.6、磁盤緩存的寫入
  • SourceGenerator——執行網絡請求加載完成後回調onDataReady()
@Override
public void onDataReady(Object data) {
  DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy(); 
  //(1)判斷是否允許磁盤緩存原始資源
  if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
    dataToCache = data;
    cb.reschedule();  //(2)執行DecodeJob.reschedule()
  } 

@Override
public void reschedule() {
  runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;  //設置runReason標識
  callback.reschedule(this);  //方法回調EngineJob,然後線程池執行最終會切換磁盤緩存線程,執行Decode.run()
}
由於runReason = RunReason.SWITCH_TO_SOURCE_SERVICE,方法直接執行到runGenerators()中,
private void runWrapped() {
  switch (runReason) {
    case INITIALIZE:
    case SWITCH_TO_SOURCE_SERVICE:
      runGenerators();
      break;
  }
}
  1. 詳細過程見註釋,之前分析過此方法,首先判斷Glide是否允許磁盤緩存,如果允許則執行cb.reschedule(),最終還是會再次執行SourceGenerator.startNext(),第二次回調SourceGenerator.startNext()時執行cacheData()緩存文件,注意此時的dataToCache被第一次執行時賦值了,cacheData()中調用 helper.getDiskCache().put(originalKey, writer)寫入文件
@Override
public boolean startNext() { //第二次調用startNext
  if (dataToCache != null) {
    cacheData(data); // 緩存源文件
  }
return started;
}

private void cacheData(Object dataToCache) {
  try {
    Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
    DataCacheWriter<Object> writer = new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
    originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
    helper.getDiskCache().put(originalKey, writer);
  }
}
  1. 繼續執行startNext(),在執行cacheData()緩存Data之後會調用sourceCacheGenerator.startNext()程序進入DataCacheGenerator.startNext()
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
  return true;
}
  • DataCacheGenerator.startNext():
public boolean startNext() {
  while (modelLoaders == null || !hasNextModelLoader()) {
    sourceIdIndex++;
    Key sourceId = cacheKeys.get(sourceIdIndex);
    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); //(2)從文件File中加載資源,見上面的磁盤緩存的讀取
    }
  }
  return started;
}

startNext中執行一下邏輯:

  1. 首先根據Key從緩存文件中獲取緩存的原始文件資源
  2. 調用loadData.fetcher.loadData()從緩存文件中讀取資源
  3. 資源讀取後回調DecodeJob.onDataFetcherReady(),onDataFetcherReady()中調用decodeFromRetrievedData()完成緩存資源的解析和轉換
private void decodeFromRetrievedData() {
    resource = decodeFromData(currentFetcher, currentData, currentDataSource);//轉換文件
    notifyEncodeAndRelease(resource, currentDataSource); // 通知加載成功,返回轉換後的資源(見源碼分析)
}
private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
  Resource<R> result = resource;
  notifyComplete(result, dataSource);
  stage = Stage.ENCODE;
  try {
    if (deferredEncodeManager.hasResourceToEncode()) {
      deferredEncodeManager.encode(diskCacheProvider, options);
    }
  }
}
void encode(DiskCacheProvider diskCacheProvider, Options options) {
  try {
    diskCacheProvider.getDiskCache().put(key,    //調用DiskLruCacheWrapper緩存轉換後的資源
        new DataCacheWriter<>(encoder, toEncode, options));
  } finally {
  }
}

DecodeJob中執行邏輯如下:

  1. 執行decodeFromData()轉換文件
  2. 調用notifyEncodeAndRelease()返回轉換後的資源
  3. 執行 deferredEncodeManager.encode(diskCacheProvider, options)解析轉換資源
  4. 執行成功後回調資源完成圖片的顯示

到此Glide的緩存機制就介紹完了,正式因爲強大的緩存功能存在才使的我們開發中加載圖片變得如此簡單,通過對源碼的分析能更好的使用框架和學習框架;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章