Glide源碼探究(三) - 網絡資源加載 ModelLoader的查詢與註冊 DataFetcher LoadPath 總結

系列文章:

接着上篇筆記,按照Glide的流程查詢完內存緩存之後應該算是查詢磁盤緩存,但是由於磁盤緩存的數據依賴第一次請求的時候從網絡下載,再寫入磁盤緩存。所以我這篇先講網絡請求部分的流程。

這部分的代碼坦白講比較多也比較繞,我在看的時候也看的頭暈。這裏就不去把代碼都列出來了,感興趣的同學可以跟着下面的時序圖去追蹤一下:

EngineJob的功能比較簡單,就是管理加載和回調:

/**
 * A class that manages a load by adding and removing callbacks for for the load and notifying
 * callbacks when the load completes.
 */
class EngineJob<R> implements DecodeJob.Callback<R>, Poolable {
    ...
}

它最重要的功能是啓動了DecodeJob去加載資源,DecodeJob是一個運行在子線程的Runnable。它會使用Generator去從緩存或者數據源加載數據,我們這次只看從數據源加載的SourceGenerator。

SourceGenerator在啓動的時候會使用DecodeHelper去獲取一個叫LoadData的東西,這一步比較有意思我們展開講,先看看DecodeHelper.getLoadData的代碼:

List<LoadData<?>> getLoadData() {
    if (!isLoadDataSet) {
        isLoadDataSet = true;
        loadData.clear();
        List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model);

        for (int i = 0, size = modelLoaders.size(); i < size; i++) {
            ModelLoader<Object, ?> modelLoader = modelLoaders.get(i);
            LoadData<?> current = modelLoader.buildLoadData(model, width, height, options);
            if (current != null) {
                loadData.add(current);
            }
        }
    }
    return loadData;
}

loadData是個List,而且DecodeHelper內部做了緩存。它的加載邏輯是先用model(就是我們load傳入的url)從glideContext裏面查詢ModelLoader列表,然後遍歷它去buildLoadData丟到loadData這個列表裏面。

ModelLoader的查詢與註冊

getModelLoaders的邏輯很簡單,就用modelLoaderRegistry去getModelLoaders:

@NonNull
public <Model> List<ModelLoader<Model, ?>> getModelLoaders(@NonNull Model model) {
    return modelLoaderRegistry.getModelLoaders(model);
}

modelLoaderRegistry裏面會根據model的class查詢modelLoaders列表,然後遍歷它去使用ModelLoader.handles方法判斷這個ModelLoader是否支持這個model,如果是的再放到filteredLoaders裏面一起返回。

public <A> List<ModelLoader<A, ?>> getModelLoaders(@NonNull A model) {
    List<ModelLoader<A, ?>> modelLoaders = getModelLoadersForClass(getClass(model));
    ...
    int size = modelLoaders.size();
    boolean isEmpty = true;
    List<ModelLoader<A, ?>> filteredLoaders = Collections.emptyList();
    for (int i = 0; i < size; i++) {
      ModelLoader<A, ?> loader = modelLoaders.get(i);
      if (loader.handles(model)) {
        if (isEmpty) {
          filteredLoaders = new ArrayList<>(size - i);
          isEmpty = false;
        }
        filteredLoaders.add(loader);
      }
    }
    ...
    return filteredLoaders;
}

這個邏輯要怎麼理解呢?舉個例子,就是假設model是一個Uri,那麼getModelLoadersForClass查出來的ModelLoader列表裏面可能有加載本地圖片的也可能有加載網絡圖片的。然後分別調用ModelLoader.handles方法去過濾。如果是個本地請求的Uri,網絡請求的ModelLoader就會被過濾掉,因爲它只支持http和https的scheme:

public class UrlUriLoader<Data> implements ModelLoader<Uri, Data> {
  private static final Set<String> SCHEMES =
      Collections.unmodifiableSet(new HashSet<>(Arrays.asList("http", "https")));
  ...
  @Override
  public boolean handles(@NonNull Uri uri) {
    return SCHEMES.contains(uri.getScheme());
  }
  ...
}

getModelLoadersForClass方法裏面也比較繞,我就不展開代碼了,只要知道它是根據model的class去查詢即可。

有了查詢就肯定有註冊,它的註冊在Glide的構造函數裏面有很長的一坨,第一個參數就是用於對比model class的,第二個參數是DataFetcher的數據類型,最後一個參數是ModelLoader的工廠:

registry
  ...
  .append(String.class, InputStream.class, new StringLoader.StreamFactory())
    ...
    .append(Uri.class, InputStream.class, new UriLoader.StreamFactory(contentResolver))
    .append(Uri.class,ParcelFileDescriptor.class,new UriLoader.FileDescriptorFactory(contentResolver))
    .append(Uri.class,AssetFileDescriptor.class,new UriLoader.AssetFileDescriptorFactory(contentResolver))
    .append(Uri.class, InputStream.class, new UrlUriLoader.StreamFactory())
    .append(URL.class, InputStream.class, new UrlLoader.StreamFactory())
    ...

光看這個流程其實還比較好理解,但是由於我們使用的是String類型的url,當我第一次追蹤代碼的時候還是有被繞暈。原因是String.class註冊的StringLoader自己並不幹活,而是將String轉換成Uri再去registry裏面找人幹活:

public class StringLoader<Data> implements ModelLoader<String, Data> {
    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);
    }

    @Override
    public boolean handles(@NonNull String model) {
        return true;
    }
    ...
    public static class StreamFactory implements ModelLoaderFactory<String, InputStream> {
        public ModelLoader<String, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
          return new StringLoader<>(multiFactory.build(Uri.class, InputStream.class));
        }
        ...
    }
    ...
}

可以看到StreamFactory創建StringLoader的時候調用了multiFactory.build(Uri.class, InputStream.class)方法創建了一個ModelLoader傳給StringLoader。這個build出來的是一個MultiModelLoader,具體細節我也不講了。它從registry查詢了append時候第一個參數爲Uri.class,第二個參數爲InputStream.class的ModelLoader。

StringLoader.handles直接返回true,然後buildLoadData的時候在從這堆ModelLoader使用handles然後build出來。

也就是相當於將String.class的model轉換成了Uri.class類型。但是這樣還沒有完,Uri.class最終又被轉換成了GlideUrl.class,這個我就不展開代碼了...

DataFetcher

得到的ModelLoader回到到DecodeHelper.getLoadData去buildLoadData創建LoadData,就得到了一個LoadData列表。

然後SourceGenerator.startNext裏面就會遍歷這個列表去找到一個能加載資源的LoadData,其中主要幹活的是DataFetcher:

public boolean startNext() {
    ...
    while (!started && hasNextModelLoader()) {
      loadData = helper.getLoadData().get(loadDataListIndex++);
      if (loadData != null
          && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
              || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
        started = true;
        startNextLoad(loadData);
      }
    }
    return started;
}

startNextLoad裏面使用loadData.fetcher啓動資源的加載,完成後回調onDataReadyInternal:

private void startNextLoad(final LoadData<?> toStart) {
    loadData.fetcher.loadData(
        helper.getPriority(),
        new DataCallback<Object>() {
          @Override
          public void onDataReady(@Nullable Object data) {
            if (isCurrentRequest(toStart)) {
              onDataReadyInternal(toStart, data);
            }
          }
        ...);
}

但是值得注意的是Fetcher的加載完成並不是把圖片文件下載完成,只是打開了文件流而已,需要等待後面的流程從裏面讀取:

public class HttpUrlFetcher implements DataFetcher<InputStream> {
    public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
        ...
        InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
        callback.onDataReady(result);
        ...
    }
}

LoadPath

看回時序圖可以知道,DataFetcher的數據是被LoadPath讀取去解碼的。這裏面也很複雜,但是我們並不需要全部瞭解,我這隻講個比較重要的東西。

LoadPath.load方法最終會調用到LoadPath.decodeResourceWithList方法。顧名思義它是遍歷解碼器列表查找一個解碼器去解碼文件:

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);
        ...
        DataType data = rewinder.rewindAndGet();
        if (decoder.handles(data, options)) {
            data = rewinder.rewindAndGet();
            result = decoder.decode(data, width, height, options);
        }
        ...

        if (result != null) {
            break;
        }
    }
    ...
    return result;
  }

這些解碼器是哪裏註冊的呢?答案還是Glide的構造函數裏面那坨很長的append,沒錯那坨append不僅註冊了ModelLoader還註冊瞭解碼器:

registry
        .append(ByteBuffer.class, new ByteBufferEncoder())
        .append(InputStream.class, new StreamEncoder(arrayPool))
        .append(Registry.BUCKET_BITMAP, ByteBuffer.class, Bitmap.class, byteBufferBitmapDecoder)
        .append(Registry.BUCKET_BITMAP, InputStream.class, Bitmap.class, streamBitmapDecoder)
            ...

總結

之後的流程就是不斷回到到Engine去放到弱引用緩存裏面的。到這裏整個網絡資源的下載解碼流程也就講完了,我們來看看簡化之後的時序圖:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章