Android 圖片載入庫Glide知其然知其所以然之載入

Drummor發表於2019-05-10

前言

距上篇Glide分析的文章已經一個月了,實際分析起來Glide很複雜,對這種複雜進行形容的話,那就是“成噸的複雜”一點也不為過。
沒看過上篇的小夥伴可以看下

Android圖片載入庫Glide 知其然知其所以然 開篇

image
這次我從Glide的建立開始談起,然後著重聊下,DataLoader這個環節,在此之前我們先搞明白,Glide是怎樣把圖片載入這個過程拆分解耦的。先說結論他的加載入主要分為這樣幾個過程:

  • 載入資料:Model轉Data
    將複雜不可確定的Model轉換成能夠直接編碼處理的Data,這裡對Model包括String Uri URL等就是我們使用Glide.with(this).load(xxx)裡面的xxx;Data表示的是能夠被編碼器識別的資料,包括byte[] 位元組陣列,InputStream位元組流,File檔案等等。
  • 編碼:Data轉Resource 這個就是將url String file等通過網路載入檔案讀取等方式轉換最終載入成檔案,Bitmap,Drawable,Gif等的過程。
  • 轉碼:Resource 轉Resouce 獲取到的資源直接能夠靈活的轉換,包括bitmap轉成位元組陣列,位元組陣列轉成bitmap轉Drawable,gif轉位元組陣列等。

去做Model轉Data 、Data轉Resource、Resource互轉工作的類分別是一次分別是ModelLoader<Model, Data> 、ResourceDecoder<T, Z>以及ResourceTranscoder<Z, R> 介面的實現。

另外有了解碼也就是從檔案、url、等解析成資源的過程也會設計到將Resource轉成Flile檔案的過程,做這個工作的interface Encoder 介面的實現類。

想必大家都聽說過“高內聚,低耦合”,“上層不應該依賴於底層而是應該依賴與抽象”這樣的說法。 Glide把剛剛上面我描述的這些功能採用了一種序號產生器制進行架構。在Glide的這個類構造方法裡有這些的程式碼

Glide(...){
 registry
        //將byteBuffer轉換為Flile
        .append(ByteBuffer.class, new ByteBufferEncoder())
        //將inputStream轉換為File
        .append(InputStream.class, new StreamEncoder(arrayPool))
        /* Bitmaps */
        
        //ByteBuffer解碼成Bitmap :decoderRegistry
        .append(Registry.BUCKET_BITMAP, ByteBuffer.class, Bitmap.class, byteBufferBitmapDecoder)
        ....
        /* BitmapDrawables */
        .append(
            Registry.BUCKET_BITMAP_DRAWABLE,
            ByteBuffer.class,
            BitmapDrawable.class,
            new BitmapDrawableDecoder<>(resources, byteBufferBitmapDecoder))
        /* GIFs */
        .append(
            Registry.BUCKET_GIF,
            InputStream.class,
            GifDrawable.class,
            new StreamGifDecoder(imageHeaderParsers, byteBufferGifDecoder, arrayPool))
        /* Files */
        .append(File.class, ByteBuffer.class, new ByteBufferFileLoader.Factory())
     FileLoader.FileDescriptorFactory())
        // Compilation with Gradle requires the type to be specified for UnitModelLoader here.
        .append(File.class, File.class, UnitModelLoader.Factory.<File>getInstance())
        /* Models */
        .register(new InputStreamRewinder.Factory(arrayPool))
        .append(int.class, InputStream.class, resourceLoaderStreamFactory)
        .append(
            int.class,
            ParcelFileDescriptor.class,
            resourceLoaderFileDescriptorFactory)
        .append(Integer.class, InputStream.class, resourceLoaderStreamFactory)
...
        /* Transcoders */
        .register(
            Bitmap.class,
            BitmapDrawable.class,
            new BitmapDrawableTranscoder(resources))
        .register(Bitmap.class, byte[].class, bitmapBytesTranscoder)
        .register(
            Drawable.class,
            byte[].class,
            new DrawableBytesTranscoder(
                bitmapPool, bitmapBytesTranscoder, gifDrawableBytesTranscoder))
        .register(GifDrawable.class, byte[].class, gifDrawableBytesTranscoder);
}
複製程式碼

程式碼實在太長我刪減的一部分,總體來講,他就是往裡註冊了ModuleLoader、Encoder、Decoder、Transcoder這四種元件,使其具備轉換模型資料,解析String url等源,解析載入bitmap drawable file用的Decoder,對資源進行轉碼的Transcoder,以及將資源轉成flile的Encoder。這些功能單元都被註冊在了Registry,這個類的註釋是這樣寫的,這其中的架構思想大家自行發散思考。

Manages component registration to extend or replace Glide's default loading, decoding, and encoding logic.

言歸正傳,這篇我們著重分析的就是Loading這個過程,抽象的講就是把複雜的Model(String Url 等)轉換成 Data(byte[] InputStream)過程。上面我們知道了Loading在Glide整個系統中的位置,他的實現是怎樣的呢。

一、ModelLoader

作用將複雜多變抽象的Mode 如String URL 資料轉換為直接被編碼的資料如byte[] inputStream等。

具體實現

public interface ModelLoader<Model, Data> {

  class LoadData<Data> {
    public final Key sourceKey;
    public final List<Key> alternateKeys;
    public final DataFetcher<Data> fetcher;

    public LoadData(@NonNull Key sourceKey, @NonNull DataFetcher<Data> fetcher) {
      this(sourceKey, Collections.<Key>emptyList(), fetcher);
    }

    public LoadData(@NonNull Key sourceKey, @NonNull List<Key> alternateKeys,
        @NonNull DataFetcher<Data> fetcher) {
      this.sourceKey = Preconditions.checkNotNull(sourceKey);
      this.alternateKeys = Preconditions.checkNotNull(alternateKeys);
      this.fetcher = Preconditions.checkNotNull(fetcher);
    }
  }

  /** 給定model 寬高資訊 已經Options生成LoadData<?> **/
  @Nullable
  LoadData<Data> buildLoadData(@NonNull Model model, int width, int height,
      @NonNull Options options);
  boolean handles(@NonNull Model model);
}

複製程式碼

生產LoadDate的地方,LoadData裡面有個成員變數DataFetcher<>

二、 DataFetcher

這個類的物件是載入資料的實際負責人。

public interface DataFetcher<T> {

  interface DataCallback<T> {
    void onDataReady(@Nullable T data);
    void onLoadFailed(@NonNull Exception e);
  }
  //載入資原始檔,這些資原始檔可能是位元組陣列,檔案等,都是用現在抽象T代表
  void loadData(@NonNull Priority priority, @NonNull DataCallback<? super T> callback);
  
  void cleanup();
  
  void cancel();

  @NonNull
  Class<T> getDataClass();

  @NonNull
  DataSource getDataSource();
}

複製程式碼

DataFetcher是一個泛型類,泛型T表示的就是要載入的Data型別。他的功能實現形式是通過回撥的,回撥介面DataCallback,DataCallback定義了兩個方法,onDataReady(T data)和onLoadFailed(Exception e); 見名知意,DataFetcher是獲取資料的抽象,他規定了獲取Data的時候是通過呼叫loadData這個方法,這個方法其中有一個引數是DataCallback,當Data獲取成功會呼叫callBack的onDataReady,當獲取失敗的使時候會呼叫onLoadFailed(Exception e)通知外界。

三、 ModelLoader的生產者,ModelLoaderFactory<T,Y>

我們在往Registry註冊Loader的時候,可以清晰的看到其實往裡新增的主要是三個內容Class Class以及ModelLoaderFactory。沒錯ModelLoaderFactory就是構建ModelLoder的工廠類,前兩個引數指明瞭被處理的資料型別和載入成的資料型別。

.append(File.class, ByteBuffer.class, new ByteBufferFileLoader.Factory())
複製程式碼

ModelLoaderFactory的程式碼。

public interface ModelLoaderFactory<T, Y> {
  ModelLoader<T, Y> build(@NonNull MultiModelLoaderFactory multiFactory);
  void teardown();
}
複製程式碼

值得注意是是在build方法中傳入了MultiModelLoaderFactory一看名字就不簡單啊,“多種模型載入工廠”,不貼程式碼了真相是往Registry物件中註冊的ModelLoaderFactory最終都是儲存在MultiModelLoaderFactory中的。

四、 DataFetcher生產者 DataFetcherGenerator

interface DataFetcherGenerator {

  interface FetcherReadyCallback {
    void reschedule();
    
    void onDataFetcherReady(
        Key sourceKey, @Nullable Object data, 
        DataFetcher<?> fetcher,DataSource dataSource, 
        Key attemptedKey);

    void onDataFetcherFailed(Key attemptedKey, Exception e,
        DataFetcher<?> fetcher,DataSource dataSource);
  }
  boolean startNext();

  void cancel();
}

複製程式碼
  • startNext()方法就是獲取一個DataFetcher 並且開始執行獲取Data操作,如果開始執行了返回true,否則返回false;
  • cancle()取消獲取
  • FetcherReadyCallback介面供呼叫者,onDataFetcherReady和onDataFetcherFailed()兩個方法見名知意不贅述,值得注意的是onDataFetcherReady()方法有個 Object data引數,他的含義是要 能直接編碼的資料Data,這就意味著,當onDataFetcherReady()方法被回撥的時候,不但已經穿件了DataFetcher物件,且DataFetcher物件已經獲取到了資料Data;
  • reschedule()的方法呼叫它能起的作用是重新執行startNext()方法。

DataFetcherGenerator的整合類有三個

  • SourceGenerator (從資料來源中獲取資料,會設計快取策略)
  • DataCacheGenerator(磁碟獲取原始快取資料)
  • ResourceCacheGenerator(磁碟中獲取變化過的資料)

在已經註冊號的Modlerloader中查詢處理

  • 在DecodeHelper提供LoadData物件
DecodeHelper{
    ...
List<LoadData<?>> getLoadData() {
    if (!isLoadDataSet) {
      isLoadDataSet = true;
      loadData.clear();
      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);
        if (current != null) {
          loadData.add(current);
        }
      }
    }
return loadData;
}
    ...
}
複製程式碼

以SourceGenerater為例

startNext方法

  @Override
  public boolean startNext() {
    //當dataToCache不為空的時候,將dataToCache放入記憶體中
    //然後然後使用sourceCahceGenerator獲取Data
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      cacheData(data);
    }

    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
      return true;
    }
    sourceCacheGenerator = null;
    
    loadData = null;
    boolean started = false;
    //獲取能夠用的LoadData物件,然後呼叫它的LoadData()
    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;
  }

複製程式碼

SourceGenerator實現了DataCallBack介面

@Override
  public void onDataReady(Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
      dataToCache = data;
      // We might be being called back on someone else's thread. Before doing anything, we should
      // reschedule to get back onto Glide's thread.
      cb.reschedule();
    } else {
      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
          loadData.fetcher.getDataSource(), originalKey);
    }
  }
複製程式碼

獲取到資料可以被快取的情況下就會將data賦值給dataToCache,如果不能被賦就回撥傳入的FetcherReadyCallback物件的onReadyCallback方法。

image

小結

Model轉Data也就是載入資料這個過程設計到單個核心介面類,ModelLoader、LoadData,DataFetcher。 LoadData是ModelLoader這個泛型介面的一個內部類,DataFetcher物件是ModelLoader的一個成員變數。實際將Model轉換成Data的工作者就是DataFetcher。

DataFetcher資料載入成功後,採用回撥的方式通知呼叫它的物件。

完。

相關文章