Android 开发之Glide《五》缓存

前言

一、简介

缓存设置

     Glide.with(this).load("url").skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.ALL)

设置内存缓存开关:true 表示不使用内存缓存

skipMemoryCache()

设置磁盘缓存模式:

diskCacheStrategy()

可以设置4种模式:

  1. DiskCacheStrategy.NONE:表示不缓存任何内容。
  2. DiskCacheStrategy.DATA:表示只缓存原始图片。
  3. DiskCacheStrategy.RESOURCE:表示只缓存转换过后的图片(默认选项)。
  4. DiskCacheStrategy.ALL :表示既缓存原始图片,也缓存转换过后的图片。

这里需要主要是RESOURCE,当我们下载下来图片之后可能对图片裁剪,放缩等操作,所有转换之后的图片就是RESOURCE。

二 内存缓存

Engine.java

  @Nullable
  private EngineResource<?> loadFromMemory(
      EngineKey key, boolean isMemoryCacheable, long startTime) {
    if (!isMemoryCacheable) {
      return null;
    }

    EngineResource<?> active = loadFromActiveResources(key);
    if (active != null) {
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return active;
    }

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

    return null;
  }

在下载图片之前会现在内存里面查找对应的图片,这个方法在Engine的loadFromMemory,

loadFromMemory 里面有分为两个部分一是ActiveResources,表示当前正在使用的资源,什么是当前正在使用?就是有某一个View 正在显示这个图片,根本上说是这个图片存在一个强引用。ActiveResources 使用的弱引用来缓存图片,弱引用应用的资源在没有强引用的时候会被自动回收。换句话说当页面销毁了图片的强引用就没了,此时系统会自动回收所有的弱引用引用的资源。这样可以更好的避免内存泄露。

loadFromCache 使用的Lru 缓存的图片,简单区分就是loadFromMemory 缓存的图片表示当前一定有一个View 在显示它,loadFromCache 厘米缓存的图片不一定存在View 显示它。

2.1 弱引用缓存

  private final ActiveResources activeResources;

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

    return active;
  }
 final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();

在这里插入图片描述
在这里插入图片描述

如果还有对WeakReference 不理解的同学可以百度一下。

2.2 LruCache

  private EngineResource<?> loadFromCache(Key key) {
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      cached.acquire();
      activeResources.activate(key, cached);
    }
    return cached;
  }
 每次从LruCache 读取的缓存都要添加到ActiveResources里面,因为读取就表示需要显示。
  private final MemoryCache cache;

  private EngineResource<?> getEngineResourceFromCache(Key key) {
    Resource<?> cached = cache.remove(key);

    final EngineResource<?> result;
    if (cached == null) {
      result = null;
    } else if (cached instanceof EngineResource) {
      // Save an object allocation if we've cached an EngineResource (the typical case).
      result = (EngineResource<?>) cached;
    } else {
      result =
          new EngineResource<>(
              cached, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
    }
    return result;
  }

在这里插入图片描述

在这里插入图片描述

这里主要是通过cache.获取缓存的图片。

LruCache

Map<T, Y> cache = new LinkedHashMap<>(100, 0.75f, true);
  @Nullable
  public synchronized Y remove(@NonNull T key) {
    final Y value = cache.remove(key);
    if (value != null) {
      currentSize -= getSize(value);
    }
    return value;
  }

此处的cache 是一个LinkedHashMap,LinkedHashMap 本身实现了Lru算法,有不了解的同学可以自行查询,这里不做介绍。

三 磁盘缓存

在Glide中磁盘缓存默认使用的也是LRU(Least Recently Used)算法。如果你想使用其他的缓存算法,就只能通过实现DiskCache接口来完成了。

在这里插入图片描述

在创建Glide 的时候会使用一个默认的DiskLruCacheFactory 来创建对应的DiskLruCache

InternalCacheDiskCacheFactory

在这里插入图片描述

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

最终创建的对象是DiskLruCacheWrapper

  protected DiskLruCacheWrapper(File directory, long maxSize) {
//缓存的目录
    this.directory = directory;
//缓存的大小
    this.maxSize = maxSize;
    this.safeKeyGenerator = new SafeKeyGenerator();
  }

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

  @Override
  public File get(Key key) {
  //获取文件的名字
    String safeKey = safeKeyGenerator.getSafeKey(key);
    File result = null;
    try {
     
      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;
  }

  @Override
  public void put(Key key, Writer writer) {
//生成key
    String safeKey = safeKeyGenerator.getSafeKey(key);
//获取锁
    writeLocker.acquire(safeKey);
    try {
      try {

        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) {
      }
    } finally {
//释放锁
      writeLocker.release(safeKey);
    }
  }

  @Override
  public void delete(Key key) {
    String safeKey = safeKeyGenerator.getSafeKey(key);
    try {
      getDiskCache().remove(safeKey);
    } catch (IOException e) {
      if (Log.isLoggable(TAG, Log.WARN)) {
        Log.w(TAG, "Unable to delete from disk cache", e);
      }
    }
  }

}

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

这里对文件的缓存使用的也是DiskLruCache,DiskLruCache 是一个有名的磁盘缓存矿建,在OkHttp 里面也使用到了这个框架。有不了解这个框架的可以查阅一下相关资料也可以看我之前写的一篇文章。

获取获取的调用时机我们已经知道,下面我们看看保存缓存文件的时机。

3.1 缓存图片原文件

前面介绍过Glide 通过SourceGenerator下载图片,当图片下载之后会调用cacheData

  @Override
private void cacheData(Object dataToCache) {
  long startTime = LogTime.getLogTime();
  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);
  } finally {
    loadData.fetcher.cleanup();
  }

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

这里的dataToCache是个InputStream,其中调用helper.getSourceEncoder(dataToCache)获取Encoder对象,

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

获取一个Encoder将 InputStream转换为图片,这里实际对应的是StreamEncoder
在这里插入图片描述

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

我们看到DiskLruCacheWrapper的put方法

  public void put(Key key, Writer writer) {
   //生成文件名
    String safeKey = safeKeyGenerator.getSafeKey(key);
    writeLocker.acquire(safeKey);
    try {

      try {
        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) {
      }
    } finally {
      writeLocker.release(safeKey);
    }
  }

这里主要是通过writer.write(file) 来保存对应的文件,writer是DataCacheWriter

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

encoder 的实际为StreamEncoder。

  @Override
  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) {
    } finally {
      if (os != null) {
        try {
          os.close();
        } catch (IOException e) {
          // Do nothing.
        }
      }
      byteArrayPool.put(buffer);
    }
    return success;
  }
}

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

以上介绍的是缓存的图片源文件,下面我们看看对图片转换后的缓存

3.2 缓存转换后的图片

这一部分在DecodeJob 里面,当下载图片并执行完图片转换之后会执行到DecodeJob 的decodeFromRetrievedData 方法
在这里插入图片描述

decodeFromRetrievedData 最终归会调用到DeferredEncodeManager 的encode 方法

在这里插入图片描述

     diskCacheProvider
            .getDiskCache()
            .put(key, new DataCacheWriter<>(encoder, toEncode, options));

通过DiskCache 的put方法缓存图片,put 方法在上面有源码。
这里的encoder 一般是BitmapDrawableDecoder
在这里插入图片描述
在这里插入图片描述

  public boolean encode(
      @NonNull Resource<BitmapDrawable> data, @NonNull File file, @NonNull Options options) {
    return encoder.encode(new BitmapResource(data.get().getBitmap(), bitmapPool), file, options);
  }
``
BitmapDrawableEncoder 调用了BitmapEncoder`的encode

```java
  @Override
  public boolean encode(
      @NonNull Resource<Bitmap> resource, @NonNull File file, @NonNull Options options) {
    final Bitmap bitmap = resource.get();
    Bitmap.CompressFormat format = getFormat(bitmap, options);
    try {
      long start = LogTime.getLogTime();
      int quality = options.get(COMPRESSION_QUALITY);

      boolean success = false;
      OutputStream os = null;
      try {
        os = new FileOutputStream(file);
        if (arrayPool != null) {
          os = new BufferedOutputStream(os, arrayPool);
        }
        //将图片保存的文件里面去
        bitmap.compress(format, quality, os);
        os.close();
        success = true;
      } catch (IOException e) {
 
      } finally {
        if (os != null) {
          try {
            os.close();
          } catch (IOException e) {
            // Do nothing.
          }
        }
      }

      }
      return success;
    } finally {
      GlideTrace.endSection();
    }
  }

可以看到最终通过 bitmap.compress(format, quality, os); 将图片保存到对应的文件里面去

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值