Glide学习笔记之缓存机制

xiaoxiao2021-02-28  15

一、内存缓存

    底层运用了近期最少使用的算法(LruCache算法)以及弱引用机制共同实现。如果能从内存缓存中读取到需要加载的图片,就直接进行回调(cb.onResourceReady),否则,才会开启线程去加载图片。

Glide.with(this) .load(url) .skipMemoryCache(true)//设置内循缓存,默认是开启的;当设置为true:禁用内存缓存 .into(imageView);

    缓存key值:

        在Engine的load()中:

public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher, ... // 获取图片的唯一标示,比如网络图片的url final String id = fetcher.getId(); // 构建一个缓存的key值,EngineKay对象 EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(), loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(), transcoder, loadProvider.getSourceEncoder()); // 获取图片的缓存 EngineResource<?> cached = loadFromCache(key, isMemoryCacheable); if (cached != null) { // 如果有缓存就直接调用GenericRequest的onResourceReady() cb.onResourceReady(cached); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Loaded resource from cache", startTime, key); } return null; } // 没有获取到图片的缓存 EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable); if (active != null) { cb.onResourceReady(active); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Loaded resource from active resources", startTime, key); } return null; } ... }

缓存方式的选择:

public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher, ... // 获取图片的缓存1(使用LruCache算法) EngineResource<?> cached = loadFromCache(key, isMemoryCacheable); if (cached != null) { // 如果有缓存就直接调用GenericRequest的onResourceReady() cb.onResourceReady(cached); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Loaded resource from cache", startTime, key); } return null; } // 获取图片的缓存2(使用弱引用) EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable); if (active != null) { cb.onResourceReady(active); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Loaded resource from active resources", startTime, key); } return null; } ... }

使用LruCache算法

private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) { // 这里的isMemoryCacheable就是skipMemoryCache中传入的boolean值 if (!isMemoryCacheable) { return null; } EngineResource<?> cached = getEngineResourceFromCache(key); if (cached != null) { cached.acquire(); // 将这个缓存图片添加到activeResorce集合中(这是一个弱引用的map),主要是为了保证正在使用中的图片不被LruCache算法回收掉 activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue())); } return cached; } @SuppressWarnings("unchecked") private EngineResource<?> getEngineResourceFromCache(Key key) { // 通过分析这里的cached就是构建Glide对象时创建的LruResourceCache,说明这里是用的是LruCache算法 // 这里就是等我们获取到缓存中的图片以后会将他从缓存中移除掉 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 /*isCacheable*/); } return result; }

使用弱引用

private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) { if (!isMemoryCacheable) { return null; } EngineResource<?> active = null; // 从弱引用的集合中取值 WeakReference<EngineResource<?>> activeRef = activeResources.get(key); if (activeRef != null) { active = activeRef.get(); if (active != null) { active.acquire(); } else { activeResources.remove(key); } } return active; }内存缓存图片加载完成后的写入:

1、写入到弱引用的缓存:

                EngineJob中回到主线程的逻辑

@Override public void onResourceReady(final Resource<?> resource) { this.resource = resource; MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget(); } private static class MainThreadCallback implements Handler.Callback { @Override public boolean handleMessage(Message message) { if (MSG_COMPLETE == message.what || MSG_EXCEPTION == message.what) { EngineJob job = (EngineJob) message.obj; if (MSG_COMPLETE == message.what) { job.handleResultOnMainThread(); } else { job.handleExceptionOnMainThread(); } return true; } return false; } } private void handleResultOnMainThread() { if (isCancelled) { resource.recycle(); return; } else if (cbs.isEmpty()) { throw new IllegalStateException("Received a resource without any callbacks to notify"); } engineResource = engineResourceFactory.build(resource, isCacheable); hasResource = true; // Hold on to resource for duration of request so we don't recycle it in the middle of notifying if it // synchronously released by one of the callbacks. engineResource.acquire(); // 这里回调了Engine的onEngineJobComplete() listener.onEngineJobComplete(key, engineResource); for (ResourceCallback cb : cbs) { if (!isInIgnoredCallbacks(cb)) { engineResource.acquire(); cb.onResourceReady(engineResource); } } // Our request is complete, so we can release the resource. engineResource.release(); }

Engine的onEngineJobComplete()

@SuppressWarnings("unchecked") @Override public void onEngineJobComplete(Key key, EngineResource<?> resource) { Util.assertMainThread(); // A null resource indicates that the load failed, usually due to an exception. if (resource != null) { resource.setResourceListener(key, this); if (resource.isCacheable()) { // 写入到弱引用的缓存 activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue())); } } // TODO: should this check that the engine job is still current? jobs.remove(key); }2、写入到LruCache的缓存: 同理来到 private void handleResultOnMainThread() { if (isCancelled) { resource.recycle(); return; } else if (cbs.isEmpty()) { throw new IllegalStateException("Received a resource without any callbacks to notify"); } engineResource = engineResourceFactory.build(resource, isCacheable); hasResource = true; // Hold on to resource for duration of request so we don't recycle it in the middle of notifying if it // synchronously released by one of the callbacks. // 记录图片被引入的次数:+1 engineResource.acquire(); // 这里回调了Engine的onEngineJobComplete()(写入弱引用缓存) listener.onEngineJobComplete(key, engineResource); for (ResourceCallback cb : cbs) { if (!isInIgnoredCallbacks(cb)) { engineResource.acquire(); cb.onResourceReady(engineResource); } } // Our request is complete, so we can release the resource. // 写入到LruCache的缓存 // 记录图片被引入的次数:-1 engineResource.release(); } 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; } void release() { if (acquired <= 0) { 当acquired>0说明正在使用,图片放到了activeResource的弱引用当中 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) { // 当acquired=0的时候说明已经不再使用,调用此方法来释放资源 listener.onResourceReleased(key, this); } } @Override public void onResourceReleased(Key cacheKey, EngineResource resource) { Util.assertMainThread(); // 先从activeResources中移除 activeResources.remove(cacheKey); if (resource.isCacheable()) { // 在put到LruResourceCache中 cache.put(cacheKey, resource); } else { resourceRecycler.recycle(resource); } } 以上就可以得到:正在使用中的图片使用弱引用缓存,使用过后的用LruCache缓存

为什么Glide内存缓存LruCache还要结合弱引用???

glide的LruCache还实现了另一个接口MemoryCache,有trimMemory和clearMemory等回收内存的方法。换言之LruCache中的数据并不安全,activeResources是安全的。 查看LruCache算法的实现,你会发现它其实是用一个Set来缓存对象的,每次内存超出缓存设定触发trim操作的时候,其实是对这个Set进行遍历,然后移除缓存。但是我们都知道Set是无序的,因此遍历的时候有可能会把正在使用的缓存给误伤了,我还在用着它呢就给移出去了。因此这个弱引用可能是对正在使用中的图片的一种保护,使用的时候先从LruCache里面移出去,用完了再把它重新加到缓存里面。

怎么手动设置Glide的内存缓存的大小

在配置内存缓存的时候我们需要同时配置BitmapPool的大小,需要通过自定义的GlideModule来实现

public class MyMemoryCache implements GlideModule { @Override public void applyOptions(Context context, GlideBuilder builder) { builder.setMemoryCache(new LruResourceCache(1)); builder.setBitmapPool(new LruBitmapPool(1)); } @Override public void registerComponents(Context context, Glide glide) { } }

一般情况下是不用我们去手动设置内存缓存的大小,这些Glide已经帮我们做好了:

Glide.with(this).setMemoryCategory(MemoryCategory.HIGH);

MemoryCategory.HIGH(初始缓存的1.5倍)、MemoryCategory.NORMAL(初始大小的1倍)、MEmoryCategory.LOW(初始大小的0.5倍)

怎么手动去设置Glide的磁盘缓存的大小和位置

同理需要去自定义自己的GlideModule public class MyMemoryCache implements GlideModule { @Override public void applyOptions(Context context, GlideBuilder builder) { //应用的内部储存 builder.setDiskCache(new InternalCacheDiskCacheFactory(context, "glide_cache", 100 * 1024 * 1024)); //构建的是应用的外部储存 builder.setDiskCache(new ExternalCacheDiskCacheFactory(context, "glide_cache", 100 * 1024 * 1024)); } @Override public void registerComponents(Context context, Glide glide) { } }如果要设置缓存的位置: public class MyMemoryCache implements GlideModule { @Override public void applyOptions(Context context, GlideBuilder builder) { //将图片缓存到自定义的路径中 builder.setDiskCache(new DiskLruCacheFactory(new DiskLruCacheFactory.CacheDirectoryGetter() { @Override public File getCacheDirectory() { //此处返回的文件不能为空,是一个已经创建好的文件夹,不能是文件 return null; } } , 100 * 1024 * 1024)); } @Override public void registerComponents(Context context, Glide glide) { } }以上两种都需要在<application>标签中加入一个meta-data配置项,其中android:name指定成我们自定义的MyGlideModule的完整路径,android:value必须指定成GlideModule,这个是固定值。

二、硬盘缓存

     Glide.with(this) .load(url) //禁止从硬盘缓存;ALL(原始图片和转换后的图片都缓存)、NONE(不缓存)、SOURCE(只缓存原始图片)、RESULT(只缓存转换后的图片) .diskCacheStrategy(DiskCacheStrategy.NONE) .into(imageView);缓存key值: 在Engine的load()中: public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher, ... // 获取图片的唯一标示,比如网络图片的url final String id = fetcher.getId(); // 构建一个缓存的key值,EngineKay对象 EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(), loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(), transcoder, loadProvider.getSourceEncoder()); // 获取图片的缓存 EngineResource<?> cached = loadFromCache(key, isMemoryCacheable); if (cached != null) { // 如果有缓存就直接调用GenericRequest的onResourceReady() cb.onResourceReady(cached); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Loaded resource from cache", startTime, key); } return null; } // 没有获取到图片的缓存 EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable); if (active != null) { cb.onResourceReady(active); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Loaded resource from active resources", startTime, key); } return null; } ... }Glide加载图片以后会执行EngineRunnable的run() @Override public void run() { if (isCancelled) { return; } Exception exception = null; Resource<?> resource = null; try { // 缓存存在于解码的逻辑中 resource = decode(); } catch (Exception e) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Exception decoding", e); } exception = e; } if (isCancelled) { if (resource != null) { resource.recycle(); } return; } if (resource == null) { onLoadFailed(exception); } else { onLoadComplete(resource); } } private Resource<?> decode() throws Exception { if (isDecodingFromCache()) { // 读取缓存的数据 return decodeFromCache(); } else { return decodeFromSource(); } } private Resource<?> decodeFromCache() throws Exception { Resource<?> result = null; try { // 一个是转换过后的图 result = decodeJob.decodeResultFromCache(); } catch (Exception e) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Exception decoding result from cache: " + e); } } if (result == null) { // 这个是原始图 result = decodeJob.decodeSourceFromCache(); } return result; }转换后的图 public Resource<Z> decodeResultFromCache() throws Exception { if (!diskCacheStrategy.cacheResult()) { return null; } long startTime = LogTime.getLogTime(); Resource<T> transformed = loadFromCache(resultKey); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Decoded transformed from cache", startTime); } startTime = LogTime.getLogTime(); // 直接解码返回 Resource<Z> result = transcode(transformed); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Transcoded transformed from cache", startTime); } 原始图 public Resource<Z> decodeSourceFromCache() throws Exception { if (!diskCacheStrategy.cacheSource()) { return null; } long startTime = LogTime.getLogTime(); Resource<T> decoded = loadFromCache(resultKey.getOriginalKey()); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Decoded source from cache", startTime); } // 原始图还需要将数据转换以后再返回 return transformEncodeAndTranscode(decoded); } private Resource<T> loadFromCache(Key key) throws IOException { // 通过key只获取到硬盘缓存中的图片 File cacheFile = diskCacheProvider.getDiskCache().get(key); if (cacheFile == null) { return null; } Resource<T> result = null; try { result = loadProvider.getCacheDecoder().decode(cacheFile, width, height); } finally { if (result == null) { diskCacheProvider.getDiskCache().delete(key); } } return result; }硬盘缓存的写入:在没有缓存的情况下调用decodeFromSource() public Resource<Z> decodeFromSource() throws Exception { Resource<T> decoded = decodeSource(); return transformEncodeAndTranscode(decoded); } private Resource<T> decodeSource() throws Exception { Resource<T> decoded = null; try { long startTime = LogTime.getLogTime(); final A data = fetcher.loadData(priority); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Fetched data", startTime); } if (isCancelled) { return null; } decoded = decodeFromSourceData(data); } finally { fetcher.cleanup(); } return decoded; } private Resource<T> decodeFromSourceData(A data) throws IOException { final Resource<T> decoded; if (diskCacheStrategy.cacheSource()) { // 判断是否需要缓存原始图 decoded = cacheAndDecodeSourceData(data); } else { long startTime = LogTime.getLogTime(); decoded = loadProvider.getSourceDecoder().decode(data, width, height); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Decoded from source", startTime); } } return decoded; } private Resource<T> cacheAndDecodeSourceData(A data) throws IOException { long startTime = LogTime.getLogTime(); SourceWriter<A> writer = new SourceWriter<A>(loadProvider.getSourceEncoder(), data); // 写入硬盘缓存,key传的是resultKey.getOriginalKey()这是就是原始图片的缓存 diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Wrote source to cache", startTime); } startTime = LogTime.getLogTime(); Resource<T> result = loadFromCache(resultKey.getOriginalKey()); if (Log.isLoggable(TAG, Log.VERBOSE) && result != null) { logWithTimeAndKey("Decoded source from cache", startTime); } return result; } private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) { long startTime = LogTime.getLogTime(); // 先转码 Resource<T> transformed = transform(decoded); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Transformed resource from source", startTime); } // 写入硬盘缓存 writeTransformedToCache(transformed); startTime = LogTime.getLogTime(); Resource<Z> result = transcode(transformed); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Transcoded transformed from source", startTime); } return result; } private void writeTransformedToCache(Resource<T> transformed) { if (transformed == null || !diskCacheStrategy.cacheResult()) { return; } long startTime = LogTime.getLogTime(); SourceWriter<Resource<T>> writer = new SourceWriter<Resource<T>>(loadProvider.getEncoder(), transformed); // 这是转换过后的图片的缓存,传入的key就是在Engine中生成的key diskCacheProvider.getDiskCache().put(resultKey, writer); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Wrote transformed from source to cache", startTime); } }

三、高级用法

结合前面的内存缓存和硬盘缓存,我们发现缓存的key是由传入的url决定的,而现实中我们的项目图片资源可能在url后面拼接上如token参数这些随时变化的参数,这样就会使我们的缓存失效。

来到Glide生成key的地方

public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher, DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder, Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) { Util.assertMainThread(); long startTime = LogTime.getLogTime(); // 这里的fetcher其实就是HttpUrlFetcher final String id = fetcher.getId(); EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(), loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(), transcoder, loadProvider.getSourceEncoder()); }

HttpUrlFetcher的getId()

@Override public String getId() { return glideUrl.getCacheKey(); } public String getCacheKey() { return stringUrl != null ? stringUrl : url.toString(); }发现id就是url(如果是string类型的就是本身,如果是Url对象就是url.toString())

所以我们的解决办法就是创建一个MyGlideUrl继承自Glide的GlideUrl重写getCacheKey()

public class MyGlideUrl extends GlideUrl { private String mUrl; public MyGlideUrl(String url) { super(url); this.mUrl = url; } @Override public String getCacheKey() { return mUrl.replace(tokenParam(), ""); } private String tokenParam() { String param = ""; int tokonIndex = mUrl.indexOf("?token=") >= 0 ? mUrl.indexOf("?token=") : mUrl.indexOf("&token="); if (tokonIndex != -1) { int nextIndex = mUrl.indexOf("&", tokonIndex + 1); if (nextIndex != -1) { param = mUrl.substring(tokonIndex + 1, nextIndex + 1); } else { param = mUrl.substring(tokonIndex); } } return param; } }

activity中引用:

Glide.with(this).load(new MyGlideUrl(url)).into(imageView);
转载请注明原文地址: https://www.6miu.com/read-1650060.html

最新回复(0)