Glide的加载流程
接上文
执行加载主流程
接上一文,昨天讲到图片加载,最终调用到了onSizeReady的方法,调用了其中的engine.load方法
@Override public synchronized void onSizeReady(int width, int height) { stateVerifier.throwIfRecycled(); ... status = Status.RUNNING; //计算缩略图的尺寸 float sizeMultiplier = requestOptions.getSizeMultiplier(); this.width = maybeApplySizeMultiplier(width, sizeMultiplier); this.height = maybeApplySizeMultiplier(height, sizeMultiplier); if (IS_VERBOSE_LOGGABLE) { logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime)); } //加载流程 loadStatus = engine.load( glideContext, model,//对应rul,图片地址 requestOptions.getSignature(), this.width, this.height, requestOptions.getResourceClass(),//默认是Object.class transcodeClass,//默认是Drawable.class priority, requestOptions.getDiskCacheStrategy(),//缓存策略,默认是AUTOMATIC requestOptions.getTransformations(), requestOptions.isTransformationRequired(), requestOptions.isScaleOnlyOrNoTransform(), requestOptions.getOptions(), requestOptions.isMemoryCacheable(), requestOptions.getUseUnlimitedSourceGeneratorsPool(), requestOptions.getUseAnimationPool(), requestOptions.getOnlyRetrieveFromCache(), this, callbackExecutor); }复制代码
我们再直接跟进Engin.load方法
public synchronizedLoadStatus load( GlideContext glideContext, Object model, Key signature, int width, int height, Class resourceClass, Class transcodeClass, Priority priority, DiskCacheStrategy diskCacheStrategy, Map , Transformation > transformations, boolean isTransformationRequired, boolean isScaleOnlyOrNoTransform, Options options, boolean isMemoryCacheable, boolean useUnlimitedSourceExecutorPool, boolean useAnimationPool, boolean onlyRetrieveFromCache, ResourceCallback cb, Executor callbackExecutor) { long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0; //1创建资源索引key,内存缓存的唯一键值 EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options); //2首先从内存中找当前正在显示的资源缓存 EngineResource active = loadFromActiveResources(key, isMemoryCacheable); if (active != null) { cb.onResourceReady(active, DataSource.MEMORY_CACHE); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from active resources", startTime, key); } return null; } //3没有找到,就从内存缓存资源中加载图片 EngineResource cached = loadFromCache(key, isMemoryCacheable); if (cached != null) { cb.onResourceReady(cached, DataSource.MEMORY_CACHE); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from cache", startTime, key); } return null; } //4还是没找到,如果这个任务已经在队列中,获取已经存在的加载任务 EngineJob current = jobs.get(key, onlyRetrieveFromCache); if (current != null) { current.addCallback(cb, callbackExecutor); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Added to existing load", startTime, key); } return new LoadStatus(cb, current); } //5还没有找到,如果是新建加载任务,创建EngineJob和DecodeJob,然后开始任务 EngineJob engineJob = engineJobFactory.build( key, isMemoryCacheable, useUnlimitedSourceExecutorPool, useAnimationPool, onlyRetrieveFromCache); //6新建解码任务,真正执行数据加载和解码的类 DecodeJob decodeJob = decodeJobFactory.build( glideContext, model, key, signature, width, height, resourceClass, transcodeClass, priority, diskCacheStrategy, transformations, isTransformationRequired, isScaleOnlyOrNoTransform, onlyRetrieveFromCache, options, engineJob); //7缓存加载任务 jobs.put(key, engineJob); engineJob.addCallback(cb, callbackExecutor); //开启解码任务 engineJob.start(decodeJob); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Started new load", startTime, key); } return new LoadStatus(cb, engineJob); }复制代码
这个方法代码有点长,却是Glide缓存机制的核心,先说一下几个名词:
活动资源Active Resources 正在显示的资源
内存缓存Memory cache 显示过的资源
资源类型Resources 被解码、转换后的资源
数据来源Data 源文件(未处理过的资源)
可以看到,这其实就是磁盘+内存+网络三级缓存!
再来分析一下上面load方法的执行流程:
- 创建资源索引key,这个是唯一的,可以看到,生成一个key需要资源本身、图片宽高转换类型、加载参数等,只要这些不一致,就不是同一张图片,所以即便是显示的图片宽高不一样,Glide都会重新执行一次加载过程,而不是使用内存中加载已有的图片资源。
- 2和3的流程:如果要加载的图片已经正在显示,直接使用已有的资源,如果图片没有在显示,但正好在内存缓存中,没有销毁,那么可以直接使用缓存中的资源。
- 4到8流程:如果内存中没有可以直接使用的图片资源,那么就要开始从网络或者本地磁盘中去加载一张图片
所以我们可以直接到engineJob.start()方法
//start方法就是根据diskCacheStrategy策略获取一个executor来执行DecodeJob public synchronized void start(DecodeJobdecodeJob) { this.decodeJob = decodeJob; //这里根据缓存策略,决定使用哪一个Executor,默认情况返回DiskCacheExecutor //共有三种执行器,diskcacheExecutor,sourceExecutor,sourceUnlimitedExecutor GlideExecutor executor = decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor(); executor.execute(decodeJob); }复制代码
可以看到,在start方法直接启用了线程池的execute方法,可以知道,DecodeJob类一定是个runnable,我们去看它的run方法。
//DecodeJob.java @Override public void run() { ... DataFetcher localFetcher = currentFetcher; try { if (isCancelled) { notifyFailed(); return; } //重点在这 runWrapped(); } catch (CallbackException e) { ... } }复制代码
如果一切正常,那么会进入runWrapped方法
private void runWrapped() { switch (runReason) { //默认状态为INITISLIZE case INITIALIZE: stage = getNextStage(Stage.INITIALIZE); currentGenerator = getNextGenerator(); runGenerators(); break; case SWITCH_TO_SOURCE_SERVICE: runGenerators(); break; case DECODE_DATA: decodeFromRetrievedData(); break; default: throw new IllegalStateException("Unrecognized run reason: " + runReason); } }复制代码
默认的状态是INITIALIZE,那我们来看它调用的几个方法
//获取任务执行阶段:初始化,读取转换后的缓存,读取原文件(未处理)缓存,远程图片加载,结束状态 private Stage getNextStage(Stage current) { switch (current) { //初始状态 case INITIALIZE: return diskCacheStrategy.decodeCachedResource() ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE); //状态2,读取转换后的缓存 case RESOURCE_CACHE: return diskCacheStrategy.decodeCachedData() ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE); //状态3,读取原文件缓存 case DATA_CACHE: // Skip loading from source if the user opted to only retrieve the resource from cache. return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE; case SOURCE: case FINISHED: return Stage.FINISHED; default: throw new IllegalArgumentException("Unrecognized stage: " + current); } } private DataFetcherGenerator getNextGenerator() { switch (stage) { case RESOURCE_CACHE: //从换后的缓存读取文件 return new ResourceCacheGenerator(decodeHelper, this); case DATA_CACHE: //从原文件缓存加载 return new DataCacheGenerator(decodeHelper, this); case SOURCE: //没有缓存,远程图片资源加载器(比如网络,本地文件) return new SourceGenerator(decodeHelper, this); case FINISHED: return null; default: throw new IllegalStateException("Unrecognized stage: " + stage); } } //这里开始加载执行 private void runGenerators() { currentThread = Thread.currentThread(); startFetchTime = LogTime.getLogTime(); boolean isStarted = false; //这里Generator.startNext方法中就是加载过程,如果成功就返回true并跳出循环,否则切换Generator继续执行 while (!isCancelled && currentGenerator != null && !(isStarted = currentGenerator.startNext())) { stage = getNextStage(stage); currentGenerator = getNextGenerator(); //如果任务执行到去远程加载,且切换任务执行环境 if (stage == Stage.SOURCE) { reschedule(); return; } } if ((stage == Stage.FINISHED || isCancelled) && !isStarted) { notifyFailed(); } } @Override public void reschedule() { //更改执行目标为:SOURCE服务。当然也只有在stage == Stage.SOURCE的情况下会被调用。 runReason = RunReason.SWITCH_TO_SOURCE_SERVICE; callback.reschedule(this);//这里callback正是EngineJob。 } //代码跟进EngineJob类中,可以看到实现方法。 @Override public void reschedule(DecodeJob job) { // 可以看到,这里获取的SourceExecutor来执行decodeJob。 //也就巧妙地将此decodeJob任务从cacheExecutor切换到了SourceExecutor,这样分工协作更加高效。 getActiveSourceExecutor().execute(job); }复制代码
可以看到,这里几个方法构成了Glide的解码流程:尝试从转换过的本地资源加载图片;尝试从没有处理过的原始资源中加载图片;尝试远程加载图片。通过状态的切换来寻找下一个加载器,直到加载到这张图,返回成功,如果找不到,返回失败。
再捋一捋:
- 这一段是从最开始没有命中内存缓存开始,然后执行Engine的start方法,默认情况下会获取到cacheExecutor执行器来执行decodeJob任务;
- decodeJob这个runnable的run方法就会被调用;
- 因为RunReason为INITIALIZE,接着获取stage,默认返回Stage.RESOURCE_CACHE
- 这时通过getNextGenerator就返回了ResourceCacheGenerator加载器
- 接着调用ResourceCacheGenerator的startNext方法,从转换后的缓存中读取已缓存的资源
- 如果命中,就结束任务并回调结果,反之,切换到DataCacheGenerator,同样的,如果还没命中就切换到SourceGenerator加载器(比如初次加载,没有任何缓存,就会走到这)。
- 切换到SourceGenerator环境,等它结束后,结束任务,回调结果,流程结束。
网络相关——加载网络图片
从上面我们可以知道,SourceGenerator可以加载网络与本地图片,那么我们直接看SourceGenerator调用startNext方法
/** * SourceGenerator * DataFetcher的简介:Fetcher的意思是抓取,所以该类可以称为数据抓取器 * 作用就是根据不同的数据来源(本地,网络,Asset等) * 以及读取方式(Stream,ByteBuffer等)来提取并解码数据资源,实现类如下 * AssetPathFetcher:加载Asset数据 * HttpUrlFetcher:加载网络数据 * LocalUriFetcher:加载本地数据 * 其他实现类... * */ @Override public boolean startNext() { //忽略缓存部分逻辑 ... if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) { return true; } sourceCacheGenerator = null; loadData = null; boolean started = false; //是否有更多的ModelLoader while (!started && hasNextModelLoader()) { loadData = helper.getLoadData().get(loadDataListIndex++); if (loadData != null && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource()) || helper.hasLoadPath(loadData.fetcher.getDataClass()))) { started = true; //选择合适的LoadData,并使用fetcher来抓取数据 loadData.fetcher.loadData(helper.getPriority(), this); } } return started; }复制代码
继续跟进代码,private volatile ModelLoader.LoadData<?> loadData,是一个接口,就需要我们来找出它的实现类了
回到Glide的初始化中
Glide(...) { ... registry ... .append(String.class, InputStream.class, new StringLoader.StreamFactory()) .append(Uri.class, InputStream.class, new HttpUriLoader.Factory()) .... .append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory()) ... .register(GifDrawable.class, byte[].class, gifDrawableBytesTranscoder); ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory(); glideContext = new GlideContext( context, arrayPool, registry, imageViewTargetFactory, defaultRequestOptions, defaultTransitionOptions, defaultRequestListeners, engine, isLoggingRequestOriginsEnabled, logLevel); }复制代码
这构造方法是很长的,我们只看我们关心的网络相关的部分,可以看到,我们将String.class Uri.class GlideUri.class三种类型注入了不同的Factory,这个Factory就是用来创建ModelLoader的,ModelLoader就是用来加载图片的。
public class Registry { //各种功能类注册器。加载、转换、解码、加密等。 private final ModelLoaderRegistry modelLoaderRegistry; private final EncoderRegistry encoderRegistry; private final ResourceDecoderRegistry decoderRegistry; private final ResourceEncoderRegistry resourceEncoderRegistry; private final DataRewinderRegistry dataRewinderRegistry; private final TranscoderRegistry transcoderRegistry; private final ImageHeaderParserRegistry imageHeaderParserRegistry; ... //modelLoader注册 publicRegistry append(Class modelClass, Class dataClass, ModelLoaderFactory factory) { modelLoaderRegistry.append(modelClass, dataClass, factory); return this; } ... } //继续跟进代码,ModelLoaderRegistry类中 public synchronized void append(Class modelClass, Class dataClass, ModelLoaderFactory factory) { multiModelLoaderFactory.append(modelClass, dataClass, factory); cache.clear(); } //最后进入MultiModelLoaderFactory类中的add方法 private void add(Class modelClass, Class dataClass, ModelLoaderFactory factory, boolean append) { Entry entry = new Entry<>(modelClass, dataClass, factory); //entries是一个list。所以,到这里就知道注册的LoaderFactory被缓存到了列表中,以便后面取用。 entries.add(append ? entries.size() : 0, entry); }复制代码
通过以上代码,知道了ModelLoaderFactory在Glide初始化时注册到了一个列表中,以后使用。在分析DecodeJob的代码里时,我们使用SourceGenerator加载远程图片,并分析到了loadData.fetcher.loadData(helper.getPriority(), this);
是真正加载数据的地方。
我们先看loadData在哪赋值的
loadData = helper.getLoadData().get(loadDataListIndex++);复制代码
可以看到,是使用Helper来调用方法。
//DecodeHelper List> getLoadData() { if (!isLoadDataSet) { isLoadDataSet = true; loadData.clear(); //根据model类型,通过Glide对应的registry获取ModelLoader列表 List
可以看到,这其实是一个列表,可以知道,在
.append(String.class, InputStream.class, new StringLoader.StreamFactory().append(Uri.class, InputStream.class, new HttpUriLoader.Factory()).append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())复制代码
这三个Factory中,因为我们load的是一个String,所以我们肯定是StringLoader中
public class StringLoader implements ModelLoader{ private final ModelLoader uriLoader; // Public API. @SuppressWarnings("WeakerAccess") public StringLoader(ModelLoader uriLoader) { this.uriLoader = uriLoader; } @Override public LoadData buildLoadData(@NonNull String model, int width, int height, @NonNull Options options) { Uri uri = parseUri(model); if (uri == null || !uriLoader.handles(uri)) { return null; } return uriLoader.buildLoadData(uri, width, height, options); } ... /** * Factory for loading { @link InputStream}s from Strings. */ public static class StreamFactory implements ModelLoaderFactory { @NonNull @Override public ModelLoader build( @NonNull MultiModelLoaderFactory multiFactory) { //关键在这儿 return new StringLoader<>(multiFactory.build(Uri.class, InputStream.class)); } @Override public void teardown() { // Do nothing. } }}复制代码
可以看到,其实它在里面MultiModelLoaderFactory通过Uri.class和InputStream.class创建一个ModelLoader给StringLoader,所以StringLoader的加载功能转移了。而且根据注册关系知道转移到了HttpUriLoader中。而它对应的Fetcher就是HttpUrlFetcher。
那么可以直接看HttpUrlFetcher的load代码
/** * HttpUrlFetcher * HttpUrlFetcher的简介:网络数据抓取器,通俗的来讲就是去服务器上下载图片,支持地址重定向(最多5次) * */ @Override public void loadData(@NonNull Priority priority, @NonNull DataCallback callback) { long startTime = LogTime.getLogTime(); try { InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders()); callback.onDataReady(result); } catch (IOException e) { ... } } private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Mapheaders) throws IOException { //重定向次数过多 if (redirects >= MAXIMUM_REDIRECTS) { throw new HttpException("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. //通过URL的equals方法来比较会导致网络IO开销,一般会有问题 //可以参考http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html. try { if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) { throw new HttpException("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()); } urlConnection.setConnectTimeout(timeout); urlConnection.setReadTimeout(timeout); urlConnection.setUseCaches(false); urlConnection.setDoInput(true); // Stop the urlConnection instance of HttpUrlConnection from following redirects so that // redirects will be handled by recursive calls to this method, loadDataWithRedirects. urlConnection.setInstanceFollowRedirects(false); // Connect explicitly to avoid errors in decoders if connection fails. urlConnection.connect(); // Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352. stream = urlConnection.getInputStream(); if (isCancelled) { return null; } final int statusCode = urlConnection.getResponseCode(); if (isHttpOk(statusCode)) { return getStreamForSuccessfulRequest(urlConnection); } else if (isHttpRedirect(statusCode)) { String redirectUrlString = urlConnection.getHeaderField("Location"); if (TextUtils.isEmpty(redirectUrlString)) { throw new HttpException("Received empty or null redirect url"); } URL redirectUrl = new URL(url, redirectUrlString); // Closing the stream specifically is required to avoid leaking ResponseBodys in addition // to disconnecting the url connection below. See #2352. cleanup(); return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers); } else if (statusCode == INVALID_STATUS_CODE) { throw new HttpException(statusCode); } else { throw new HttpException(urlConnection.getResponseMessage(), statusCode); } }复制代码
看到这,终于找到了网络通讯的代码,就是通过HttpUrlConnection来获取数据流并返回。当然也可以自定义使用OkHttp。可以参考
总结
- 从最开始没有命中内存缓存开始,然后执行Engine的start方法,默认情况下会获取到cacheExecutor执行器来执行decodeJob任务;
- decodeJob这个runnable的run方法就会被调用;
- 因为RunReason为INITIALIZE,接着获取stage,默认返回Stage.RESOURCE_CACHE
- 这时通过getNextGenerator就返回了ResourceCacheGenerator加载器
- 接着调用ResourceCacheGenerator的startNext方法,从转换后的缓存中读取已缓存的资源
- 如果命中,就结束任务并回调结果,反之,切换到DataCacheGenerator,同样的,如果还没命中就切换到SourceGenerator加载器(比如初次加载,没有任何缓存,就会走到这)。
- 切换到SourceGenerator环境,如果是网络请求图片,就调用HttpUrlFetcher的LoadData方法
- LoadData通过调用loadDataWithRedirects,里面默认使用HttpUrlConnection来下载图片,等它结束后,结束任务,回调结果,流程结束。
分析源码的时候,我们只应该关注主流程,不要太过于纠结每一个细节。Glide的源码非常值得仔细反复阅读,每次都能学到不少东西,当然也是一个考验耐心的事情。
参考
-
- 郭神基于3.x版本的。
下面是我的公众号,欢迎大家关注我