美文网首页
Glide->02Bitmap复用

Glide->02Bitmap复用

作者: 冉桓彬 | 来源:发表于2018-05-09 20:23 被阅读19次

参考文章:

一、源码解析:

  • 如果是第一次加载图片, 即不存在缓存的情况下, 最终在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;
        /**
         * 从网络获取流数据;模块<1.2>
         * 从模块<1.2>可知, 从网络获取流数据是HttpURLConnection的方式;
         */
        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)
      throws IOException {
    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缓存:

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;
     */
//1--->
    engineResource.acquire();
    /**
     * 1. listener实际指向Engine;模块<2.2>
     * 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);
        }
    }
//2--->
    /**
     * 图片加载时, EngineResource引用计数+1操作, 加载完成之后-1操作;模块<2.9>
     */
    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);
    ...
}

相关文章

  • Glide->02Bitmap复用

    参考文章: Glide源码分析之缓存处理 Glide缓存机制 一、源码解析: 如果是第一次加载图片, 即不存在缓存...

  • 复用

    从复用角度讲,设计模式是代码级复用、框架是模块级复用、架构是系统级复用、平台是企业应用级复用。

  • maven module 和 微服务

    代码复用分为:代码模块(module)复用和项目(微服务)复用。 module 划分规范合理,能够使代码复用,是从...

  • 信道复用技术(时分复用,频分复用,波分复用,码分复用)

    概念 由于一条传输线路的能力远远超过传输一个用户信号所需的能力,为了提高线路利用率,经常让多个信号同时共用一条物理...

  • ListView适配器的简单用法,优化

    优化:1:ConvertView的复用2:findViewById()的复用 1:ConvertView的复用 2...

  • IO模式(未完待续。。。)

    多路复用 Redis 线程中经常提到 IO 多路复用 多路指的是多个 Socket 连接,复用指的是复用一个线程。...

  • 设计模式汇总

    一、软件设计的目标:复用 这里的复用指的是编译单元的复用,而不仅仅是代码的复用。 二、面向对象设计 变化是复用的天...

  • iOS 开发随记

    1、TableViewCell 复用 复用 复用的原则就是样式相同,包括cell的accessoryView或a...

  • 【golang】重视内存复用

    sync.pool的内存复用,常用于结构体对象复用 切片的内存复用

  • 2019-04-02

    频分复用复用的意思将多个信息信号合在一起,当做一个来传输频分复用(FDM)时分复用(TDM)码分复用(CDM) 频...

网友评论

      本文标题:Glide->02Bitmap复用

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