Glide4缓存机制源码解析

本文深入解析Glide4的缓存机制,从网络图片加载流程入手,详细阐述了从远程获取图片到本地缓存的过程,包括数据加载器的选择、缓存策略的实施以及磁盘缓存的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这篇文章基于Glide4,只分析Glide4的缓存机制,如果大家还不了解Glide4源码,可以去看一下下面这两篇做一下铺垫:

Glide4初始化

Glide4数据模型转换与数据获取

好了,直接进入正题~~

这里以加载一张网络图片来讲解缓存过程,从前面的文章中可以知道,当第一次加载一张新的网络图片时,本地是没有这张网络图片的缓存的。

所以会进入到SourceGenerator类的startNext()方法中

public boolean startNext() {
    //1.判断是否有缓存,如果有,直接加载缓存(这里第一次加载,所以dataToCache为null)
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      cacheData(data);
    }

    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
      return true;
    }
    sourceCacheGenerator = null;
    //2.没有缓存,从远程加载
    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      //3.获取数据加载器
      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;
  }

因为是首次加载,所以我们直接看从远程加载图片的情况。

获取到数据加载器loadData后,会调用loadData.fetcher.loadData(helper.getPriority(),this)

void loadData(@NonNull Priority priority, @NonNull DataCallback<? super T> callback);

第二个参数是个回调,数据加载器在获取到数据后,会回调callback的onDataReady方法,并传入获取到的数据。

这里数据加载器有很多种,感兴趣的可以去看上面的文章。由于这里是从远程加载图片,所以最终会得到HttpGlideUrlLoader这个加载器,我们围绕这个数据加载器来展开,

HttpGlideUrlLoader继承自ModelLoader,HttpGlideUrlLoader类的buildLoadData方法如下

    @Override
  public LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height,
      @NonNull Options options) {
    // GlideUrls memoize parsed URLs so caching them saves a few object instantiations and time
    // spent parsing urls.
    GlideUrl url = model;
    if (modelCache != null) {
      url = modelCache.get(model, 0, 0);
      if (url == null) {
        modelCache.put(model, 0, 0, model);
        url = model;
      }
    }
    int timeout = options.get(TIMEOUT);
    return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
  }

返回一个LoadData,第二个参数fetcher传入一个HttpUrlFetcher对象,之后会调用HttpUrlFetcher的loadData方法:

    @Override
  public void loadData(@NonNull Priority priority,
      @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
      //获取资源的InputStream
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
      //调用回调函数
      callback.onDataReady(result);
    } catch (IOException e) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Failed to load data for url", e);
      }
      callback.onLoadFailed(e);
    } finally {
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
      }
    }
  }

可以看到,这个方法中调用loadDataWithRedirects方法获取输入流result,然后调用callback回调函数onDataReady。

我们知道这里的callback就是SourceGenerator对象,

    @Override
  public void onDataReady(Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
      //将data赋值给dataToCache
      dataToCache = data;
      // We might be being called back on someone else's thread. Before doing anything, we should
      // reschedule to get back onto Glide's thread.
      //重新执行此次加载,即重新调用startNext方法
      cb.reschedule();
    } else {
      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
          loadData.fetcher.getDataSource(), originalKey);
    }
  }

这里将data赋值给dataToCache,然后重新执行此次任务,最终重新调用startNext方法,不过这次dataToCache不为空了

public boolean startNext() {
    //1.判断是否有缓存,如果有,直接加载缓存(这里第一次加载,所以dataToCache为null)
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      cacheData(data);
    }

    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
      return true;
    }
    sourceCacheGenerator = null;
    //2.没有缓存,从远程加载
    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      //3.获取数据加载器
      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;
  }

dataToCache不为空,所以这次执行cacheData方法,这里面就是做缓存了,我们一起来看一下

private void cacheData(Object dataToCache) {
    long startTime = LogTime.getLogTime();
    try {
      //将dataToCache封装为一个DataCacheWriter对象
      Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
      DataCacheWriter<Object> writer =
          new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
      //生成key
      originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
      //缓存
      helper.getDiskCache().put(originalKey, writer);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished encoding source to cache"
            + ", key: " + originalKey
            + ", data: " + dataToCache
            + ", encoder: " + encoder
            + ", duration: " + LogTime.getElapsedMillis(startTime));
      }
    } finally {
      loadData.fetcher.cleanup();
    }

    sourceCacheGenerator =
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
  }

将dataToCache封装为一个DataCacheWriter对象,然后生成key,即DataCacheKey对象,然后调用helper.getDiskCache().put()进行缓存。

接下来我们来看一下helper.getDiskCache()得到了什么?

这里helper即DecodeHelper,它的getDiskCache方法如下

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

这里的diskCacheProvider是从哪里来的呢?

其实如果你一直追到源头,你会发现这个!在GlideBuilder类的build方法中

@NonNull
  Glide build(@NonNull Context context) {
    ......
    //构造内存缓存对象
    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);
  }

其实是在构造Glide对象的时候生成的,一个是内存缓存LruResourceCache,一个是磁盘缓存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) {
    //调用父构造函数,返回DiskLruCacheFactory对象
    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);
  }
}

可以看到,其构造函数调用了父构造函数,因为其继承了DiskLruCacheFactory,所以得到的是DiskLruCacheFactory对象。

我们再看DiskLruCacheFactory的build方法

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

 返回DiskLruCacheWrapper对象,helper.getDiskCache()方法得到的也是DiskLruCacheWrapper对象。

DiskLruCacheWrapper里面就是缓存的逻辑所在了~~

我们看一下它的put方法

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

这里在参数key的基础上再调用了safeKeyGenerator.getSafeKey()生成safeKey,然后采用了Lock机制,获取锁,进行缓存操作,在finally中释放锁。

问题是这里缓存是如何做的?这个Writer是什么?

我们的data是通过HttpUrlFetcher得到的,我们再看一下这个类的loadData方法

    @Override
  public void loadData(@NonNull Priority priority,
      @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
      callback.onDataReady(result);
    } catch (IOException e) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Failed to load data for url", e);
      }
      callback.onLoadFailed(e);
    } finally {
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
      }
    }
  }

返回的是一个InputStream对象,也就是说这个data就是一个InputStream。

我们回头看一下cacheData方法

private void cacheData(Object dataToCache) {
    long startTime = LogTime.getLogTime();
    try {
      //将dataToCache封装为一个DataCacheWriter对象
      Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
      DataCacheWriter<Object> writer =
          new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
      //生成key
      originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
      //缓存
      helper.getDiskCache().put(originalKey, writer);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished encoding source to cache"
            + ", key: " + originalKey
            + ", data: " + dataToCache
            + ", encoder: " + encoder
            + ", duration: " + LogTime.getElapsedMillis(startTime));
      }
    } finally {
      loadData.fetcher.cleanup();
    }

    sourceCacheGenerator =
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
  }

已经知道dataToCache是个InputStream,其中调用helper.getSourceEncoder(dataToCache)获取Encoder对象,

  //DecodeHelper类

  <X> Encoder<X> getSourceEncoder(X data) throws Registry.NoSourceEncoderAvailableException {
    return glideContext.getRegistry().getSourceEncoder(data);
  }

最终会从Registry类中获取Encoder,

//Registry类

public <X> Encoder<X> getSourceEncoder(@NonNull X data) throws NoSourceEncoderAvailableException {
    Encoder<X> encoder = encoderRegistry.getEncoder((Class<X>) data.getClass());
    if (encoder != null) {
      return encoder;
    }
    throw new NoSourceEncoderAvailableException(data.getClass());
  }

又从encoderRegistry中获取。encoderRegistry里的Encoder是在Glide初始化过程中添加的,在Glide的构造函数中有这么一段

    registry
        .append(ByteBuffer.class, new ByteBufferEncoder())
        .append(InputStream.class, new StreamEncoder(arrayPool))
        /* Bitmaps */
        .append(Registry.BUCKET_BITMAP, ByteBuffer.class, Bitmap.class, byteBufferBitmapDecoder)
        .append(Registry.BUCKET_BITMAP, InputStream.class, Bitmap.class, streamBitmapDecoder)
        .append(
            Registry.BUCKET_BITMAP,
            ParcelFileDescriptor.class,
            Bitmap.class,
            parcelFileDescriptorVideoDecoder)
        .append(
            Registry.BUCKET_BITMAP,
            AssetFileDescriptor.class,
            Bitmap.class,
            VideoDecoder.asset(bitmapPool))
        ......

其中前面两个就是Encoder,ByteBufferEncoder和StreamEncoder。

那选哪个呢?

我们从encoderRegistry中获取Encoder的时候会做判断

//EncoderRegistry.java

  public synchronized <T> Encoder<T> getEncoder(@NonNull Class<T> dataClass) {
    for (Entry<?> entry : encoders) {
      if (entry.handles(dataClass)) {
        return (Encoder<T>) entry.encoder;
      }
    }
    return null;
  }

只有Encoder的handles方法返回true时才采用该Encoder。上面的for循环从encoders中逐个取出Entry实例,

 private static final class Entry<T> {
    private final Class<T> dataClass;
    @Synthetic @SuppressWarnings("WeakerAccess") final Encoder<T> encoder;

    Entry(@NonNull Class<T> dataClass, @NonNull Encoder<T> encoder) {
      this.dataClass = dataClass;
      this.encoder = encoder;
    }

    boolean handles(@NonNull Class<?> dataClass) {
      return this.dataClass.isAssignableFrom(dataClass);
    }
  }

Entry的handles决定了是否能处理该任务,内部属性dataClass是能处理的数据类型,encoder对象就是解码器。

由于我们的data是InputStream类型的,所以只有StreamEncoder才能处理这个类型

我们回到SourceGenerator的cacheData方法里~

得到Encoder解码器后,包装为一个DataCacheWriter,最后调用put方法进行缓存。

class DataCacheWriter<DataType> implements DiskCache.Writer {
  private final Encoder<DataType> encoder;
  private final DataType data;
  private final Options options;

  DataCacheWriter(Encoder<DataType> encoder, DataType data, Options options) {
    this.encoder = encoder;
    this.data = data;
    this.options = options;
  }

  @Override
  public boolean write(@NonNull File file) {
    return encoder.encode(data, file, options);
  }
}

我们看到DiskLruCacheWrapper的put方法

public void put(Key key, Writer writer) {
    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 {
        ...
        DiskLruCache.Editor editor = diskCache.edit(safeKey);
        try {
          File file = editor.getFile(0);
          if (writer.write(file)) {
            editor.commit();
          }
        } finally {
          editor.abortUnlessCommitted();
        }
      }
    ...
    } finally {
      writeLocker.release(safeKey);
    }
  }

里面调用了writer的write方法,write方法里再调用解码器encoder的encode方法,这里的encoder就是StreamEncoder

//StreamEncoder.class

public boolean encode(@NonNull InputStream data, @NonNull File file, @NonNull Options options) {
    byte[] buffer = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
    boolean success = false;
    OutputStream os = null;
    try {
      os = new FileOutputStream(file);
      int read;
      while ((read = data.read(buffer)) != -1) {
        os.write(buffer, 0, read);
      }
      os.close();
      success = true;
    } catch (IOException e) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Failed to encode data onto the OutputStream", e);
      }
    } finally {
      if (os != null) {
        try {
          os.close();
        } catch (IOException e) {
          // Do nothing.
        }
      }
      byteArrayPool.put(buffer);
    }
    return success;
  }

很简单,就是文件的读写而已~~

 

至此,磁盘缓存的操作就完成了~~

喜欢的点个赞~~~

 

 

 

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值