Glide 系列-2:主流程源碼分析(4.8.0)

Glide 是 Android 端比較經常使用的圖片加載框架,這裏咱們就再也不介紹它的基礎的使用方式。你能夠經過查看其官方文檔學習其基礎使用。這裏,咱們給出一個 Glide 的最基本的使用示例,並以此來研究這個整個過程發生了什麼:java

Glide.with(fragment).load(myUrl).into(imageView);
複製代碼

上面的代碼雖然簡單,可是整個執行過程涉及許多類,其流程也比較複雜。爲了更清楚地說明這整個過程,咱們將 Glide 的圖片加載按照調用的時間關係分紅了下面幾個部分:android

  1. with() 方法的執行過程
  2. load() 方法的執行過程
  3. into() 方法的執行過程
    1. 階段1:開啓 DecodeJob 的過程
    2. 階段2:打開網絡流的過程
    3. 階段3:將輸入流轉換爲 Drawable 的過程
    4. 階段4:將 Drawable 展現到 ImageView 的過程

即按照上面的示例代碼,先分紅 with()load()into() 三個過程,而 into() 過程又被細化成四個階段。git

下面咱們就按照上面劃分的過程來分別介紹一下各個過程當中都作了哪些操做。github

一、with() 方法的執行過程

1.1 實例化單例的 Glide 的過程

當調用了 Glide 的 with() 方法的時候會獲得一個 RequestManager 實例。with() 有多個重載方法,咱們可使用 Activity 或者 Fragment 等來獲取 Glide 實例。它們最終都會調用下面這個方法來完成最終的操做:數組

public static RequestManager with(Context context) {
    return getRetriever(context).get(context);
}
複製代碼

getRetriever() 方法內部咱們會先使用 Glideget() 方法獲取一個單例的 Glide 實例,而後從該 Glide 實例中獲得一個 RequestManagerRetriever:緩存

private static RequestManagerRetriever getRetriever(Context context) {
    return Glide.get(context).getRequestManagerRetriever();
}
複製代碼

這裏調用了 Glide 的 get() 方法,它最終會調用 initializeGlide() 方法實例化一個單例Glide 實例。在以前的文中咱們已經介紹了這個方法。它主要用來從註解和 Manifest 中獲取 GlideModule,並根據各 GlideModule 中的方法對 Glide 進行自定義:網絡

《Glide 系列-1:預熱、Glide 的經常使用配置方式及其原理》app

下面的方法中須要傳入一個 GlideBuilder 實例。很明顯這是一種構建者模式的應用,咱們可使用它的方法來實現對 Glide 的個性化配置:框架

private static void initializeGlide(Context context, GlideBuilder builder) {

    // ... 各類操做,略

    // 賦值給靜態的單例實例
    Glide.glide = glide;
}
複製代碼

最終 Glide 實例由 GlideBuilderbuild() 方法構建完畢。它會直接調用 Glide 的構造方法來完成 Glide 的建立。在該構造方法中會將各類類型的圖片資源及其對應的加載類的映射關係註冊到 Glide 中,你能夠閱讀源碼瞭解這部份內容。ide

1.2 Glide 的生命週期管理

with() 方法的執行過程還有一個重要的地方是 Glide 的生命週期管理。由於當咱們正在進行圖片加載的時候,Fragment 或者 Activity 的生命週期可能已經結束了,因此,咱們須要對 Glide 的生命週期進行管理。

Glide 對這部份內容的處理也很是巧妙,它使用沒有 UI 的 Fragment 來管理 Glide 的生命週期。這也是一種很是經常使用的生命週期管理方式,好比 RxPermission 等框架都使用了這種方式。你能夠經過下面的示例來了解它的做用原理:

示例代碼:使用 Fragment 管理 onActivityResult()

with() 方法中,當咱們調用了 RequestManagerRetrieverget() 方法以後,會根據 Context 的類型調用 get() 的各個重載方法。

public RequestManager get(@NonNull Context context) {
    if (context == null) {
      throw new IllegalArgumentException("You cannot start a load on a null Context");
    } else if (Util.isOnMainThread() && !(context instanceof Application)) {
      if (context instanceof FragmentActivity) {
        return get((FragmentActivity) context);
      } else if (context instanceof Activity) {
        return get((Activity) context);
      } else if (context instanceof ContextWrapper) {
        return get(((ContextWrapper) context).getBaseContext());
      }
    }

    return getApplicationManager(context);
  }
複製代碼

咱們以 Activity 爲例。以下面的方法所示,噹噹前位於後臺線程的時候,會使用 Application 的 Context 獲取 RequestManager,不然會使用無 UI 的 Fragment 進行管理:

public RequestManager get(@NonNull Activity activity) {
    if (Util.isOnBackgroundThread()) {
      return get(activity.getApplicationContext());
    } else {
      assertNotDestroyed(activity);
      android.app.FragmentManager fm = activity.getFragmentManager();
      return fragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
    }
  }
複製代碼

而後就調用到了 fragmentGet() 方法。這裏咱們從 RequestManagerFragment 中經過 getGlideLifecycle() 獲取到了 Lifecycle 對象。Lifecycle 對象提供了一系列的、針對 Fragment 生命週期的方法。它們將會在 Fragment 的各個生命週期方法中被回調。

private RequestManager fragmentGet(Context context, FragmentManager fm, Fragment parentHint, boolean isParentVisible) {
    RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
      Glide glide = Glide.get(context);
      requestManager =
          factory.build(
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
      current.setRequestManager(requestManager);
    }
    return requestManager;
  }
複製代碼

而後,咱們將該 Lifecycle 傳入到 RequestManager 中,以 RequestManager 中的兩個方法爲例,RequestManager 會對 Lifecycle 進行監聽,從而達到了對 Fragment 的生命週期進行監聽的目的:

public void onStart() {
    resumeRequests();
    targetTracker.onStart();
  }

  public void onStop() {
    pauseRequests();
    targetTracker.onStop();
  }
複製代碼

1.3 小結

通過上述分析,咱們可使用下面的流程圖總結 Glide 的 with() 方法的執行過程:

Glide 的 with() 方法的執行過程

二、load() 方法的執行過程

2.1 load() 的過程

當咱們拿到了 RequestManager 以後就可使用它來調用 load() 方法了。在咱們的示例中傳入的是一個 url 對象。load() 方法也是重載的,咱們能夠傳入包括 Bitmap, Drawable, Uri 和 String 等在內的多種資源類型。示例中會調用下面的這個方法獲得一個 RequestBuilder 對象,顯然這是一種構建者模式的應用。咱們可使用 RequestBuilder 的其餘方法來繼續構建圖片加載請求,你能夠經過查看它的源碼瞭解 Glide 都爲咱們提供了哪些構建方法:

public RequestBuilder<TranscodeType> load(@Nullable String string) {
    return loadGeneric(string);
  }
複製代碼

RequestBuilder 的構造方法中存在一個 apply() 方法值得咱們一提,其定義以下。從下面的方法定義中能夠看出,咱們能夠經過爲 RequestBuilder 指定 RequestOptions 來配置當前圖片加載請求。好比,指定磁盤緩存的策略,指定佔位圖,指定圖片加載出錯時顯示的圖片等等。那麼咱們怎麼獲得 RequestOptions 呢?在 Glide 4.8.0 中的類 RequestOptions 爲咱們提供了一系列的靜態方法,咱們能夠這些方法來獲得 RequestOptions 的實例:

public RequestBuilder<TranscodeType> apply(RequestOptions requestOptions) {
    Preconditions.checkNotNull(requestOptions);
    this.requestOptions = getMutableOptions().apply(requestOptions);
    return this;
  }
複製代碼

回過頭來,咱們能夠繼續跟蹤 load() 方法。其實,不論咱們使用了 load() 的哪一個重載方法,最終都會調用到下面的方法。它的邏輯也比較簡單,就是將咱們的圖片資源信息賦值給 RequestBuilder 的局部變量就完事了。至於圖片如何被加載和顯示,則在 into() 方法中進行處理。

public RequestBuilder<TranscodeType> load(@Nullable String string) {
    return loadGeneric(string);
  }

  private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
    this.model = model;
    isModelSet = true;
    return this;
  }
複製代碼

2.2 小結

因此,咱們能夠總結 Glide 的 load() 方法的執行過程以下。也就是使用 RequestManger 獲得一個 RequestBuilder 的過程:

Glide 的 load() 方法執行過程

三、into() 方法的執行過程

考慮到 into() 方法流程比較長、涉及的類比較多,咱們按照圖片加載的過程將其分紅四個階段來進行介紹。

第一個階段是開啓 DecodeJob 的過程。DecodeJob 負責從緩存或者從原始的數據源中加載圖片資源,對圖片進行變換和轉碼,是 Glide 圖片加載過程的核心。DecodeJob 繼承了 Runnable,實際進行圖片加載的時候會將其放置到線程池當中執行。這個階段咱們重點介紹的是從 RequestBuilder 構建一個 DecodeJob 並開啓 DecodeJob 任務的過程。即構建一個 DecodeJob 並將其丟到線程池裏的過程。

第二個階段是打開網絡流的過程。這個階段會根據咱們的圖片資源來從數據源中加載圖片數據。以咱們的示例爲例,在默認狀況下會從網絡當中加載圖片,並獲得一個 InputStream.

第三個階段是將輸入流轉換爲 Drawable 的過程。獲得了 InputStream 以後還要調用 BitmapFactorydecodeStream() 方法來從 InputStream 中獲得一個 Drawable.

第四個階段是將 Drawable 顯示到 ImageView 上面的過程。

3.1 階段1:開啓 DecodeJob 的過程

3.1.1 流程分析

咱們繼續沿着 into() 方法進行分析。

into() 方法也定義在 RequestBuilder 中,而且也是重載的。不論咱們調用哪一個重載方法都會將要用來顯示圖片的對象封裝成一個 Target 類型。Target 主要用來對用來顯示圖片的對象的生命週期進行管理。當咱們要將圖片加載到 ImageView 的時候,最終會調用下面的 buildTarget() 方法來說咱們的 ImageView 封裝成一個 ViewTarget,而後調用 into() 的重載方法進行後續處理:

public <Z> ViewTarget<ImageView, Z> buildTarget(ImageView view, Class<Z> clazz) {
    if (Bitmap.class.equals(clazz)) {
      return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
    } else if (Drawable.class.isAssignableFrom(clazz)) {
      return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
    } else {
      throw new IllegalArgumentException(
          "Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
    }
  }

  private <Y extends Target<TranscodeType>> Y into(Y target, RequestListener<TranscodeType> targetListener, RequestOptions options) {

    options = options.autoClone();
    Request request = buildRequest(target, targetListener, options); // 1

    Request previous = target.getRequest();
    if (request.isEquivalentTo(previous)
        && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
      request.recycle();
      if (!Preconditions.checkNotNull(previous).isRunning()) {
        previous.begin();
      }
      return target;
    }

    requestManager.clear(target);
    target.setRequest(request);
    requestManager.track(target, request); // 2

    return target;
  }
複製代碼

在上面的 into() 方法的 1 處最終會調用到下面的方法來構建一個請求對象。(這裏咱們忽略掉具體的參數,只給看構建請求的邏輯)。簡而言之,該方法會根據咱們是否調用過 RequestBuildererror() 方法設置過圖片加載出錯時候顯示的圖片來決定返回 mainRequest 仍是 errorRequestCoordinator。由於咱們沒有設置該參數,因此會直接返回 mainRequest

private Request buildRequestRecursive(/*各類參數*/) {

    ErrorRequestCoordinator errorRequestCoordinator = null;
    if (errorBuilder != null) {
      errorRequestCoordinator = new ErrorRequestCoordinator(parentCoordinator);
      parentCoordinator = errorRequestCoordinator;
    }

    Request mainRequest = buildThumbnailRequestRecursive(/*各類參數*/); // 1

    if (errorRequestCoordinator == null) {
      return mainRequest;
    }

    // ... 略

    Request errorRequest = errorBuilder.buildRequestRecursive(/*各類參數*/);
    errorRequestCoordinator.setRequests(mainRequest, errorRequest);
    return errorRequestCoordinator;
  }
複製代碼

上面是根據是否設置加載失敗時顯示的圖片來決定返回的請求對象的。若是你使用過 Glide 的話,那麼必定記得除了設置加載失敗時的圖片,咱們還會先加載一張小圖,即 Thumbnail。因此,在上面方法的 1 處會根據設置調用過 RequestBuilderthumbnail() 方法來決定返回 Thumbnail 的請求仍是真實圖片的請求。一樣由於咱們沒有設置過該方法,因此最終會調用下面的方法來構建最終的圖片加載請求。

private Request obtainRequest(/*各類參數*/) {
    return SingleRequest.obtain(/*各類參數*/);
  }
複製代碼

SingleRequestobtain() 方法中會先嚐試從請求的池中取出一個請求,當請求不存在的時候就會實例化一個 SingleRequest,而後調用它的 init() 方法完成請求的初始化工做。這裏的請求池使用了 Android 的 support v4 包中的 Pool 相關的 API. 它被設計用來構建基於數組的請求池,具體如何使用能夠參考相關的文檔和源碼。

public static <R> SingleRequest<R> obtain(/*各類參數*/) {
    SingleRequest<R> request = (SingleRequest<R>) POOL.acquire();
    if (request == null) {
      request = new SingleRequest<>();
    }
    request.init(/*各類參數*/);
    return request;
  }
複製代碼

獲得了請求以後會用 RequestManagertrack() 方法:

void track(@NonNull Target<?> target, @NonNull Request request) {
    targetTracker.track(target);
    requestTracker.runRequest(request);
  }
複製代碼

該方法的主要做用有兩個:

  1. 調用 TargetTrackertrack() 方法對對當前 Target 的生命週期進行管理;
  2. 調用 RequestTrackerrunRequest() 方法對當前請求進行管理,當 Glide 未處於暫停狀態的時候,會直接使用 Requestbegin() 方法開啓請求。

下面是 SingeleRequestbegin() 方法。它會根據當前加載的狀態來判斷應該調用哪一個方法。由於咱們以前圖片加載的過程可能由於一些意想不到的緣由被終止,因此當重啓的時候就須要根據以前的狀態進行恢復。對於咱們第一次加載的狀況,則會直接進入到下方 1 處的 onSizeReady() 方法中:

public void begin() {
    if (model == null) {
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        width = overrideWidth;
        height = overrideHeight;
      }
      int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
      onLoadFailed(new GlideException("Received null model"), logLevel);
      return;
    }

    if (status == Status.RUNNING) {
      throw new IllegalArgumentException("Cannot restart a running request");
    }

    // 若是咱們在完成以後從新啓動(一般經過諸如 notifyDataSetChanged() 之類的方法,
    // 在相同的目標或視圖中啓動相同的請求),咱們可使用咱們上次檢索的資源和大小
    // 並跳過獲取新的大小。因此,若是你由於 View 大小發生了變化而想要從新加載圖片
    // 就須要在開始新加載以前清除視圖 (View) 或目標 (Target)。
    if (status == Status.COMPLETE) {
      onResourceReady(resource, DataSource.MEMORY_CACHE);
      return;
    }

    status = Status.WAITING_FOR_SIZE;
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
      onSizeReady(overrideWidth, overrideHeight); // 1
    } else {
      target.getSize(this);
    }

    if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
        && canNotifyStatusChanged()) {
      target.onLoadStarted(getPlaceholderDrawable()); // 2
    }
  }
複製代碼

下面是 onSizeReady() 方法,咱們能夠看出它會先判斷當前是否處於 Status.WAITING_FOR_SIZE 狀態,並隨後將狀態更改成 Status.RUNNING 並調用 engineload() 方法。顯然,更改完狀態以後繼續回到上面的方法,在 2 處即調用了 TargetonLoadStarted() 方法。這樣 Target 的第一個生命週期就被觸發了。

public void onSizeReady(int width, int height) {
    if (status != Status.WAITING_FOR_SIZE) {
      return;
    }
    status = Status.RUNNING;

    float sizeMultiplier = requestOptions.getSizeMultiplier();
    this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
    this.height = maybeApplySizeMultiplier(height, sizeMultiplier);

    loadStatus = engine.load(/*各類參數*/);

    if (status != Status.RUNNING) {
      loadStatus = null;
    }
  }
複製代碼

而後,讓咱們將重點放到 Engineload() 方法。該方法雖然不長,可是卻包含了許多重要的內容。咱們在下篇文章中將要研究的 Glide 的緩存就是在這裏實現的。該方法大體的邏輯上,先嚐試從內存緩存當中查找指定的資源,當內存中不存在的時候就準備使用 DecodeJob 來加載圖片。

public <R> LoadStatus load(/*各類參數*/) {
    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);

    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      return null;
    }

    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      return null;
    }

    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
      current.addCallback(cb);
      return new LoadStatus(cb, current);
    }

    EngineJob<R> engineJob = engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);

    DecodeJob<R> decodeJob = decodeJobFactory.build(/*各類參數*/);

    jobs.put(key, engineJob);

    engineJob.addCallback(cb);
    engineJob.start(decodeJob);

    return new LoadStatus(cb, engineJob);
  }
複製代碼

上面方法中涉及兩個類,一個是 DecodeJob、一個是 EngineJob。它們之間的關係是,EngineJob 內部維護了線程池,用來管理資源加載,已經當資源加載完畢的時候通知回調。 DecodeJob 繼承了 Runnable,是線程池當中的一個任務。就像上面那樣,咱們經過調用 engineJob.start(decodeJob) 來開始資源加載。

3.1.2 小結

階段1:開啓 DecodeJob 的過程

根據上文中的分析,咱們不可貴出上面的流程圖。不考慮緩存的問題,這個部分的邏輯仍是比較清晰的,即:當調用了 into() 以後,首先構建一個請求對象 SingleRequest,而後調用 RequestManagertrack() 方法對 RequestTarget 進行管理;隨後,使用 Requestbegin() 方法來啓動請求;該方法中會使用 Engineload() 方法決定是從緩存當中獲取資源仍是從數據源中加載數據;若是是從數據源中加載數據的話,就構建一個 DecodeJob 交給 EngineJob 來執行便可。

3.2 階段2:打開網絡流的過程

3.2.1 打開網絡流的過程

在上面的分析中,將 DecodeJob 交給 EngineJob 就完事了。由於 DecodeJob 是一個任務,會在線程池當中進行執行。因此,若是咱們繼續追蹤的話,就應該從 DecodeJobrun() 方法開始:

因此,若是想要找到加載資源和解碼的邏輯,就應該查看 DecodeJob 的 run() 方法。下面就是這個方法的定義:

public void run() {
    DataFetcher<?> localFetcher = currentFetcher;
    try {
      if (isCancelled) {
        notifyFailed();
        return;
      }
      runWrapped();
    } catch (Throwable t) {
      if (stage != Stage.ENCODE) {
        throwables.add(t);
        notifyFailed();
      }
      if (!isCancelled) {
        throw t;
      }
    } finally {
      if (localFetcher != null) {
        localFetcher.cleanup();
      }
      GlideTrace.endSection();
    }
  }
複製代碼

DecodeJob 的執行過程使用了狀態模式,它會根據當前的狀態決定將要執行的方法。在上面的方法中,噹噹前任務沒有被取消的話,會進入到 runWrapped() 方法。該方法中會使用 runReason 做爲當前的狀態決定要執行的邏輯:

private void runWrapped() {
    switch (runReason) {
      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);
    }
  }
複製代碼

這裏的 runReason 是一個枚舉類型,它包含的枚舉值即爲上面的三種類型。當咱們在一個過程執行完畢以後會回調 DecodeJob 中的方法修改 runReason,而後根據新的狀態值執行新的邏輯。

除了 runReasonDecodeJob 中還有一個變量 stage 也是用來決定 DecodeJob 狀態的變量。一樣,它也是一個枚舉,用來表示將要加載數據的數據源以及數據的加載狀態。它主要在加載數據的時候在 runGenerators()runWrapped()getNextStage() 三個方法中被修改。一般它的邏輯是,先從(大小、尺寸等)轉換以後的緩存中拿數據,若是沒有的話再從沒有轉換過的緩存中拿數據,最後仍是拿不到的話就從原始的數據源中加載數據。

以上就是 DecodeJob 中的狀態模式運行的原理。

對於一個新的任務,會在 DecodeJobinit() 方法中將 runReason 置爲 INITIALIZE,因此,咱們首先會進入到上述 switch 中的 INITIALIZE 中執行。而後,由於咱們沒有設置過磁盤緩存的策略,所以會使用默認的 AUTOMATIC 緩存方式。因而,咱們將會按照上面所說的依次從各個緩存中拿數據。因爲咱們是第一次加載,而且暫時咱們不考慮緩存的問題,因此,最終數據的加載會交給 SourceGenerator 進行。

不知道你是否還記得上一篇文章中咱們在講解在 Glide 中使用 OkHttp 時提到的相關的類。它們真正做用的地方就在下面的這個方法中。這是 SourceGeneratorstartNext() 方法,它會:

  1. 先使用 DecodeHelpergetLoadData() 方法從註冊的映射表中找出當前的圖片類型對應的 ModelLoader
  2. 而後使用它的 DataFetcherloadData() 方法從原始的數據源中加載數據。
public boolean startNext() {
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      cacheData(data);
    }

    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
      return true;
    }
    sourceCacheGenerator = null;

    loadData = null;
    boolean started = false;
    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(helper.getPriority(), this);
      }
    }
    return started;
  }
複製代碼

因爲咱們的圖片時網絡中的資源,在默認狀況下會使用 Glide 內部的 HttpUrlFetcher 從網絡中加載數據。其 loadData() 方法定義以下:

public void loadData(Priority priority, DataCallback<? super InputStream> callback) {
    try {
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
      callback.onDataReady(result);
    } catch (IOException e) {
      callback.onLoadFailed(e);
    } finally {
    }
  }
複製代碼

很明顯,這裏從網絡中打開輸入流以後獲得了一個 InputStream 以後就使用回調將其返回了。至於 loadDataWithRedirects() 方法的實現,就是使用 HttpURLConnection 打開網絡流的過程,這裏咱們不進行詳細的說明了。

3.2.2 小結

階段2:打開網絡流的過程

這樣,into() 方法的第二個階段,即從網絡中獲取一個輸入流的過程就分析完畢了。整個過程並不算複雜,主要是在 DecodeJob 中的狀態模式可能一開始看不太懂,還有就是其中涉及到的一些類不清楚其做用。若是你存在這兩個疑惑的話,那麼建議你:1).耐心思考下狀態模式的轉換過程;2).翻下上一篇文章瞭解自定義 Glide 圖片加載方式的幾個類的設計目的;3).最重要的,多看源碼。

3.3 階段3:將輸入流轉換爲 Drawable 的過程

3.3.1 轉換 Drawable 的過程

在上面的小節中咱們已經打開了網絡流,按照 Android 自身提供的 BitmapFactory,咱們能夠很容易地從輸入流中獲得 Drawable 不是?那麼爲何這個轉換的過程還要單獨分爲一個階段呢?

實際上,這裏的轉換過程並不比上面打開輸入流的過程簡單多少。這是由於它涉及轉碼和將圖片轉換成適合控件大小的過程。好了,下面就讓咱們來具體看一下這個過程都發生了什麼吧!

首先,從上面的 loadData(),咱們能夠看出當獲得了輸入流以後會回調 onDataReady() 方法。這個方法會一直從 HttpUrlFetcher 中一直回調到 SourceGenerator 中。這裏它會使用默認的磁盤緩存策略判斷數據是否能夠緩存,並決定對數據進行緩存仍是繼續回調。

public void onDataReady(Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
      dataToCache = data;
      cb.reschedule(); // 1
    } else {
      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
          loadData.fetcher.getDataSource(), originalKey);
    }
  }
複製代碼

由於咱們的數據是使用 HttpUrlFetcher 加載的,因此將會進入到 1 處繼續進行處理。此時,DecodeJob 將會根據當前的狀態從 run() 方法開始執行一遍,並再次調用 DataCacheGeneratorstartNext() 方法。可是,這次與上一次不一樣的地方在於,此次已經存在能夠用於緩存的數據了。因此,下面的方法將會被觸發:

private void cacheData(Object dataToCache) {
    try {
      Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
      DataCacheWriter<Object> writer =
          new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
      originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
      helper.getDiskCache().put(originalKey, writer);
    } finally {
      loadData.fetcher.cleanup();
    }

    sourceCacheGenerator =
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
  }
複製代碼

這裏的主要邏輯是構建一個用於將數據緩存到磁盤上面的 DataCacheGeneratorDataCacheGenerator 的流程基本與 SourceGenerator 一致,也就是根據資源文件的類型找到 ModelLoader,而後使用 DataFetcher 加載緩存的資源。與以前不一樣的是,此次是用 DataFecher 來加載 File 類型的資源。也就是說,當咱們從網絡中拿到了數據以後 Glide 會先將其緩存到磁盤上面,而後再從磁盤上面讀取圖片並將其顯示到控件上面。因此,當從網絡打開了輸入流以後 SourceGenerator 的任務基本結束了,然後的顯示的任務都由 DataCacheGenerator 來完成。

HttpUrlFetcher 同樣,File 類型的資源將由 ByteBufferFetcher 來加載,當它加載完畢以後也也會回調 onDataReady() 方法。此時,將會調用 DataCacheGeneratoronDataReady()

public void onDataReady(Object data) {
    cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.DATA_DISK_CACHE, sourceKey);
  }
複製代碼

該方法會繼續回調到 DecodeJobonDataFetcherReady() 方法,後續的邏輯比較清晰,只是在不斷繼續調用方法,咱們依次給出這些方法:

// DecodeJob#onDataFetcherReady()
  public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher, DataSource dataSource, Key attemptedKey) {
    // ... 賦值,略
    if (Thread.currentThread() != currentThread) {
      runReason = RunReason.DECODE_DATA;
      callback.reschedule(this);
    } else {
      try {
        // decode 數據以獲得期待的資源類型
        decodeFromRetrievedData();
      } finally {
        GlideTrace.endSection();
      }
    }
  }

  // DecodeJob#decodeFromRetrievedData()
  private void decodeFromRetrievedData() {
    Resource<R> resource = null;
    try {
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);
    } catch (GlideException e) {
      // ... 異常處理
    }
    // ... 釋放資源和錯誤重試等
  }

  // DecodeJob#decodeFromData()
  private <Data> Resource<R> decodeFromData(DataFetcher<?> fetcher, Data data, DataSource dataSource) throws GlideException {
    try {
      // ... 略
      Resource<R> result = decodeFromFetcher(data, dataSource);
      return result;
    } finally {
      fetcher.cleanup();
    }
  }

  // DecodeJob#decodeFromFetcher()
  private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource) throws GlideException {
    LoadPath<Data, ?, R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());
    return runLoadPath(data, dataSource, path);
  }

  // DecodeJob#runLoadPath()
  private <Data, ResourceType> Resource<R> runLoadPath(Data data, DataSource dataSource, LoadPath<Data, ResourceType, R> path) throws GlideException {
    // ... 獲取參數信息
    try {
      // 使用 LoadPath 繼續處理
      return path.load(
          rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));
    } finally {
      rewinder.cleanup();
    }
  }

  // LoadPath#load()
  public Resource<Transcode> load(DataRewinder<Data> rewinder, @NonNull Options options, int width, int height, DecodePath.DecodeCallback<ResourceType> decodeCallback) throws GlideException {
    try {
      // 繼續加載
      return loadWithExceptionList(rewinder, options, width, height, decodeCallback, throwables);
    } finally {
      listPool.release(throwables);
    }
  }

  // LoadPath#loadWithExceptionList()
  private Resource<Transcode> loadWithExceptionList(/*各類參數*/) throws GlideException {
    Resource<Transcode> result = null;
    for (int i = 0, size = decodePaths.size(); i < size; i++) {
      DecodePath<Data, ResourceType, Transcode> path = decodePaths.get(i);
      try {
        // 使用 DecodePath 繼續處理
        result = path.decode(rewinder, width, height, options, decodeCallback);
      } catch (GlideException e) {
        exceptions.add(e);
      }
      if (result != null) {
        break;
      }
    }
    return result;
  }
複製代碼

通過了上面的一系列猛如虎的操做以後,咱們進入了 loadWithExceptionList() 方法,這裏會對 DecodePath 進行過濾,以獲得咱們指望的圖片的類型。這個方法中調用了 DecodePathdecode() 方法。這個方法比較重要,它像一個岔路口:1 處的代碼是將數據轉換成咱們指望的圖片的過程;2 處的代碼是當獲得了指望的圖片以後對處理繼續處理並顯示的過程。

// DecodePath#decode()
  public Resource<Transcode> decode(DataRewinder<DataType> rewinder, int width, int height, Options options, DecodeCallback<ResourceType> callback) throws GlideException {
    Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options); // 1
    Resource<ResourceType> transformed = callback.onResourceDecoded(decoded); // 2
    return transcoder.transcode(transformed, options);
  }
複製代碼

而後,讓咱們繼續沿着 decodeResource() 走。它會調用下面的這個循環對當前的數據類型和指望的、最終的圖片類型匹配從而決定用來繼續處理的 ResourceDecoder

private Resource<ResourceType> decodeResourceWithList(/*各類參數*/) throws GlideException {
    Resource<ResourceType> result = null;
    for (int i = 0, size = decoders.size(); i < size; i++) {
      ResourceDecoder<DataType, ResourceType> decoder = decoders.get(i);
      try {
        DataType data = rewinder.rewindAndGet();
        if (decoder.handles(data, options)) {
          data = rewinder.rewindAndGet();
          result = decoder.decode(data, width, height, options);
        }
      } catch (IOException | RuntimeException | OutOfMemoryError e) {
        exceptions.add(e);
      }

      if (result != null) {
        break;
      }
    }
    return result;
  }
複製代碼

ResourceDecoder 具備多個實現類,好比 BitmapDrawableDecoderByteBufferBitmapDecoder等。從名字也能夠看出來是用來將一個類型轉換成另外一個類型的。

在咱們的程序中會使用 ByteBufferBitmapDecoder 來將 ByteBuffer 專成 Bitmap。它最終會在 DownsamplerdecodeStream() 方法中調用 BitmapFactorydecodeStream() 方法來從輸入流中獲得 Bitmap。(咱們的 ByteBufferByteBufferBitmapDecoder 中先被轉換成了輸入流。)

private static Bitmap decodeStream(InputStream is, BitmapFactory.Options options, DecodeCallbacks callbacks, BitmapPool bitmapPool) throws IOException {
    // ... 略
    TransformationUtils.getBitmapDrawableLock().lock();
    try {
      result = BitmapFactory.decodeStream(is, null, options);
    } catch (IllegalArgumentException e) {
      // ... 錯誤處理,略
    } finally {
      TransformationUtils.getBitmapDrawableLock().unlock();
    }

    if (options.inJustDecodeBounds) {
      is.reset();
    }
    return result;
  }
複製代碼

這樣剩下的就只有不斷繼續向上回調或者返回,最終回到了咱們上面所說的岔路口。這樣從輸入流中加載圖片的邏輯就結束了:)

3.3.2 小結

階段3:將輸入流轉換爲 Drawable 的過程

怎麼樣,是否是以爲這個過程比打開輸入流的過程複雜多了?畢竟這個部分涉及到了從緩存當中取數據以及向緩存寫數據的過程,算的上是核心部分了。總體而言,這部分的設計仍是很是巧的,即便用了狀態模式,根據當前的狀態來決定下一個 Generator。從網絡中拿到輸入流以後又使用 DataCacheGenerator 從緩存當中讀取數據,這個過程連我第一次讀源碼的時候都沒發現,以致於後來調試驗證了推理以後才確信這部分是這樣設計的……

3.4 階段4:將 Drawable 展現到 ImageView 的過程

根據上面的分析,咱們已經從網絡中獲得了圖片數據,而且已經將其放置到了緩存中,又從緩存當中取出數據進行準備進行顯示。上面的過程比較複雜,下面將要出場的這個階段也並不輕鬆……

3.4.1 最終展現圖片的過程

在上面分析中,咱們已經進入到了以前所謂的岔路口,這裏咱們再給出這個方法的定義以下。上面的分析到了代碼 1 處,如今咱們繼續從代碼 2 處進行分析。

// DecodePath#decode()
  public Resource<Transcode> decode(DataRewinder<DataType> rewinder, int width, int height, Options options, DecodeCallback<ResourceType> callback) throws GlideException {
    Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options); // 1
    Resource<ResourceType> transformed = callback.onResourceDecoded(decoded); // 2
    return transcoder.transcode(transformed, options); // 3
  }
複製代碼

這裏會調用 callback 的方法進行回調,它最終會回調到 DecodeJobonResourceDecoded() 方法。其主要的邏輯是根據咱們設置的參數進行變化,也就是說,若是咱們使用了 centerCrop 等參數,那麼這裏將會對其進行處理。這裏的 Transformation 是一個接口,它的一系列的實現都是對應於 scaleType 等參數的。

<Z> Resource<Z> onResourceDecoded(DataSource dataSource, Resource<Z> decoded) {
    Class<Z> resourceSubClass = (Class<Z>) decoded.get().getClass();
    Transformation<Z> appliedTransformation = null;
    Resource<Z> transformed = decoded;
    // 對獲得的圖片資源進行變換
    if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
      appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
      transformed = appliedTransformation.transform(glideContext, decoded, width, height);
    }
    if (!decoded.equals(transformed)) {
      decoded.recycle();
    }

    // ... 緩存相關的邏輯,略
    return result;
  }
複製代碼

在上面的方法中對圖形進行變換以後還會根據圖片的緩存策略決定對圖片進行緩存。而後這個方法就直接返回了咱們變換以後的圖象。這樣咱們就又回到了以前的岔路口。程序繼續執行就到了岔路口方法的第 3 行。這裏還會使用 BitmapDrawableTranscodertranscode() 方法返回 Resouces<BitmapDrawable>。只是這裏會使用 BitmapDrawableTranscoder 包裝一層,即作了延遲初始化處理。

這樣,當第 3 行方法也執行完畢,咱們的岔路口方法就分析完了。而後就是不斷向上 return 進行返回。因此,咱們又回到了 DecodeJobdecodeFromRetrievedData() 方法以下。這裏會進入到下面方法的 1 處來完成最終的圖片顯示操做。

private void decodeFromRetrievedData() {
    Resource<R> resource = null;
    try {
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);
    } catch (GlideException e) {
      throwables.add(e);
    }
    if (resource != null) {
      notifyEncodeAndRelease(resource, currentDataSource); // 1
    } else {
      runGenerators();
    }
  }
複製代碼

接着程序會達到 DecodeJobonResourceReady() 方法以下。由於達到下面的方法的過程的邏輯比較簡單,咱們就不貼出這部分的代碼了。

public void onResourceReady(Resource<R> resource, DataSource dataSource) {
    this.resource = resource;
    this.dataSource = dataSource;
    MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
  }
複製代碼

這裏會獲取到一個消息並將其發送到 Handler 中進行處理。當 Handler 收到消息以後會調用 EncodeJobhandleResultOnMainThread() 方法繼續處理:

void handleResultOnMainThread() {
    // ... 略
    engineResource = engineResourceFactory.build(resource, isCacheable);
    hasResource = true;

    engineResource.acquire();
    listener.onEngineJobComplete(this, key, engineResource);

    for (int i = 0, size = cbs.size(); i < size; i++) {
      ResourceCallback cb = cbs.get(i);
      if (!isInIgnoredCallbacks(cb)) {
        engineResource.acquire();
        cb.onResourceReady(engineResource, dataSource); // 1
      }
    }
    engineResource.release();

    release(false /*isRemovedFromQueue*/);
  }
複製代碼

通過一系列的判斷以後程序進入到代碼 1 處,而後繼續進行回調。這裏的 cb 就是 SingeleRequest

程序到了 SingleRequest 的方法中以後在下面的代碼 1 處回調 Target 的方法。而這裏的 Target 就是咱們以前所說的 ImageViewTarget.

private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
    boolean isFirstResource = isFirstReadyResource();
    status = Status.COMPLETE;
    this.resource = resource;

    isCallingCallbacks = true;
    try {
      // ... 略

      if (!anyListenerHandledUpdatingTarget) {
        Transition<? super R> animation =
            animationFactory.build(dataSource, isFirstResource);
        target.onResourceReady(result, animation); // 1
      }
    } finally {
      isCallingCallbacks = false;
    }

    notifyLoadSuccess();
  }
複製代碼

當程序到了 ImageViewTarget 以後會使用 setResource() 方法最終調用 ImageView 的方法將 Drawable 顯示到控件上面。

protected void setResource(@Nullable Drawable resource) {
    view.setImageDrawable(resource);
  }
複製代碼

這樣,咱們的 Glide 的加載過程就結束了。

3.4.2 小結

階段4:將 Drawable 展現到 ImageView 的過程

上面是咱們將以前獲得的 Drawable 顯示到控件上面的過程。這個方法包含了必定的邏輯,涉及的代碼比較多,可是總體的邏輯比較簡單,因此這部分的篇幅並不長。

四、總結

以上的內容即是咱們的 Glide 加載圖片的整個流程。從文章的篇幅和涉及的代碼也能夠看出,整個完整的過程是比較複雜的。從總體來看,Glide 以前啓動和最終顯示圖片的過程比較簡單、邏輯也比較清晰。最複雜的地方也是核心的地方在於 DecodeJob 的狀態切換。

上面的文章中,咱們重點梳理圖片加載的整個流程,對於圖片緩存和緩存的圖片的加載的過程我沒有作過多的介紹。咱們會在下一篇文章中專門來介紹這部份內容。

以上。

Glide 系列文章:

  1. Glide 系列-1:預熱、Glide 的經常使用配置方式及其原理
  2. Glide 系列-2:主流程源碼分析(4.8.0)
  3. Glide 系列-3:Glide 緩存的實現原理(4.8.0)

若是您喜歡個人文章,能夠在如下平臺關注我:

更多文章:Gihub: Android-notes

相關文章
相關標籤/搜索