Glide篇_01关于Bitmap缓存

参考文章:

理解Glide的缓存机制需要掌握以下几个知识点:

1. 软引用/弱引用与缓存队列;
2. LRU原理; 
3. AQS锁: 生产者-消费者模式;

Glide总结:

  1. 网络数据流数据以后进行解码然后转换成Bitmap, 将Bitmap装载进一个BitmapHolder中, 由于此时Bitmap正在被使用, 所以此时进行强引用缓存;
  2. 当Bitmap使用完成以后, 将此Bitmap进行弱引用缓存, 并与对应的引用队列进行绑定;
  3. 当LRU被触发时, 也就是最近最少使用的Bitmap要被回收时, 首先会判断当前BitmapHolder是否为强引用缓存,如果是不进行移除,如果为弱引用缓存, 则进行内存移除;
  4. 同时开启一个线程, 然后从引用队列中获取弱引用对象, 这里采用的是生产者-消费者模式, 集合采用的是BlockQueue的具体实现类, BlockQueue子类有一个共性就是使用了AQS锁, 而AQS锁就是利用了生产者-消费者模式, 如果能够从中获取到引用, 说明该引用持有的BitmapHolder已经被GC回收, 如果被回收, 则发出消息通知BitmapHolder缓存集合删除该BitmapHolder;
  5. BitmapHolder之所以这里需要采用强/软引用缓存是因为, LRU会删除最近最少使用的对象, 而在删除时, 如果碰巧遇到该对象正在被使用, 就会报异常;

一、源码解析:

  • 如果是第一次加载图片, 即不存在缓存的情况下, 最终在HttpUrlFetcher中进行了加载;
1.1 HttpUrlFetcher.loadData:
public class HttpUrlFetcher implements DataFetcher<InputStream> {
    @Override
    public void loadData(Priority priority, DataCallback<? super InputStream> callback) {
        long startTime = LogTime.getLogTime();
        final InputStream result;
        // 获取流数据;
        result = loadDataWithRedirects(glideUrl.toURL(), 0 , null , glideUrl.getHeaders());
        // 1. 流数据获取成功之后, 对其进行解码;模块<1.4>
        // 2. 解码完成便是开始缓存, 显示;模块<二>
        callback.onDataReady(result);
    }
}
1.2 HttpUrlFetcher.loadDataWithRedirects:
private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
      Map<String, String> headers) throws IOException {

    urlConnection = connectionFactory.build(url);
    for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
        urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
    }
    urlConnection.setConnectTimeout(timeout);
    urlConnection.setReadTimeout(timeout);
    urlConnection.setUseCaches(false);
    urlConnection.setDoInput(true);
    urlConnection.setInstanceFollowRedirects(false);
    urlConnection.connect();
    stream = urlConnection.getInputStream();
    if (isCancelled) {
        return null;
    }
    final int statusCode = urlConnection.getResponseCode();
    if (statusCode / 100 == 2) {
        return getStreamForSuccessfulRequest(urlConnection);
    } else if (statusCode / 100 == 3) {
        String redirectUrlString = urlConnection.getHeaderField("Location");
        URL redirectUrl = new URL(url, redirectUrlString);
        cleanup();
        return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
    }
}

private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection) {
    if (TextUtils.isEmpty(urlConnection.getContentEncoding())) {
        int contentLength = urlConnection.getContentLength();
        stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength);
    } else {
        stream = urlConnection.getInputStream();
    }
    return stream;
}
1.4 Downsampler.decode:
public final class Downsampler {
    @SuppressWarnings({"resource", "deprecation"})
    public Resource<Bitmap> decode(InputStream is, int requestedWidth, int requestedHeight,
        Options options, DecodeCallbacks callbacks) {
        Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
          downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
          requestedHeight, fixBitmapToRequestedDimensions, callbacks);
        return BitmapResource.obtain(result, bitmapPool);
    }
}

public class BitmapResource implements Resource<Bitmap>, Initializable {
    private final Bitmap bitmap;
    private final BitmapPool bitmapPool;

    @Nullable
    public static BitmapResource obtain(@Nullable Bitmap bitmap, BitmapPool bitmapPool) {
        // 将Bitmap封装进BitmapResource中;
        return new BitmapResource(bitmap, bitmapPool);
    }
}

二、Bitmap缓存

结合模块一可知, 获取的流数据装入Bitmap中, 然后被封装进BitmapResource中;
2.1 EngineJob.handleResultOnMainThread:
  • 还是省略一万行代码... 经过n多层回调到这里进行内存缓存;
@Synthetic
void handleResultOnMainThread() {
    engineResource = engineResourceFactory.build(resource, isCacheable);
    hasResource = true;
    // 1.这里的acquire与下文的release形成对应, 在加载显示图片之前, 通过acquire方式让
    //   引用计数+1, 图片加载完成之后, 引用计数-1操作;
    // 2.EngineResource内部持有一个Resource, 而该Resource实际指向BitmapDrawableResource, 又持有
    //   BitmapDrawable, BitmapDrawable内部持有了Bitmap;
    engineResource.acquire();
    // 1. listener实际指向Engine;
    // 2. 对模块<2.2> ~ <2.8>的分析可知, onEngineJobComplete内部完成了Bitmap从弱引用缓存到内存缓存的转变;
    listener.onEngineJobComplete(this, key, engineResource);

    int size = cbs.size();
    for (int i = 0; i < size; i++) {
        ResourceCallback cb = cbs.get(i);
        if (!isInIgnoredCallbacks(cb)) {
            engineResource.acquire();
            // 将数据显示在ImageView中;
            cb.onResourceReady(engineResource, dataSource);
        }
    }
    // 图片展示之前, EngineResource引用计数+1操作, 加载完成之后-1操作;
    engineResource.release();
    release(false);
}
2.2 Engine.onEngineJobComplete
@SuppressWarnings("unchecked")
@Overridepublic void onEngineJobComplete(EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
    if (resource != null) {
        // 这里通过setResourceListener触发EngineResource持有Engine的引用, 与模块<2.1>_//2--->对应;
        resource.setResourceListener(key, this);
        if (resource.isCacheable()) {
            // 如果EngineResource支持缓存;模块<2.3>
            activeResources.activate(key, resource);
        }
    }
    jobs.removeIfCurrent(key, engineJob);
}
2.3 ActiveResources.activate:
@VisibleForTesting
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();

void activate(Key key, EngineResource<?> resource) {
   /**
     * 构造一个弱引用对象, 通过getReferenceQueue()传入一个与弱引用关联的队列;模块<2.4>
     * 将EngineResource被弱引用持有, 这样就可以知道EngineResource何时被gc回收, 然后就可以将
     * 不再使用的EngineResource放入到LruCache中;
     */
    ResourceWeakReference toPut =
        new ResourceWeakReference(
            key,
            resource,
            // 模块<2.5>
            getReferenceQueue(),
            isActiveResourceRetentionAllowed);
    /**
     * 一个key对应一个ResourceWeakReference;
     */
    ResourceWeakReference removed = activeEngineResources.put(key, toPut);
}
2.4 ResourceWeakReference:
@VisibleForTesting
  static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
    final Key key;
    final boolean isCacheable;

    Resource<?> resource;

    ResourceWeakReference(
        Key key,
        EngineResource<?> referent,
        ReferenceQueue<? super EngineResource<?>> queue...) {
        /**
         * 其实Glide也无法预测到弱引用何时被gc回收, 但是Glide在构建ReferenceQueue时创建了一个线程,
         * 然后在后台一直轮询查询该ReferenceQueue内是否存在EngineResource, 如果存在, 则说明该
         * EngineResource被gc回收, 然后就把该EngineResource放入到LruCache中, 进行内存缓存;
         */
        super(referent, queue);
        ...
        /**
         * 这里的Resource当做Bitmap来理解, 一个Bitmap对应一个EngineResource, 通过构造函数将
         * ResourceWeakReference内部的Resource指向EngineResource, 为下文弱引用缓存转为强引
         * 用缓存做铺垫;   
         */
        this.resource = Preconditions.checkNotNull(r.getResource());
        this.key = key;
    }
}
  • 1、如果一个对象只具有软引用, 则内存空间足够, 垃圾回收器就不会回收它, 如果内存空间不足了, 就会回收这些对象的内存.
  • 2、软引用可以和一个引用队列(ReferenceQueue)联合使用, 如果软引用所引用的对象被垃圾回收器回收, java虚拟机就会把这个软引用加入到与之关联的引用队列中;
2.5 ActiveResources.getReferenceQueue:
private ReferenceQueue<EngineResource<?>> getReferenceQueue() {
    if (resourceReferenceQueue == null) {
        resourceReferenceQueue = new ReferenceQueue<>();
        cleanReferenceQueueThread = new Thread(new Runnable() {
            @Override
            public void run() {
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                ResourceWeakReference ref;
                while (!isShutdown) {
                    /**
                     * 这里采用的是生产者-消费者模式, 如果resourceReferenceQueue为空, 则当前线程
                     * 会被挂起, 直到外界线程显示的唤醒, 这里唤醒的条件就是当被弱引用持有的
                     * EngineResource被gc回收时, ResourceWeakReference被放入到resourceReferenceQueue
                     * 中, 当前线程被唤醒, 然后从resourceReferenceQueue中读取被gc回收的
                     * ResourceWeakReference, 有模块<2.4>可知此处的ResourceWeakReference实际
                     * 持有被回收的EngineResource的Bitmap的实例, 然后通过handler方式发送到主线程中;
                     */ 
                    ref = (ResourceWeakReference) resourceReferenceQueue.remove();
                    mainHandler.obtainMessage(MSG_CLEAN_REF, ref).sendToTarget();
                    DequeuedResourceCallback current = cb;
                    if (current != null) {
                        current.onResourceDequeued();
                    }
                }
            }
        }, "glide-active-resources");
        cleanReferenceQueueThread.start();
    }
    return resourceReferenceQueue;
}

private final Handler mainHandler = new Handler(Looper.getMainLooper(), new Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        if (msg.what == MSG_CLEAN_REF) {
            /**
             * 处理被gc回收的EngineResource;
             */
            cleanupActiveReference((ResourceWeakReference) msg.obj);     模块<2.6>
            return true;
        }
        return false;
    }
});
2.6 ActiveResources.cleanupActiveReference:
private void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
    /**
     * 既然已经被gc回收, 弱引用.get() = null, 占了个空位无意义了, 从弱引用集合中删除该key对应的弱引用;
     */
    activeEngineResources.remove(ref.key);
    /**
     * 结合模块<2.1>可知, ref.resource实际指向BitmapDrawable, isCacheable默认为true, 
     * Glide的思路是先将EngineResource进行弱引用缓存, 如果该弱引用被gc回收, 则将其内部的
     * 的Resource即BitmapDrawable进行缓存, 如果ref.resource == null, 也就失去了缓存的意义, 直接返回;
     */
    if (!ref.isCacheable || ref.resource == null) {
        return;
    }
    /**
     * 如果EngineResource被gc回收, 且其内部的resource还存在, 则重新构建EngineResource, 并进行缓存;
     */
    EngineResource<?> newResource = new EngineResource<>(ref.resource, true, false);
    /**
     * 1. 构建一个新的EngineResource, 持有listener的引用;
     * 2. 然后将EngineResource缓存在Engine.cache中;模块<2.7>
     */
    newResource.setResourceListener(ref.key, listener);
    listener.onResourceReleased(ref.key, newResource);
}
2.7 Engine.onResourceReleased:
@Override
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    // 1. 将key从ActiveResources中移除;
    // 2. ActiveResources充当了一个什么角色?弱引用缓存的载体;
    activeResources.deactivate(cacheKey);
    if (resource.isCacheable()) {
        // 1. 在GlideBuilder中初始化Engine时传入memoryCache指向LruResourceCache;模块<2.8>
        // 2. 通过cache.put完成内存缓存;
        cache.put(cacheKey, resource);
    } else {
        resourceRecycler.recycle(resource);
    }
}
2.8 LruResourceCache.put:
// LruCache通过内部维持的LinkedHashMap对内存缓存完成LRU机制;
public class LruResourceCache extends LruCache<Key>;
public class LruCache<T, Y> {
    private final LinkedHashMap<T, Y> cache = new LinkedHashMap<>(100, 0.75f, true);
}
2.9 EngineResource.release:
void release() {
    // 结合模块<2.1> ~ <2.8>其实也可以看出EngineResource充当了一个载体, 当数据被加载到ImageView之前,
    // EngineResource执行++acquired操作, 表明此时正在执行数据显示的操作, 当数据显示操作完成以后,
    // 执行--acquired操作, 当acquired = 0时, 则说明所有的数据显示操作均已完成, 此时再将EngineResource
    // 缓存在LruResourceCache中;
    if (--acquired == 0) {
        listener.onResourceReleased(key, this);
    }
}
2.10 Glide为何要引用两级内存缓存?
  • 1、Glide通过LruResourceCache完成内存缓存, 依靠LRU机制, 所以就有一个弊端, 当缓存不足时, 会删除最近最少使用的Bitmap, 而假如此时这个Bitmap正在用于ImageView显示呢? 如果被复用或者被移除了岂不是GG了?
  • 2、针对这个问题, Glide采用弱引用缓存 + 引用计数的方式. 当系统不在使用该EngineResource时, gc时就会回收该EngineResource, 既然EngineResource不再被引用, 则其内部的Bitmap放到LinkedHashMap队列中, 执行LRU时就不会出现什么问题;
  • 3、如果弱引用EngineResource没有被gc回收, 则当EngineResource用于ImageView显示时, 执行acquire++操作, 当ImageView显示结束时, 执行acquire--操作, 当acquire = 0时, 则表明EngineResource没有被任何ImageView使用, 此时手动触发EngineResource从弱引用缓存中移除, 然后强引用存在于LruResourceCache中;
  • 4、关于第三条其实是因为同一个EngineResource可能被多个ImageView所使用, 结合下文模块<3.2>;

三、从缓存中读取数据:

  • 模块<二>基本上是分析了第一次从网络获取数据之后是如何进行缓存的, 接下来分析如果存在缓存, 则如何使用这些缓存;
3.1 Engine.load:
public <R> LoadStatus load(...) {

    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);
    // 从弱引用缓存队列中获取EngineResource;模块<3.2>
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
        // 如果存在则使用该EngineResource进行显示;
        cb.onResourceReady(active, DataSource.MEMORY_CACHE);
        return null;
    }
    // 1. 如果不存在弱引用, 则尝试从内存中获取EngineResource;模块<3.3>
    // 2. 如果LRU中存在, 则将该EngineResource从Lru中移除, 然后添加至activeResources弱引用缓存队列中;
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
        cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
        return null;
    }
  }
3.2 Engine.loadFromActiveResources:
@Nullable
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
        return null;
    }
    /**
     * 从这里可以看出, 一个EngineResource可以被多个ImageView所使用, 所以可以通过引用计数的方式判断
     * EngineResource被多少个ImageView所引用;
     */
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
        // 如果存在, 则其引用计数+1操作;
        active.acquire();
    }
    return active;
}
3.3 Engine.loadFromCache:
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
        return null;
    }
    // 从LruResourceCache中移除该Key-EngineResouce对;
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
        cached.acquire();
        // 将该EngineResource添加至activeResources内的弱引用缓存队列中;
        activeResources.activate(key, cached);
    }
    return cached;
}

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