博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
重学Android——Glide4.x源码分析(2)
阅读量:6097 次
发布时间:2019-06-20

本文共 20431 字,大约阅读时间需要 68 分钟。

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 synchronized 
LoadStatus 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方法的执行流程:

  1. 创建资源索引key,这个是唯一的,可以看到,生成一个key需要资源本身、图片宽高转换类型、加载参数等,只要这些不一致,就不是同一张图片,所以即便是显示的图片宽高不一样,Glide都会重新执行一次加载过程,而不是使用内存中加载已有的图片资源。
  2. 2和3的流程:如果要加载的图片已经正在显示,直接使用已有的资源,如果图片没有在显示,但正好在内存缓存中,没有销毁,那么可以直接使用缓存中的资源。
  3. 4到8流程:如果内存中没有可以直接使用的图片资源,那么就要开始从网络或者本地磁盘中去加载一张图片

所以我们可以直接到engineJob.start()方法

//start方法就是根据diskCacheStrategy策略获取一个executor来执行DecodeJob    public synchronized void start(DecodeJob
decodeJob) { 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注册     public 
Registry 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
> modelLoaders = glideContext.getRegistry().getModelLoaders(model); //noinspection ForLoopReplaceableByForEach to improve perf for (int i = 0, size = modelLoaders.size(); i < size; i++) { ModelLoader
modelLoader = modelLoaders.get(i); LoadData
current = modelLoader.buildLoadData(model, width, height, options); //循环创建出LoadData,用户后面加载 if (current != null) { loadData.add(current); } } } return loadData; }复制代码

可以看到,这其实是一个列表,可以知道,在

.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, Map
headers) 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版本的。


下面是我的公众号,欢迎大家关注我

转载于:https://juejin.im/post/5d028d655188253d592e0657

你可能感兴趣的文章
pfsense锁住自己
查看>>
vsftpd 相关总结
查看>>
bash complete -C command
查看>>
解决zabbix 3.0中1151端口不能运行问题
查看>>
售前工程师的成长---一个老员工的经验之谈
查看>>
Get到的优秀博客网址
查看>>
压力测试对于BCH真的有意义吗?
查看>>
Node.js Web 模块
查看>>
Vlmcsd: 自建 KMS 激活服务器
查看>>
maven的私服搭建
查看>>
基于环状队列和迭代器实现分布式任务RR分配策略
查看>>
React Native Android 开发环境搭建,只需4步
查看>>
IntelliJ Idea编译报错:请使用 -source 7 或更高版本以启用 diamond 运算符
查看>>
YII中的$this->createUrl()参数前面斜杠使用说明
查看>>
java动态编译
查看>>
如何修改xmind语言设置
查看>>
OSChina 周三乱弹——喜欢就好,520?我会不好意思
查看>>
OSChina 周三乱弹 —— 风扇写着先生请自爱
查看>>
python os.mkdir与 os.makedirs
查看>>
AccessDB 读取 mybatis实现 以及 + 单独测试类
查看>>