〔两行哥〕提纲挈领,带你梳理Glide主要源码逻辑(四)

前文分析了Glide的with()、load()以及into()方法中的大部分逻辑,今天我们来分析Glide的核心图片加载逻辑。因为Glide的封装程度比较高,之前三篇文章分析完毕其外围逻辑(参阅:〔两行哥〕提纲挈领,带你梳理Glide主要源码逻辑(一)〔两行哥〕提纲挈领,带你梳理Glide主要源码逻辑(二)〔两行哥〕提纲挈领,带你梳理Glide主要源码逻辑(三))。
那么Glide图片加载的核心逻辑在哪儿呢?就是上文结尾所说的engine.load()方法,在engine.load()方法中传入了上文已经分析过的DataFetcher实例、LoadProvider实例以及ResourceTransCoder实例。让我们进入源码分析看看。

Engine.java
    /**
     * Starts a load for the given arguments. Must be called on the main thread.
     *
     * <p>
     *     The flow for any request is as follows:
     *     <ul>
     *         <li>Check the memory cache and provide the cached resource if present</li>
     *         <li>Check the current set of actively used resources and return the active resource if present</li>
     *         <li>Check the current set of in progress loads and add the cb to the in progress load if present</li>
     *         <li>Start a new load</li>
     *     </ul>
     * </p>
     *
     * <p>
     *     Active resources are those that have been provided to at least one request and have not yet been released.
     *     Once all consumers of a resource have released that resource, the resource then goes to cache. If the
     *     resource is ever returned to a new consumer from cache, it is re-added to the active resources. If the
     *     resource is evicted from the cache, its resources are recycled and re-used if possible and the resource is
     *     discarded. There is no strict requirement that consumers release their resources so active resources are
     *     held weakly.
     * </p>
     *
     * @param signature A non-null unique key to be mixed into the cache key that identifies the version of the data to
     *                  be loaded.
     * @param width The target width in pixels of the desired resource.
     * @param height The target height in pixels of the desired resource.
     * @param fetcher The fetcher to use to retrieve data not in the disk cache.
     * @param loadProvider The load provider containing various encoders and decoders use to decode and encode data.
     * @param transformation The transformation to use to transform the decoded resource.
     * @param transcoder The transcoder to use to transcode the decoded and transformed resource.
     * @param priority The priority with which the request should run.
     * @param isMemoryCacheable True if the transcoded resource can be cached in memory.
     * @param diskCacheStrategy The strategy to use that determines what type of data, if any,
     *                          will be cached in the local disk cache.
     * @param cb The callback that will be called when the load completes.
     *
     * @param <T> The type of data the resource will be decoded from.
     * @param <Z> The type of the resource that will be decoded.
     * @param <R> The type of the resource that will be transcoded from the decoded resource.
     */
    public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
            DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
            Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
        Util.assertMainThread();
        long startTime = LogTime.getLogTime();

        final String id = fetcher.getId();
        EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
                loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
                transcoder, loadProvider.getSourceEncoder());

        EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
        if (cached != null) {
            cb.onResourceReady(cached);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Loaded resource from cache", startTime, key);
            }
            return null;
        }

        EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
        if (active != null) {
            cb.onResourceReady(active);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Loaded resource from active resources", startTime, key);
            }
            return null;
        }

        EngineJob current = jobs.get(key);
        if (current != null) {
            current.addCallback(cb);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Added to existing load", startTime, key);
            }
            return new LoadStatus(cb, current);
        }

        EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
        DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
                transcoder, diskCacheProvider, diskCacheStrategy, priority);
        EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
        jobs.put(key, engineJob);
        engineJob.addCallback(cb);
        engineJob.start(runnable);

        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Started new load", startTime, key);
        }
        return new LoadStatus(cb, engineJob);
    }

    private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {
            return null;
        }

        EngineResource<?> active = null;
        WeakReference<EngineResource<?>> activeRef = activeResources.get(key);
        if (activeRef != null) {
            active = activeRef.get();
            if (active != null) {
                active.acquire();
            } else {
                activeResources.remove(key);
            }
        }

        return active;
    }

    private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {
            return null;
        }

        EngineResource<?> cached = getEngineResourceFromCache(key);
        if (cached != null) {
            cached.acquire();
            activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
        }
        return cached;
    }

首先先做一下知识的铺垫。我们知道Glide中有三级缓存机制,拥有内存缓存和硬盘缓存。而内存缓存同时采用了两种机制,一种是前文讲过的LruCache算法机制(什么是LruCache算法?请参阅前文),另外一种就是弱引用机制。缓存的作用是什么呢?内存缓存的作用就是防止把图片重复加载到内存中,而硬盘缓存的作用就是防止重复从网络中获取图片。

一、内存缓存

让我们回到load()方法中。首先调用了fetcher.getId()获得了一个唯一标识Id。这个Id是图片加载的唯一标识Id(如果是网络图片加载请求,那么其实就是图片的Url)。接着通过EngineKey的构建者模式,传入了获取到的Id、Width、Height、transformation等参数,构建了另外一个标识Key。这个Key是什么呢?上文讲到Glide的内存缓存,这个Key就是内存缓存的唯一标识Key,与传入的Id、Width、Height、transformation参数相关。为什么要与这么多参数相关呢?大家想一下,需要加载的图片只有与内存缓存中的某图片Url、宽高、解码方式等一致,才可以直接取用内存缓存。如果需要加载的图片改变了Url或者宽高或者解码方式,那么原有旧的内存缓存就不能使用了,需要重新缓存。那么在EngineKey的构建方法中传入如此多的参数也可以理解了。
接着往下看。上文说过Glide的内存缓存同时使用了两种缓存机制,一种是LruCache缓存机制,一种是弱引用缓存机制,让我们一一分析。首先,load()方法内部调用了loadFromCache()方法,源码如下:

Engine.java
    private final MemoryCache cache;
    private final Map<Key, WeakReference<EngineResource<?>>> activeResources;

    private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {
            return null;
        }

        EngineResource<?> cached = getEngineResourceFromCache(key);
        if (cached != null) {
            cached.acquire();
            activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
        }
        return cached;
    }

    @SuppressWarnings("unchecked")
    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 /*isCacheable*/);
        }
        return result;
    }

可以看到逻辑是从cache中获取到缓存(cache中存储的是曾经使用过,而当前没有在使用中的缓存。通过调用cache.remove(key)方法删除缓存并返回被删除的缓存),如果获取到的缓存result不为null,调用 activeResources.put()方法将此缓存放入激活缓存Map中(正在使用中的缓存)。而这里的cache为实现了MemoryCache接口的LruResourceCache类的实例。我们稍微看一下LruResourceCache类的源码:

LruResourceCache.java
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {
    private ResourceRemovedListener listener;

    /**
     * Constructor for LruResourceCache.
     *
     * @param size The maximum size in bytes the in memory cache can use.
     */
    public LruResourceCache(int size) {
        super(size);
    }

    @Override
    public void setResourceRemovedListener(ResourceRemovedListener listener) {
        this.listener = listener;
    }

    @Override
    protected void onItemEvicted(Key key, Resource<?> item) {
        if (listener != null) {
            listener.onResourceRemoved(item);
        }
    }

    @Override
    protected int getSize(Resource<?> item) {
        return item.getSize();
    }

    @SuppressLint("InlinedApi")
    @Override
    public void trimMemory(int level) {
        if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
            // Nearing middle of list of cached background apps
            // Evict our entire bitmap cache
            clearMemory();
        } else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Entering list of cached background apps
            // Evict oldest half of our bitmap cache
            trimToSize(getCurrentSize() / 2);
        }
    }
}

留意一下isMemoryCacheable变量,这个布尔值是在配置Glide参数的时候设置的,决定了Glide是否使用内存缓存。如果为false,这里获取内存缓存的方法就会返回false。LruResourceCache类就不再赘述,之前的文章就已经分析过LruCache,只要知道cache是基于LruCache算法,内部是基于LinkedHashMap实现的。
回到load()方法中,如果loadFromCache()方法返回的cached对象不为null,则执行onResourceReady()回调。如果获取到的cached为null(没有从LruCache中的LinkedHashMap中获取到缓存),则调用loadFromActiveResources()方法,再去激活缓存的Map中(使用中的缓存)去寻找是否有缓存,看看内部源码的逻辑:

Engine.java
    private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
    private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {
            return null;
        }

        EngineResource<?> active = null;
        WeakReference<EngineResource<?>> activeRef = activeResources.get(key);
        if (activeRef != null) {
            active = activeRef.get();
            if (active != null) {
                active.acquire();
            } else {
                activeResources.remove(key);
            }
        }

        return active;
    }

activeResources是持有了缓存弱引用的HashMap,而这里的逻辑主要是从activeResources这个HashMap中获取到图片的缓存。如果调用loadFromActiveResources()方法返回的缓存不为null,同样执行onResourceReady()回调方法,如果返回的缓存为null,则进入下一步,从磁盘中或者网络去获取图片。

二、磁盘缓存及图片获取

Glide磁盘缓存与内存缓存类似,也采用了LruCache算法。首先让我们追踪一下Glide读取磁盘缓存的逻辑。
在读取内存缓存之后,如果没有获取到内存缓存,Glide就会去读取磁盘缓存,如下代码:

EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(runnable);

首先创建了一个EngineJob并放入jobs集合,然后engineJob开始执行传入的EngineRunnable任务。本地磁盘缓存的逻辑就隐藏在EngineRunnable中,让我们看看这个Runnable对象中覆写的run()方法:

EngineRunnable.java
    @Override
    public void run() {
        if (isCancelled) {
            return;
        }

        Exception exception = null;
        Resource<?> resource = null;
        try {
            resource = decode();
        } catch (OutOfMemoryError e) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "Out Of Memory Error decoding", e);
            }
            exception = new ErrorWrappingGlideException(e);
        } catch (Exception e) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "Exception decoding", e);
            }
            exception = e;
        }

        if (isCancelled) {
            if (resource != null) {
                resource.recycle();
            }
            return;
        }

        if (resource == null) {
            onLoadFailed(exception);
        } else {
            onLoadComplete(resource);
        }
    }

可以看到run()方法中执行了decode()方法,并在执行结束后根据执行的结果,调用了onLoadFailed()或者onLoadComplete()回调。再让我们看看decode()方法到底做了哪些工作。

EngineRunnable.java
    private Resource<?> decode() throws Exception {
        ...
        private boolean isDecodingFromCache() {
            return stage == Stage.CACHE;
        }
        ...
        if (isDecodingFromCache()) {
            return decodeFromCache();
        } else {
            return decodeFromSource();
        }
        ...
    }

decode()方法根据isDecodingFromCache()方法的返回值(这个参数在初始化Glile的时候进行过配置,是否使用磁盘缓存),分别调用decodeFromCache()和decodeFromSource()。磁盘缓存的逻辑终于被找到了,就在decodeFromCache()中!

EngineRunnable.java
    private Resource<?> decodeFromCache() throws Exception {
        Resource<?> result = null;
        try {
            result = decodeJob.decodeResultFromCache();
        } catch (Exception e) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "Exception decoding result from cache: " + e);
            }
        }

        if (result == null) {
            result = decodeJob.decodeSourceFromCache();
        }
        return result;
    }

可以看到decodeFromCache()方法中执行了decodeResultFromCache()或decodeSourceFromCache()方法。首先调用decodeResultFromCache()方法获取缓存,如果获取不到,就调用decodeSourceFromCache()方法获取缓存。至于什么时候执行第一个方法,什么时候又会执行到第二个方法,与我们在第一篇中讲的磁盘缓存配置策略有关:

diskCacheStrategy(DiskCacheStrategy.RESULT)//缓存处理后的图像(如尺寸调整、裁剪后的图像)
diskCacheStrategy(DiskCacheStrategy.SOURCE)//缓存原尺寸的图像
diskCacheStrategy(DiskCacheStrategy.ALL)//缓存所有图像

见名知意,两行哥就不再深入讲解了,让我们看一下源码:

DecodeJob.java
     public Resource<Z> decodeResultFromCache() throws Exception {
        if (!diskCacheStrategy.cacheResult()) {
            return null;
        }

        long startTime = LogTime.getLogTime();
        Resource<T> transformed = loadFromCache(resultKey);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Decoded transformed from cache", startTime);
        }
        startTime = LogTime.getLogTime();
        Resource<Z> result = transcode(transformed);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Transcoded transformed from cache", startTime);
        }
        return result;
    }

    public Resource<Z> decodeSourceFromCache() throws Exception {
        if (!diskCacheStrategy.cacheSource()) {
            return null;
        }

        long startTime = LogTime.getLogTime();
        Resource<T> decoded = loadFromCache(resultKey.getOriginalKey());
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Decoded source from cache", startTime);
        }
        return transformEncodeAndTranscode(decoded);
    }

可以看到,它们内部都调用了loadFromCache()方法从缓存当中获取数据,如果是decodeResultFromCache()方法就直接将数据解码并返回,如果是decodeSourceFromCache()方法,还要调用一下transformEncodeAndTranscode()方法转换数据后再解码并返回。
再者,它们调用loadFromCache()方法时传入的参数却不一样,一个传入的是resultKey,另外一个却又调用了resultKey的getOriginalKey()方法,分别对应处理后图片缓存Key和原图缓存Key。上文讲过,Glide的缓存Key是由多个参数共同组成的,包括图片的width、height等。但如果我们如果缓存的原始图片,其实并不需要这么多的参数,因为并没有对原始图片进行过尺寸缩放等额外处理。那么我们来看一下getOriginalKey()方法的源码:

OriginalKey.java
    public Key getOriginalKey() {
        if (originalKey == null) {
            originalKey = new OriginalKey(id, signature);
        }
        return originalKey;
    }

可以看到,这里忽略了大部分的参数,只使用了id和signature两个参数来构成Key,而signature参数大多数情况下都是用不到的,因此几乎是由id(也就是图片url)来决定OriginalKey。
接下来让我们回到loadFromCache()方法,看看内部的源码:

DecodeJob.java
    private Resource<T> loadFromCache(Key key) throws IOException {
        File cacheFile = diskCacheProvider.getDiskCache().get(key);
        if (cacheFile == null) {
            return null;
        }

        Resource<T> result = null;
        try {
            result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
        } finally {
            if (result == null) {
                diskCacheProvider.getDiskCache().delete(key);
            }
        }
        return result;
    }

loadFromCache()方法调用getDiskCache()方法获取到的就是Glide自己编写的DiskLruCache工具类的实例,然后调用它的get()方法并把缓存key传入,得到硬盘缓存的文件。如果文件为null就返回null,如果文件不为空则将它解码成Resource对象后返回。
让我们重新回到decode()方法,看看方法中另一个else分支decodeFromSource()方法执行了什么逻辑:
DecodeJob.java

    public Resource<Z> decodeFromSource() throws Exception {
        Resource<T> decoded = decodeSource();
        return transformEncodeAndTranscode(decoded);
    }

    private Resource<T> decodeSource() throws Exception {
        Resource<T> decoded = null;
        try {
            long startTime = LogTime.getLogTime();
            final A data = fetcher.loadData(priority);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Fetched data", startTime);
            }
            if (isCancelled) {
                return null;
            }
            decoded = decodeFromSourceData(data);
        } finally {
            fetcher.cleanup();
        }
        return decoded;
    }

    private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
        long startTime = LogTime.getLogTime();
        Resource<T> transformed = transform(decoded);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Transformed resource from source", startTime);
        }

        writeTransformedToCache(transformed);

        startTime = LogTime.getLogTime();
        Resource<Z> result = transcode(transformed);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Transcoded transformed from source", startTime);
        }
        return result;
    }

decodeFromSource()中只有两行代码,decodeSource()顾名思义是用来解析原图片的,而transformEncodeAndTranscode()则是用来对图片进行转换和转码的。decodeSource()方法中主要做了如下逻辑:
1.通过 fetcher.loadData(priority)方法来获取到图片数据,比如从网络获取图片资源;
2.通过decodeFromSourceData(data)方法来解码数据:
3.decodeFromSourceData()方法中,判断配置的缓存策略是否支持磁盘缓存,如果支持的话,则调用cacheAndDecodeSourceData()方法来缓存数据及解码。Glide提供了DiskCacheProvider类,其本质是封装后的DiskLruCache实例。
相关代码如下:
HttpUrlFetcher.java

    @Override
    public InputStream loadData(Priority priority) throws Exception {
        return loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/, glideUrl.getHeaders());
    }

    private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers)
            throws IOException {
        if (redirects >= MAXIMUM_REDIRECTS) {
            throw new IOException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
        } else {
            // Comparing the URLs using .equals performs additional network I/O and is generally broken.
            // See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
            try {
                if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
                    throw new IOException("In re-direct loop");
                }
            } catch (URISyntaxException e) {
                // Do nothing, this is best effort.
            }
        }
        urlConnection = connectionFactory.build(url);
        for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
          urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
        }
        urlConnection.setConnectTimeout(2500);
        urlConnection.setReadTimeout(2500);
        urlConnection.setUseCaches(false);
        urlConnection.setDoInput(true);

        // Connect explicitly to avoid errors in decoders if connection fails.
        urlConnection.connect();
        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");
            if (TextUtils.isEmpty(redirectUrlString)) {
                throw new IOException("Received empty or null redirect url");
            }
            URL redirectUrl = new URL(url, redirectUrlString);
            return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
        } else {
            if (statusCode == -1) {
                throw new IOException("Unable to retrieve response code from HttpUrlConnection.");
            }
            throw new IOException("Request failed " + statusCode + ": " + urlConnection.getResponseMessage());
        }
    }

DecodeJob.java

    private Resource<T> decodeFromSourceData(A data) throws IOException {
        final Resource<T> decoded;
        if (diskCacheStrategy.cacheSource()) {
            decoded = cacheAndDecodeSourceData(data);
        } else {
            long startTime = LogTime.getLogTime();
            decoded = loadProvider.getSourceDecoder().decode(data, width, height);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Decoded from source", startTime);
            }
        }
        return decoded;
    }

    private Resource<T> cacheAndDecodeSourceData(A data) throws IOException {
        long startTime = LogTime.getLogTime();
        SourceWriter<A> writer = new SourceWriter<A>(loadProvider.getSourceEncoder(), data);
        diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Wrote source to cache", startTime);
        }

        startTime = LogTime.getLogTime();
        Resource<T> result = loadFromCache(resultKey.getOriginalKey());
        if (Log.isLoggable(TAG, Log.VERBOSE) && result != null) {
            logWithTimeAndKey("Decoded source from cache", startTime);
        }
        return result;
    }

注意,diskCacheProvider调用了getDiskCache()方法获取DiskLruCache实例,接着调用它的put()方法写入硬盘缓存,这里原始图片的缓存key用的是resultKey.getOriginalKey(),最后让我们再看一下decodeFromSource()方法中第二行transformEncodeAndTranscode(decoded)方法内部做了哪些逻辑:
DecodeJob.java

    private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
        long startTime = LogTime.getLogTime();
        Resource<T> transformed = transform(decoded);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Transformed resource from source", startTime);
        }

        writeTransformedToCache(transformed);

        startTime = LogTime.getLogTime();
        Resource<Z> result = transcode(transformed);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Transcoded transformed from source", startTime);
        }
        return result;
    }

    private void writeTransformedToCache(Resource<T> transformed) {
        if (transformed == null || !diskCacheStrategy.cacheResult()) {
            return;
        }
        long startTime = LogTime.getLogTime();
        SourceWriter<Resource<T>> writer = new SourceWriter<Resource<T>>(loadProvider.getEncoder(), transformed);
        diskCacheProvider.getDiskCache().put(resultKey, writer);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Wrote transformed from source to cache", startTime);
        }
    }

transformEncodeAndTranscode()方法首先调用transform()方法来对图片进行转换,然后在writeTransformedToCache()方法中将转换过后的图片写入到硬盘缓存中,调用的同样是DiskLruCache实例的put()方法,不过这里用的缓存key是resultKey。

至此,我们已经把Glide内部主要逻辑分析完毕,可以说Glide是Android现有开源框架出色封装的典范,封装后用户完全感觉不到内部复杂的逻辑,仅仅通过一行链式调用就完成了很多逻辑。不得不惊叹作者的高超代码艺术。让我们下次再会。

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

推荐阅读更多精彩内容