Glide源码分析4 -- 缓存,编解码和网络请求

1. 概述和核心类

在Glide源码分析 – request创建与发送过程一文中,我们谈到request最终通过GenericRequest的onSizeReady()方法进行,其中调用了engine.load()方法去实际获取数据。本文主要讲述engine.load()之后发生的那些事,让大家能够对底层数据获取有个更清晰的认识。从这点也可以看出Glide设计分层的精妙。主要涉及的核心类如下

1)GenericRequest:定义了很多对request的处理方法,我们比较关心的是request的发送,它的入口是begin(),会调用到onSizeReady(),最终调用到engine.load(),也就是数据获取部分的入口
2)Engine:封装了数据获取的很多关键方法,向request层提供这些API,比如load(), release(), clearDiskCache()等方法。可以认为是一个外观模式。
3)MemoryCache:内存缓存类,先从缓存中获取数据,如果没有才做后面的工作。这是第一级缓存。Glide采用了两级缓存模式。第二级缓存为DiskLruCache,为磁盘缓存。获取磁盘缓存比较耗时,需要在子线程中进行,故而在DecodeJob中得到调用。此处不会调用磁盘缓存。
4)EngineJob, EngineRunnable:EngineJob是一个控制类,作为EngineRunnable的管理者,提供start(), cancel()等很多操作runnable的方法。一般会以线程池的方式向子线程提交EngineRunnable任务。而EngineRunnable就是我们在子线程中需要执行的任务,也是特别关键的一个类。
5)DecodeJob,DataFetcher,ResourceDecoder,Transformation:DecodeJob流程为从缓存或网络或本地获取数据,然后转码为所需的格式,最后编码并保存到DiskLruCache中。这是数据获取阶段很关键的一个类。DataFetcher负责根据不同途径数据获取(如本地File,url,URI等),ResourceDecoder负责根据不同文件格式解码(如Bitmap,GIF等)Transformation负责编码为不同格式文件(如Bitmap,GIF等)。后面在类层次关系中会详细讲解这几个类的关系以及它们的子类。现在只需要知道这几个类的作用就可以了。

上面讲解了五个方面,大概十多个类。我们可以把整个过程分为两个阶段:任务提交阶段和任务执行阶段。为了更清晰的理清逻辑关系,可以看下面这张图。

任务提交阶段:

任务执行阶段

2. 任务提交阶段源码分析

1)GenericRequest的begin()方法是整个提交阶段的入口,它会调用engine来完成任务的提交,并回调一些listener。这些listener是我们经常使用的,从下面的源码中我们可以清晰的看见这些listener的调用时机。

    public void begin() {
        startTime = LogTime.getLogTime();
        if (model == null) {
            // loadModel为空时,会回调requestListener的onException()
            onException(null);
            return;
        }

        status = Status.WAITING_FOR_SIZE;
        if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
            // size验证通过后,会提交请求
            onSizeReady(overrideWidth, overrideHeight);
        } else {
            target.getSize(this);
        }

        if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
            // onLoadStarted回调时机,任务提交最开始的时候
            target.onLoadStarted(getPlaceholderDrawable());
        }
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logV("finished run method in " + LogTime.getElapsedMillis(startTime));
        }
    }

    public void onSizeReady(int width, int height) {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
        }
        if (status != Status.WAITING_FOR_SIZE) {
            return;
        }
        status = Status.RUNNING;

        // 计算尺寸
        width = Math.round(sizeMultiplier * width);
        height = Math.round(sizeMultiplier * height);

        ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();
        // 获取DataFetcher,我们可以自定义DataFetcher
        final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);

        if (dataFetcher == null) {
            // 会回调requestListener的onException()
            onException(new Exception("Failed to load model: \'" + model + "\'"));
            return;
        }
        ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
        }
        // 默认使用内存缓存
        loadedFromMemoryCache = true;
        // 进入Engine的入口,十分关键
        loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
                priority, isMemoryCacheable, diskCacheStrategy, this);
        loadedFromMemoryCache = resource != null;
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
        }
    }

2)engine.load(), 先尝试从内存缓存获取数据,再尝试从当前活跃Resources中获取数据,再看看这个任务是否当前已经提交过了。这些都没有的话,最后提交任务。它规范了整个任务提交的流程,可以看做是一个模板方法。

    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) {
            // 获取数据成功,会回调target的onResourceReady()
            cb.onResourceReady(cached);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Loaded resource from cache", startTime, key);
            }
            return null;
        }

        // 在尝试从活动Resources map中获取,它表示的是当前正在使用的Resources
        // 它也是在内存中,与内存缓存不同之处是clear缓存时不会clear它。
        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;
        }

        // 然后看看当前jobs中是否包含这个任务了,如果包含说明任务之前已经提交了,正在执行
        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);
       // runnable是关键,它是任务执行阶段的入口
        EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
        jobs.put(key, engineJob);
        engineJob.addCallback(cb);
        // 开始提交job
        engineJob.start(runnable);

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

3)engineJob.start(runnable),提交任务到线程池

class EngineJob implements EngineRunnable.EngineRunnableManager {
    public void start(EngineRunnable engineRunnable) {
        this.engineRunnable = engineRunnable;
        // 提交任务,diskCacheService默认是一个AbstractExecutorService
        future = diskCacheService.submit(engineRunnable);
    }
}

public abstract class AbstractExecutorService implements ExecutorService {
    public Future submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture ftask = newTaskFor(task, null);
        // 线程池中执行子线程的任务,子线程得到调用后任务就可以执行了
        execute(ftask);
        return ftask;
    }
}

3. 任务执行阶段源码分析

1)EngineRunnable的run(),它是任务执行的入口

class EngineRunnable implements Runnable, Prioritized {
    public void run() {
        if (isCancelled) {
            return;
        }

        Exception exception = null;
        Resource> resource = null;
        try {
            // 使用DecodeJob来完成数据的获取,编解码等
            resource = decode();
        } catch (Exception e) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "Exception decoding", e);
            }
            exception = e;
        }

        if (isCancelled) {
            if (resource != null) {
                // 取消则回收各种资源防止内存泄露,此处的子类一般采用对象池方式来回收,防止反复的创建和回收。如BitmapPool
                resource.recycle();
            }
            return;
        }

        if (resource == null) {
            // 任务执行最终失败则回调onLoadFailed,可以从此处分析出target callback的最终回调时机
            onLoadFailed(exception);
        } else {
            // 任务执行最终成功则回调onLoadComplete
            onLoadComplete(resource);
        }
    }

    private Resource> decode() throws Exception {
        if (isDecodingFromCache()) {
            // 从DiskLruCache中获取数据并解码,比较简单,读者可自行分析
            return decodeFromCache();
        } else {
            // 从其他途径获取数据并解码,如网络,本地File,数据流等
            return decodeFromSource();
        }
    }

    private Resource> decodeFromSource() throws Exception {
        // 调用decodeJob来完成数据获取和编解码
        return decodeJob.decodeFromSource();
    }  
}

2)decodeJob.decodeFromSource()

class DecodeJob {
    public Resource decodeFromSource() throws Exception {
        // 获取数据,解码
        Resource decoded = decodeSource();
        // 编码并保存到DiskLruCache中
        return transformEncodeAndTranscode(decoded);
    }

    private Resource decodeSource() throws Exception {
        Resource decoded = null;
        try {
            long startTime = LogTime.getLogTime();
            // 利用不同的DataFetcher来获取数据,如网络,本地File,流文件等
            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 transformEncodeAndTranscode(Resource decoded) {
        long startTime = LogTime.getLogTime();
        // 根据ImageView的scaleType等参数计算真正被ImageView使用的图片宽高,并保存真正宽高的图片。
        // 比如centerCrop并且图片超出被ImageView裁剪时,我们没必要保存原图的宽高,而应该是裁剪之后的宽高,这样节省存储空间。
        // 这也是Glide相对于Picasso的一个很大的优势
        Resource transformed = transform(decoded);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Transformed resource from source", startTime);
        }

        // 写入到DiskLruCache中,下次就可以直接从它里面拿了
        writeTransformedToCache(transformed);

        startTime = LogTime.getLogTime();
        // 转码,将源图片转码为ImageView所需的图片格式
        Resource result = transcode(transformed);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Transcoded transformed from source", startTime);
        }
        return result;
    }
}

3)DataFetcher的loadData(priority), DataFetcher的子类很多,参见概述和核心类部分。此处我们分析下从url获取的情形,这是我们碰到最多的情形。从url获取的DataFetcher是HttpUrlFetcher,它采用了android原生的HttpURLConnection网络库,如下

public class HttpUrlFetcher implements DataFetcher<InputStream> {

    // 采用HttpURLConnection作为网络库,
    // 我们可以自定义DataFetcher,从而使用其他网络库,如OkHttp
    private HttpURLConnection urlConnection;

    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 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.
            }
        }
        // 静态工厂模式创建HttpURLConnection对象
        urlConnection = connectionFactory.build(url);
        for (Map.Entry headerEntry : headers.entrySet()) {
          urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
        }
        // Do our best to avoid gzip since it's both inefficient for images and also makes it more
        // difficult for us to detect and prevent partial content rendering. See #440.
        if (TextUtils.isEmpty(urlConnection.getRequestProperty(ENCODING_HEADER))) {
            urlConnection.setRequestProperty(ENCODING_HEADER, DEFAULT_ENCODING);
        }
        // 设置HttpURLConnection的参数
        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());
        }
    }
}

4)ResourceDecoder的decode(),ResourceDecoder的子类也很多,同样参见概述和核心类部分。我们分析下FileDescriptorBitmapDecoder,它是decode bitmap的关键所在

public class FileDescriptorBitmapDecoder implements ResourceDecoder<ParcelFileDescriptor, Bitmap> {
    public Resource decode(ParcelFileDescriptor source, int width, int height) throws IOException {
        Bitmap bitmap = bitmapDecoder.decode(source, bitmapPool, width, height, decodeFormat);
        // 对象池管理方式,从bitmapPool中获取一个BitmapResource对象
        return BitmapResource.obtain(bitmap, bitmapPool);
    }

    public Bitmap decode(ParcelFileDescriptor resource, BitmapPool bitmapPool, int outWidth, int outHeight,
            DecodeFormat decodeFormat)
            throws IOException {
            // 通过MediaMetadataRetriever来解码
        MediaMetadataRetriever mediaMetadataRetriever = factory.build();
        mediaMetadataRetriever.setDataSource(resource.getFileDescriptor());
        Bitmap result;
        if (frame >= 0) {
          result = mediaMetadataRetriever.getFrameAtTime(frame);
        } else {
          result = mediaMetadataRetriever.getFrameAtTime();
        }
        mediaMetadataRetriever.release();
        resource.close();
        return result;
    }
}

5)Transformation的transform(), 根据ImageView的实际宽高来裁剪图片数据。这样可以减小保存到DiskLruCache中的数据大小。它的子类也很多,我们分析下BitmapTransformation。


public abstract class BitmapTransformation implements Transformation<Bitmap> {
    public final Resource transform(Resource resource, int outWidth, int outHeight) {
        if (!Util.isValidDimensions(outWidth, outHeight)) {
            throw new IllegalArgumentException("Cannot apply transformation on width: " + outWidth + " or height: "
                    + outHeight + " less than or equal to zero and not Target.SIZE_ORIGINAL");
        }
        Bitmap toTransform = resource.get();
        int targetWidth = outWidth == Target.SIZE_ORIGINAL ? toTransform.getWidth() : outWidth;
        int targetHeight = outHeight == Target.SIZE_ORIGINAL ? toTransform.getHeight() : outHeight;
        // 裁剪,根据CenterCrop和fitCenter有两种裁剪方式
        Bitmap transformed = transform(bitmapPool, toTransform, targetWidth, targetHeight);

        final Resource result;
        if (toTransform.equals(transformed)) {
            result = resource;
        } else {
            // 从bitmapPool对象池中获取BitmapResource
            result = BitmapResource.obtain(transformed, bitmapPool);
        }

        return result;
    }
}

public class CenterCrop extends BitmapTransformation {
    protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
        final Bitmap toReuse = pool.get(outWidth, outHeight, toTransform.getConfig() != null
                ? toTransform.getConfig() : Bitmap.Config.ARGB_8888);
        // 按centerCrop方式裁剪
        Bitmap transformed = TransformationUtils.centerCrop(toReuse, toTransform, outWidth, outHeight);
        if (toReuse != null && toReuse != transformed && !pool.put(toReuse)) {
            toReuse.recycle();
        }
        return transformed;
    }
}

6)ResourceTranscoder的transcode(), 编码。有兴趣的读者可以自行分析

4 总结

Glide的Engine部分分为数据获取任务提交阶段和任务执行阶段。提交阶段先从内存缓存和当前活动Resources中获取,然后再在线程池中新开子线程,之后就只需要等待子线程得到执行了。任务执行阶段先尝试从DiskLruCache中获取Resources,然后利用DataFetcher获取数据,利用ResourceDecoder解码,利用Transformation裁剪图片数据,利用ResourceTranscoder转码为ImageView所需格式,这样就获取到了最终所需的Resources。

这一篇也是Glide源码解析的最终篇,感谢大家能看完我写的这些。不正确的地方,还希望指出来。谢谢!

你可能感兴趣的:(移动开发,glide,源码,engine,编解码)