重學Android——Glide4.x源碼分析(2)

Glide的加載流程

接上文重學Android——Glide4.x源碼分析(1)html

執行加載主流程

接上一文,昨天講到圖片加載,最終調用到了onSizeReady的方法,調用了其中的engine.load方法java

@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緩存機制的核心,先說一下幾個名詞:bash

活動資源Active Resources 正在顯示的資源服務器

內存緩存Memory cache 顯示過的資源網絡

資源類型Resources 被解碼、轉換後的資源app

數據來源Data 源文件(未處理過的資源)框架

能夠看到,這其實就是磁盤+內存+網絡三級緩存!ide

再來分析一下上面load方法的執行流程:oop

  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

下面是個人公衆號,歡迎你們關注我

相關文章
相關標籤/搜索