Glide篇_01关于Bitmap缓存

参考文章:

  • Glide源码分析之缓存处理
  • Glide缓存机制

理解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 {
    @Override
    public void loadData(Priority priority, DataCallback 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 headers) throws IOException {

    urlConnection = connectionFactory.build(url);
    for (Map.Entry 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:
  • 中间省略一万行代码, 直接跳到解码这里, 详细流程参考(https://blog.csdn.net/guiman/article/details/73217102);
public final class Downsampler {
    @SuppressWarnings({"resource", "deprecation"})
    public Resource 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, 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 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> {
    final Key key;
    final boolean isCacheable;

    Resource resource;

    ResourceWeakReference(
        Key key,
        EngineResource referent,
        ReferenceQueue> 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> 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;
public class LruCache {
    private final LinkedHashMap 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  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);
    ...
}

你可能感兴趣的:(Glide篇_01关于Bitmap缓存)