Glide内存缓存机制

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

首先通过博客,可以知道,大概的逻辑都在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一层缓存也是一种方法。

参考:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容