美文网首页
Glide内存缓存机制

Glide内存缓存机制

作者: 春日野家的兔子 | 来源:发表于2018-03-29 15:01 被阅读0次

    这篇是在郭霖的这个博客的基础上,对内存缓存的一次理解,而且磁盘那里感觉可以归到网上和本地一类的东西,就没过于细看,主要是要研究下内存缓存生命周期的问题。

    首先通过博客,可以知道,大概的逻辑都在Engine这个类里面。然后我们先定位到里面的load()方法

    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);
    
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return null;
    }
    
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return null;
    }
    
    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    

    上面就是创建一个key,然后通过这个key来依次查找内存中的缓存,第一个是当前被使用的缓存,第二个是lrucache缓存,然后都没有再创建EngineJob,这里后面会谈及。

    private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {
          return null;
        }
        EngineResource<?> active = activeResources.get(key);
        if (active != null) {
          active.acquire();
        }
    
        return active;
    }
    
    private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {
          return null;
        }
    
        EngineResource<?> cached = getEngineResourceFromCache(key);
        if (cached != null) {
          cached.acquire();
          activeResources.activate(key, cached);
        }
        return cached;
    }
    
    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, true /*isMemoryCacheable*/, true /*isRecyclable*/);
        }
        return result;
    }
    

    第一步就是从ActiveResources的对象中通过key找到对应的缓存,然后返回,没有的话就从cache也就是lrucache中找出,然后在lrucache删除这个缓存,然后加入activeResource中,这里就是为了确保一个,只要是正在使用的缓存,都是加入到ActiveResource中的。

    那么这里的核心就是ActiveResource这个类。

    final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
    
    static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
        @SuppressWarnings("WeakerAccess") @Synthetic final Key key;
        @SuppressWarnings("WeakerAccess") @Synthetic final boolean isCacheable;
        @Nullable @SuppressWarnings("WeakerAccess") @Synthetic Resource<?> resource;
        ...
    }
    void activate(Key key, EngineResource<?> resource) {
        ResourceWeakReference toPut =
            new ResourceWeakReference(
                key,
                resource,
                getReferenceQueue(),
                isActiveResourceRetentionAllowed);
    
        ResourceWeakReference removed = activeEngineResources.put(key, toPut);
        if (removed != null) {
          removed.reset();
    }
    private ReferenceQueue<EngineResource<?>> getReferenceQueue() {
        if (resourceReferenceQueue == null) {
          resourceReferenceQueue = new ReferenceQueue<>();
          cleanReferenceQueueThread = new Thread(new Runnable() {
            @SuppressWarnings("InfiniteLoopStatement")
            @Override
            public void run() {
              Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
              cleanReferenceQueue();
            }
          }, "glide-active-resources");
          cleanReferenceQueueThread.start();
        }
        return resourceReferenceQueue;
    }
    
    @SuppressWarnings("WeakerAccess")
    @Synthetic void cleanReferenceQueue() {
        while (!isShutdown) {
          try {
            ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
            mainHandler.obtainMessage(MSG_CLEAN_REF, ref).sendToTarget();
    
            // This section for testing only.
            DequeuedResourceCallback current = cb;
            if (current != null) {
              current.onResourceDequeued();
            }
            // End for testing only.
          } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
          }
    }
    

    上面就是ActiveResources的核心类了,这里内部主要是维护了一个弱引用的Map来储存对应的缓存数据,然后每次加入这个缓存数据的时候,都会和ReferenceQueue来绑定到一起。

    还能看到,当里面有缓存的时候,会自动创建一个后台线程,而且不会中断,这里就调用了ReferenceQueue的remove()方法,这个方法是阻塞方法,当有一个缓存会系统回收,我们就能得到这个弱应用的对象,最后通过一系列操作,调用了 listener.onResourceReleased(ref.key, newResource);这个方法来将这个回收的对象再处理。

    这里有个重点就是,WeakReference绑定的对象在remove的时候,是已经为null了,所以这个get()是得不到缓存的,所以也就是为什么这里要自己有个内部类并继承了WeakReference,是要保留里面的source和key,以生成新的Resource,来继续新的处理。

    @Override
    public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
        Util.assertMainThread();
        activeResources.deactivate(cacheKey);
        if (resource.isCacheable()) {
          cache.put(cacheKey, resource);
        } else {
          resourceRecycler.recycle(resource);
        }
    }
    

    这里就是最终的回调方法,也就是再次删除Map中这个对象,然后加入lrucache中,或者直接回收对象。

    至于对应的cache也就是如我上面说的那样是lrucache不过自己改写了一些方法,就没什么好说的了。

    class EngineResource<Z> implements Resource<Z> {
      private Key key;
      private int acquired;
      private boolean isRecycled;
      private final Resource<Z> resource;
      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) {
          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);
        }
      }
      ...
    }
    

    下面就是这个缓存的数据EngineResource这个类,至于为啥要看这个类,还是说道Engine的onRedourceReleased这个回调方法(就是从Map中弱引用要删除时的操作)除了刚刚系统自动删除然后我们回调外,就是从Engine这里回调了。

    从郭霖的那个博客,也可以知道这两个方法的含义,就是通过acquired来判断这个缓存有多少在使用,acquire()就+1,release()就-1,当他为0的时候说明此时已经没有内存在使用,应该删除缓存加入lrucache中,所以回调这个方法,也很好理解。

    要弄懂缓存的生命周期,下面就是看这个acquire()和release()什么时候,在哪使用。

    首先是acquire(),这首先在engine中有2处出现, 都是在我们上面的代码,也就是分别从ActiveResource和lruCache中找到资源时调用,还有两处就是出现在EngineJob类中,定位到那里

    void handleResultOnMainThread() {
        // 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();
        listener.onEngineJobComplete(this, key, engineResource);
    
        //noinspection ForLoopReplaceableByForEach to improve perf
        for (int i = 0, size = cbs.size(); i < size; i++) {
          ResourceCallback cb = cbs.get(i);
          if (!isInIgnoredCallbacks(cb)) {
            engineResource.acquire();
            cb.onResourceReady(engineResource, dataSource);
          }
        }
        // Our request is complete, so we can release the resource.
        engineResource.release();
    
        release(false /*isRemovedFromQueue*/);
      }
      
     @Override
      public void onEngineJobComplete(EngineJob<?> engineJob, 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.activate(key, resource);
          }
        }
    
        jobs.removeIfCurrent(key, engineJob);
      }
    

    嗯,就这两处,这里是EngineJob的这个方法里,就是数据从磁盘或者别处加载好了之后的处理,第一处的注释也很好的说明了为啥要用,不过要和release()相配合,所以是考虑在数据处理阶段,防止某处调用release直接被回收,所以,先调用,然后回调之Engine的这个方法,也就是加入缓存之中,然后就是根据几个请求来调用几次acquire()。

    所以这个EngineResource的acquire()就用在如下的几处:

    1. 从ActiveResources被查找到
    2. 从lrucache中被查找到
    3. 从网络或者磁盘中加载成功

    能很好的看出就是有一处使用它的时候,就通过acquire(),使得EngineResource的内部的acquire+1,也就是被使用数+1,也不从acqiure被回收,而且这3处就是engine中load的3步加载步骤。

    下面就是它的release()方法,除去刚刚在engineJob中看到的保证加载。然后就是出现在Engine的release方法里,那里只有一句resource.release()方法,就不贴代码了,直接去找哪里使用了这个engine的release()方法。

    然后定位到SingleRequest的releaseResource()中。

    这下就很清楚了,当图片加载的一个请求主动要求释放资源的时候,通过一系列操作,最后release(),使用数-1。

    所以最后总结一下这个数据在弱引用Map中的生命周期:

    1. 正常情况下:从lrucache或者磁盘,网络中加载成功时,加入至Map中,代表开始,当所有用到该数据的request也就是请求都主动释放这个资源,然后从里面移除,加入到lrucache之中,代表结束。
    2. 特殊情况下:开始情况一样象,最终结束时,不是通过release,是系统的gc线程,然后扫描到这个对就进行回收,然后加入到lruchche之中,代表结束。
    3. 特殊情况下:在磁盘,网络中加载的过程中,也就是刚加入Map中,没来的及调用后面请求的回调方法和acquire(),直接就被回收了,这里的话我就不清楚会怎么样。

    最后还有一个软引用是softReference,和weakReference,两者区别是gc扫描到的话,weakReference一定被回收,而softReference是当系统内存不足时才被回收,因为这个gilde是被回收了还能加入到lrucache之中,所以是采用了weakReference,当不想太麻烦,直接使用softReference一层缓存也是一种方法。

    参考:

    相关文章

      网友评论

          本文标题:Glide内存缓存机制

          本文链接:https://www.haomeiwen.com/subject/rzbmcftx.html