Glide源码分析4 -- 缓存,编解码和网络请求
2016-08-07 16:14
549 查看
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<Void> 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<A, T, Z> { public Resource<Z> decodeFromSource() throws Exception { // 获取数据,解码 Resource<T> decoded = decodeSource(); // 编码并保存到DiskLruCache中 return transformEncodeAndTranscode(decoded); } private Resource<T> decodeSource() throws Exception { Resource<T> 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<Z> transformEncodeAndTranscode(Resource<T> decoded) { long startTime = LogTime.getLogTime(); // 根据ImageView的scaleType等参数计算真正被ImageView使用的图片宽高,并保存真正宽高的图片。 // 比如centerCrop并且图片超出被ImageView裁剪时,我们没必要保存原图的宽高,而应该是裁剪之后的宽高,这样节省存储空间。 // 这也是Glide相对于Picasso的一个很大的优势 Resource<T> transformed = transform(decoded); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Transformed resource from source", startTime); } // 写入到DiskLruCache中,下次就可以直接从它里面拿了 writeTransformedToCache(transformed); startTime = LogTime.getLogTime(); // 转码,将源图片转码为ImageView所需的图片格式 Resource<Z> 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<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. } } // 静态工厂模式创建HttpURLConnection对象 urlConnection = connectionFactory.build(url); for (Map.Entry<String, String> 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<Bitmap> 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<Bitmap> transform(Resource<Bitmap> 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<Bitmap> 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源码解析的最终篇,感谢大家能看完我写的这些。不正确的地方,还希望指出来。谢谢!
相关文章推荐
- 4、Volley解析(二),源码的深入分析一,缓存线程和网络请求线程
- # Volley源码解析(二) 没有缓存的情况下直接走网络请求源码分析#
- 基于TCP网络通信的自动升级程序源码分析-客户端请求服务器上的升级信息
- (源码分析)Glide(图片异步加载缓存库)的方法介绍
- Android源码分析:手把手带你分析 Glide的缓存功能
- Volley -- 网络请求源码分析
- OkHttp 3.7源码分析(二)——拦截器&一个实际网络请求的实现
- OKHttp网络框架源码解析(一)okHttp框架同步异步请求流程和源码分析
- Android源码分析:手把手带你分析 Glide的缓存功能
- Volley网络请求封装之LruCache源码分析
- OkHttp 3.7源码分析(二)——拦截器&一个实际网络请求的实现
- [Android]Volley源码分析(三)网络请求
- Anroid-async-http封装网络请求框架源码分析
- Volley源码解析(三) 有缓存机制的情况走缓存请求的源码分析
- tp5源码分析之网络请求
- Android源码分析:手把手带你分析 Glide的缓存功能
- OkHttp 3.7源码分析(二)——拦截器&一个实际网络请求的实现
- Glide的缓存和网络请求方式的配置
- 日常开发——Android网络请求openConnection()源码分析
- OkHttp 3.7源码分析(二)——拦截器&一个实际网络请求的实现