Glide(四)内存缓存

本文深入探讨了Glide的内存缓存机制,包括LruCache和弱引用缓存activeResources的使用。在加载图片时,首先尝试从LruCache中移除并使用缓存,接着检查弱引用缓存。若两者都未找到,则从磁盘或网络加载资源。加载完成后,资源会被添加到弱引用缓存,并在不再使用时释放回LruCache。整个过程确保了内存的有效管理和资源的高效利用。
摘要由CSDN通过智能技术生成

内存缓存

前引声明

  • 上文中我们知道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中
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值