Glide学习笔记之缓存机制

一、内存缓存

    底层运用了近期最少使用的算法(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来实现

扫描二维码关注公众号,回复: 1303945 查看本文章
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);

猜你喜欢

转载自blog.csdn.net/qq_36447701/article/details/80525426
今日推荐