重学Android——Glide4.x源码分析(2)

1,173 阅读11分钟

Glide的加载流程

接上文重学Android——Glide4.x源码分析(1)

执行加载主流程

接上一文,昨天讲到图片加载,最终调用到了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 <R> LoadStatus load(
        GlideContext glideContext,
        Object model,
        Key signature,
        int width,
        int height,
        Class<?> resourceClass,
        Class<R> transcodeClass,
        Priority priority,
        DiskCacheStrategy diskCacheStrategy,
        Map<Class<?>, 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<R> engineJob =
            engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);

        //6新建解码任务,真正执行数据加载和解码的类
        DecodeJob<R> 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<R> 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 <Model, Data> Registry append(Class<Model> modelClass, Class<Data> dataClass,
          ModelLoaderFactory<Model, Data> factory) {
        modelLoaderRegistry.append(modelClass, dataClass, factory);
        return this;
      }
  
    ...
  
  }
  
  //继续跟进代码,ModelLoaderRegistry类中
  public synchronized <Model, Data> void append(Class<Model> modelClass, Class<Data> dataClass,
      ModelLoaderFactory<Model, Data> factory) {
    multiModelLoaderFactory.append(modelClass, dataClass, factory);
    cache.clear();
  }
  
  //最后进入MultiModelLoaderFactory类中的add方法
  private <Model, Data> void add(Class<Model> modelClass, Class<Data> dataClass,
      ModelLoaderFactory<Model, Data> factory, boolean append) {
    Entry<Model, Data> 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<LoadData<?>> getLoadData() {
        if (!isLoadDataSet) {
            isLoadDataSet = true;
            loadData.clear();
            //根据model类型,通过Glide对应的registry获取ModelLoader列表
            List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model);
            //noinspection ForLoopReplaceableByForEach to improve perf
            for (int i = 0, size = modelLoaders.size(); i < size; i++) {
                ModelLoader<Object, ?> 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<Data> implements ModelLoader<String, Data> {
    private final ModelLoader<Uri, Data> uriLoader;

    // Public API.
    @SuppressWarnings("WeakerAccess")
    public StringLoader(ModelLoader<Uri, Data> uriLoader) {
        this.uriLoader = uriLoader;
    }

    @Override
    public LoadData<Data> 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<String, InputStream> {
            @NonNull
            @Override
            public ModelLoader<String, InputStream> 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<? super InputStream> 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<String, String> 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<String, String> 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。可以参考Glide4.8集成现有OkHttpClient并加载https图片blog.csdn.net/ysy950803/a…

总结

  • 从最开始没有命中内存缓存开始,然后执行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的源码非常值得仔细反复阅读,每次都能学到不少东西,当然也是一个考验耐心的事情。


参考


我的CSDN

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