内存缓存

前引声明

  • 上文中我们知道Glide内存缓存通过两部分组成又叫做运行时缓存
  • LruCache -》MemoryCache cache 实现类是LruResourceCache在创建Glide的时候创建,大小根据当前手给应用分配的内存而定 具体请看源码里的MemorySizeCalculator方法
  • 采用LruCache实现,遵循最近最少使用原则,当缓存大小达到设定缓存大小时将最近使用的最少的缓存对象清除
  • 其内部维护了一个LinkedHashMap 将 缓存对象的强引用缓存
  • 该缓存只存储当前没在使用的资源
  • 弱引用缓存(活动缓存)-》Map<Key, WeakReference<EngineResource<?>>> activeResources
  • 是一个Map集合,存储资源的弱引用
  • 该缓存存储的是正在使用的资源
  • 这里为什么使用弱引用缓存
  1. 因为LruCache的大小有限为了为了保护正在使用的对象不被lruCache回收掉
  2. 为了不影响系统对无引用对象资源的回收
  • 下面我们分取,存两个过程结合源码分析

  • 当然这要从加载图片说起上文我们知道是在Engine.load
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();
        //获取id字符转 加载图片的唯一标识
        final String id = fetcher.getId();
        //生成缓存的key
        EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
                loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
                transcoder, loadProvider.getSourceEncoder());
        // 第一处 1 .根据key 获取 缓存
        EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
        // 此时得到缓存 若不为空则调用ResourceCallback.onResourceReady 将缓存回调出去
        if (cached != null) {
            cb.onResourceReady(cached);
            return null;
        }
        //第二处 2. 从弱引用缓存中获取
        EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
        // 不为空
        if (active != null) {
            // 调用onResourceReady 给target 设置图片
            cb.onResourceReady(active);
            return null;
        }
        //第三处 3. 若缓存中没有则开启新线程 创建 EngineJob 来从磁盘或者网络里加载
        EngineJob current = jobs.get(key);
        if (current != null) {
            current.addCallback(cb);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Added to existing load", startTime, key);
            }
            return new LoadStatus(cb, current);
        }
        EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
        DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
                transcoder, diskCacheProvider, diskCacheStrategy, priority);
        EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
        jobs.put(key, engineJob);
        engineJob.addCallback(cb);
        engineJob.start(runnable);
    }
  • 注意观察上面代码 三处涉及缓存
  • 第一处 EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);从Lrucache获取
  • 第二处 EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);从弱引用缓存获取
  • 第三处 EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);创建EngineRunnable 异步从磁盘缓存或者网络加载资源
  • 下面看第一处 loadFromCache以及其内部执行逻辑
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
        // 如果没使用缓存在返回null
        if (!isMemoryCacheable) {
            return null;
        }
        // 1. 根据key获取缓存
        EngineResource<?> cached = getEngineResourceFromCache(key);
        if (cached != null) {
            // 2. 累计引用次数+1
            cached.acquire();
            // 3. 将其添加到弱引用缓存 key 为EngineKey value 为 ResourceWeakReference
            activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
        }
        // 返回缓存
        return cached;
    }
//-> 在 1 处 调用 getEngineResourceFromCache(key);获取缓存
private EngineResource<?> getEngineResourceFromCache(Key key) {
        // 从缓存中通过remove key对应的缓存 来获取缓存 
        // 获取到说明要使用该资源 将资源从内存缓存中移除
    	// remove 方法 移除并返回当前key 对应的值 
        Resource<?> cached = cache.remove(key);
		//包装成EngineResource
        final EngineResource result;
        if (cached == null) {
            result = null;
        } else if (cached instanceof EngineResource) {
            result = (EngineResource) cached;
        } else {
            result = new EngineResource(cached, true /*isCacheable*/);
        }
        return result;
    }
  • 上面可以看到 在loadFromCache 1 的位置
  • 通过 getEngineResourceFromCache中通过remove(key)方法移除key所对应的缓存并得到该缓存,
  • 此时lruCache中已经将该key对应的缓存移除,代码执行返回 ,此时继续执行loadFromCache方法里的 2 位置的代码
  • 2位置代码判断当前返回缓存是否为空
  • 若不为空则调用cached.acquire();让该资源的被引用次数+1,并put到弱引用缓存activeResources中去. 返回缓存
  • 若为空不处理 直接返回 该缓存 此时代码执行到Engine.load中的第二处通过loadFromActiveResources 方法直接从弱引用缓存中获取缓存
  • 第二处之前判断从内存中得到的缓存是否为空
  • 若为空则调用第二处代码从弱引用缓存获取
  • 不为空则直接通过回调函数onResourceReady(cache)通知target设置图片
  • 下面看 第二处 loadFromActiveResources
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
        EngineResource<?> active = null;
    	 //	从弱引用缓存中获取EngineResource
        WeakReference<EngineResource<?>> activeRef = activeResources.get(key);
        if (activeRef != null) {
            active = activeRef.get();
            //判断缓存是否为空
            if (active != null) {
                // 引用次数+1
                active.acquire();
            } else {
                // 为空则移除该key
                activeResources.remove(key);
            }
        }
        return active;
    }
  • 上面代码可知
  • 直接从弱引用缓存activeResources获取缓存
  • 判断是否为空
  • 若为空则从activeResources中移除当前key
  • 不为空则调用acquire()将该资源引用次数+1
  • 执行return 该方法执行结束 此时代码执行到Engine.load中的第三处
  • 同样的在第三处之前判断从弱引用缓存得到的缓存是否为空
  • 不为空则直接通过回调函数onResourceReady(cache)通知target设置图片
  • 若为空则调用第三处代码从磁盘缓存,或者网络获取
  • 当Lrucache和弱引用缓存中都没有时应该从磁盘或者网络中获取
  • 在第一处 和第二处 都是从内存取缓存的过程
  • 到了第三处则是缓存对象如何存到内存的过程了

取总结

  • 先从LruCache中通过remove(key)获取缓存
  • 若有则从LruCache中移除,acquire+1添加到弱引用缓存activeResources中,并返回直接使用缓存
  • 若没有就从弱引用缓存activeResources中获取
  • 若有则acquire+1并返回直接使用缓存
  • 若没有则从磁盘或者网络获取
  • 这里为什么先从LruCache中获取
  • 为了保证LruCache中没有正在使用的缓存对象

  • 前面的文章分析知道
  • 从磁盘和网络获取缓存是创建EngineRunnable进行异步加载的
  • 具体加载方法在run方法中的decode方法加载到的resource
  • 当资源加载结束后根据resource的值是否为空调用了如下方法,这里以加载成功为例
if (resource == null) {
    onLoadFailed(exception);//加载失败
} else {
    onLoadComplete(resource);//加载成功
}
  • 调用onLoadComplete之后通过Handler 将 发送message 通知主线程加载完成
  • 在Handler的CallBack中 调用job.handleResultOnMainThread();处理加载完成的数据
private void handleResultOnMainThread() {
		//...
    	//构建一个 EngineResource 对象
        engineResource = engineResourceFactory.build(resource, isCacheable);
        hasResource = true;
		// 1. 刚加载完成肯定在使用,所以使用次数 +1 直接加入弱引用缓存
        engineResource.acquire();
        listener.onEngineJobComplete(key, engineResource);
		// 2. 调用ResourceCallback.onResourceReady 将engineResource通知给target,target接收到之后设置到view上
        for (ResourceCallback cb : cbs) {
            if (!isInIgnoredCallbacks(cb)) {
                engineResource.acquire();
                cb.onResourceReady(engineResource);
            }
        }
        // 3. 此时请求执行结束了,应该释放资源
        engineResource.release();
    }
  • 1 ,2,位置代码
  • 当我们刚加载完成,通知给target的时候资源正在被使用,所以使用次数+1put到弱引用缓存
  • 3位置代码 当请求执行结束 target 也操作结束,应该调用release释放该资源
void release() {
       if (acquired <= 0) {
           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) {
           listener.onResourceReleased(key, this);
       }
   }
  • --acquired(引用累计次数)==0的时候调用onResourceReleased
  • –acquired(引用累计次数)==0 说明当前已经没有引用了 可以放到LruCache里了
@Override
   public void onResourceReleased(Key cacheKey, EngineResource resource) {
       Util.assertMainThread();
       // 1 从弱引用缓存中移除
       activeResources.remove(cacheKey);
       // 2 判断是否可缓存 若可缓存则put到LruCache 若不可缓存则根据不同的资源类型调用不同的释放方法 若resource为bitmap则调用bitmap.recycle
       if (resource.isCacheable()) {
           cache.put(cacheKey, resource);
       } else {
           resourceRecycler.recycle(resource);
       }
   }
  • 根据上面可以得出
  • acquired(引用累计次数)>0的时候,放入弱引用缓存
  • acquired>0说明图片资源正在被使用
  • acquired(引用累计次数)==0的时候 则从弱引用缓存移除放入Lrucache中
  • acquired==0说明图片资源没有被使用

存总结

  • 资源加载完成
  • 首先添加到弱引用缓存acquired+1,通知目标target 加载完成
  • 当加载完成target操作结束后标记此次请求加载过程结束后要释放资源
  • 判断--acquired是否等于0若为0 则从弱引用缓存中移除当前缓存,并put到LruCache中