Glide4.8原始碼拆解(三)Registry和資料轉換流程

HitenDev發表於2019-01-09

前言

Registry是Glide中非常重要的知識,可以把它理解成連結各個核心功能模組的集中營或者掛載中心,這一章節就來分解它是如何建立和運作的:

本章要討論的內容:

  • Registry的基本構成;
  • 各個模組的功能和介紹;
  • 資料的轉換流程;

從Registry開始

Registry是一個元件管理類,它的主要用途是擴充套件和替換Glide元件,這些元件包括載入,編碼,解碼等邏輯;Registry內部支援的模組型別如下: Registry.java

  private final ModelLoaderRegistry modelLoaderRegistry;
  private final EncoderRegistry encoderRegistry;
  private final ResourceDecoderRegistry decoderRegistry;
  private final ResourceEncoderRegistry resourceEncoderRegistry;
  private final DataRewinderRegistry dataRewinderRegistry;
  private final TranscoderRegistry transcoderRegistry;//Resource轉換模組註冊
  private final ImageHeaderParserRegistry imageHeaderParserRegistry;//檔案頭解析模組註冊
複製程式碼

Registry並不是承當所有模組的註冊工作,而是把各個模組分配的不同的Registry當中; 主要模組的功能:

  • ModelLoaderRegistry ://資料載入模組註冊
  • EncoderRegistry://所有對資料進行編碼模組的註冊
  • ResourceDecoderRegistry://處理過的解碼模組註冊
  • ResourceEncoderRegistry://處理過的編碼模組註冊
  • DataRewinderRegistry : //資料流重置起點模組註冊
  • TranscoderRegistry: // Resource進行轉換模組註冊
  • ImageHeaderParserRegistry //圖片頭解析模組註冊

Glide自身充當對外呼叫的門戶,Registry提供了一下入口方法來實現各個模組的註冊和呼叫;主要方法如下:

註冊相關方法:

  • append() //尾步追加
  • prepend() //頭部插入
  • register() //註冊,相當於append()
  • replace() //替換掉相同條件的所有模組

操作相關的方法:

  • getLoadPath()//獲取載入路徑
  • getDecodePaths() //獲取解析路徑
  • getRegisteredResourceClasses()//獲取所有匹配的ResourceClasses;
  • isResourceEncoderAvailable()//ResourceEncoder是否可用;
  • getResultEncoder()//獲取Encoder;
  • getRewinder()//獲取Rewinder;
  • getModelLoaders()//獲取ModelLoader;

總結:Registry通過內部Registry分別管理不同型別的元件,Registry提供統一的入口方法來實現註冊和獲取;

下面對各個模組基本介紹:

模組簡要分析

ModelLoader

ModelLoader是通過ModelLoaderRegistry進行管理,ModelLoader需要接受兩個泛型型別<Model,Data>ModelLoader本身是一個工廠介面,主要工作是將複雜資料模型轉通過DataFetcher轉換成需要的Data,LoadData是ModelLoader的內部類,是對DataFetcher和Key的封裝實體,ModelLoader的建立用ModelLoaderFactory,一個基本的ModelLoader建立應該是這個樣子的: 參考HttpGlideUrlLoader

第一步:自定義類實現自ModelLoader,重寫BuildLoadData()方法和handles()方法;

public class HttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {

  @Nullable private final ModelCache<GlideUrl, GlideUrl> modelCache;

  public HttpGlideUrlLoader() {
    this(null);
  }

  public HttpGlideUrlLoader(@Nullable ModelCache<GlideUrl, GlideUrl> modelCache) {
    this.modelCache = modelCache;
  }

  @Override
  public LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height,
      @NonNull Options options) {
    GlideUrl url = model;
    if (modelCache != null) {
      url = modelCache.get(model, 0, 0);
      if (url == null) {
        modelCache.put(model, 0, 0, model);
        url = model;
      }
    }
    int timeout = options.get(TIMEOUT);
    return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
  }

  @Override
  public boolean handles(@NonNull GlideUrl model) {
    return true;
  }
 }
複製程式碼

buildLoadData()需要建立LoadData,需要傳入Key和DataFetcher,handles()返回值代表是否接受當前model型別的,true代表接受,所以一般都是true;

第二步:自定義Fetcher實現DataFecher,重寫loadData()、cleanup()、cancel()、getDataClass()、getDataSource()方法;

public class HttpUrlFetcher implements DataFetcher<InputStream> {
 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) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Failed to load data for url", e);
      }
      callback.onLoadFailed(e);
    } finally {
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
      }
    }
  }
  //清理工作
  @Override
  public void cleanup() {
    if (stream != null) {
      try {
        stream.close();
      } catch (IOException e) {
    
      }
    }
    if (urlConnection != null) {
      urlConnection.disconnect();
    }
    urlConnection = null;
  }
  //取消請求
  @Override
  public void cancel() {
    isCancelled = true;
  }
  //返回Data型別
  @NonNull
  @Override
  public Class<InputStream> getDataClass() {
    return InputStream.class;
  }
  //返回DataSource
  @NonNull
  @Override
  public DataSource getDataSource() {
    return DataSource.REMOTE;
  }
}

複製程式碼

第三步:建立Factory類,重寫build()、teardown()方法,在build()方法中返回真正的MolderLoader物件;

public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
    private final ModelCache<GlideUrl, GlideUrl> modelCache = new ModelCache<>(500);

    @NonNull
    @Override
    public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
      return new HttpGlideUrlLoader(modelCache);//建立ModelLoader
    }

    @Override
    public void teardown() {
      // Do nothing.
    }
  }
複製程式碼

接下來,就可以在Registry中註冊ModelLoader了;在第一章我們簡單說過模組的配置可以用Annotation和Manifest兩種型別,在registerComponents()方法中,可以拿到Registry,這樣就可以呼叫registry的註冊相關方法;

@Override
  public void registerComponents(Context context, Registry registry) {
    registry.replace(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory(context));
  }
複製程式碼

最後,說一下泛型<Model,Data>接受的範圍,Model代表使用者輸入的型別,理論上可以用任意資料型別,主要的輸入點在Glide.with().load(model); Data理論上也可以是任意資料型別,但基於後續流程的支援,一般都是File,InputStreamByteBuffer;

ResourceDecoder

ResourceDecoder是一個解析Resource的介面,接受兩個泛型<T,Z>,定義了兩個方法handles()decode,參考一個簡單的BitmapDecoder:

public class StreamBitmapDecoder implements ResourceDecoder<InputStream, Bitmap> {

  private final Downsampler downsampler;
  private final ArrayPool byteArrayPool;

  public StreamBitmapDecoder(Downsampler downsampler, ArrayPool byteArrayPool) {
    this.downsampler = downsampler;
    this.byteArrayPool = byteArrayPool;
  }

  @Override
  public boolean handles(@NonNull InputStream source, @NonNull Options options) {
    return downsampler.handles(source);
  }
   @Override
  public Resource<Bitmap> decode(@NonNull InputStream source, int width, int height,
      @NonNull Options options)
      throws IOException {
    ...
    try {
      return downsampler.decode(invalidatingStream, width, height, options, callbacks);
    } finally {
      exceptionStream.release();
      if (ownsBufferedStream) {
        bufferedStream.release();
      }
    }
  }
 }
複製程式碼

泛型解析:T表示輸入型別一般為FileInputStream或者ByteBufferZ表示輸出型別,一般為BitmapDrawable

Encoder和ResourceEncoder

Encoder表面意思為加密,本質上和加密沒有關係,主要作用是將T持久化到本地cache;Encode接受泛型T,而這個T可以是InputStreamByteBufferResource<T>ResourceEncoder繼承Encoder<Resource<T>>,接受泛型T,而Resource中的T一般取值範圍為:BitmapBitmapDrawableGifDrawable;

我們分析一下把Bitmap持久化到本地cache的類:BitmapEncoder

public class BitmapEncoder implements ResourceEncoder<Bitmap> {
  @Override
  public boolean encode(@NonNull Resource<Bitmap> resource, @NonNull File file,
      @NonNull Options options) {
    final Bitmap bitmap = resource.get();
    Bitmap.CompressFormat format = getFormat(bitmap, options);
    GlideTrace.
        beginSectionFormat("encode: [%dx%d] %s", bitmap.getWidth(), bitmap.getHeight(), format);
    try {
      long start = LogTime.getLogTime();
      int quality = options.get(COMPRESSION_QUALITY);

      boolean success = false;
      OutputStream os = null;
      try {
        os = new FileOutputStream(file);
        if (arrayPool != null) {
          os = new BufferedOutputStream(os, arrayPool);
        }
        bitmap.compress(format, quality, os);
        os.close();
        success = true;
      } catch (IOException e) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
          Log.d(TAG, "Failed to encode Bitmap", e);
        }
      } finally {
        if (os != null) {
          try {
            os.close();
          } catch (IOException e) {
            // Do nothing.
          }
        }
      }

      return success;
    } finally {
      GlideTrace.endSection();
    }
  }
}

複製程式碼

BitmapEncoder重寫encode()方法,該方法中入參file代表將要儲存的cache檔案;encode基本流程是:根據File得到輸出流OutPutStream,呼叫Bitmap.compress將bitmap寫入輸出流,然後關閉流等等處理;

DataRewinder

DataRewinder是一個介面,作用是將流進行rewinding,主要的方法是rewindAndGet();Glide中有兩個有意義的實現類:InputStreamRewinderByteBufferRewinder,簡單看一下這兩個類是實現:

ByteBufferRewinder.java

public class ByteBufferRewinder implements DataRewinder<ByteBuffer> {
  private final ByteBuffer buffer;

  @SuppressWarnings("WeakerAccess")
  public ByteBufferRewinder(ByteBuffer buffer) {
    this.buffer = buffer;
  }

  @NonNull
  @Override
  public ByteBuffer rewindAndGet() {
    buffer.position(0);
    return buffer;
  }

  @Override
  public void cleanup() {
    // Do nothing.
  }
複製程式碼

InputStreamRewinder.java

public final class InputStreamRewinder implements DataRewinder<InputStream> {
  // 5mb.
  private static final int MARK_LIMIT = 5 * 1024 * 1024;

  private final RecyclableBufferedInputStream bufferedStream;

  @Synthetic
  InputStreamRewinder(InputStream is, ArrayPool byteArrayPool) {
    bufferedStream = new RecyclableBufferedInputStream(is, byteArrayPool);
    bufferedStream.mark(MARK_LIMIT);
  }

  @NonNull
  @Override
  public InputStream rewindAndGet() throws IOException {
    bufferedStream.reset();
    return bufferedStream;
  }

  @Override
  public void cleanup() {
    bufferedStream.release();
  }
複製程式碼

ResourceTranscoder

ResourceTranscoder是一個介面,作用是對兩個Resource<?>進行轉換,接受泛型<Z,R>,主要方法是transcode(),一般泛型的接收範圍是BitmapDrawablebyte[]等,看一下簡單的BitmapBytesTranscoder原始碼:

public class BitmapBytesTranscoder implements ResourceTranscoder<Bitmap, byte[]> {
  private final Bitmap.CompressFormat compressFormat;
  private final int quality;

  public BitmapBytesTranscoder() {
    this(Bitmap.CompressFormat.JPEG, 100);
  }

  @SuppressWarnings("WeakerAccess")
  public BitmapBytesTranscoder(@NonNull Bitmap.CompressFormat compressFormat, int quality) {
    this.compressFormat = compressFormat;
    this.quality = quality;
  }

  @Nullable
  @Override
  public Resource<byte[]> transcode(@NonNull Resource<Bitmap> toTranscode,
      @NonNull Options options) {
    ByteArrayOutputStream os = new ByteArrayOutputStream();
    toTranscode.get().compress(compressFormat, quality, os);
    toTranscode.recycle();
    return new BytesResource(os.toByteArray());
  }
}

複製程式碼

載入/解析 資料轉換流程

上面介紹一堆元件和一堆泛型,資料型別的轉換到底是怎樣?搞明白這一點還得從Rigistry提供的操作方法入手:

Registry提供getModelLoaders()getLoadPath(),我們先從定義方法的泛型來看:

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

public <Data, TResource, Transcode> LoadPath<Data, TResource, Transcode> getLoadPath(
      @NonNull Class<Data> dataClass, @NonNull Class<TResource> resourceClass,
      @NonNull Class<Transcode> transcodeClass) {...}
複製程式碼

getModelLoader()分析

getModelLoaders()入參型別為<Model>,返回型別為<Model,?><Model>具體型別就是我們呼叫Glide.with().load(model)load()傳入的型別,返回型別<?>是我們在Registry中註冊的所有符合輸入<Model>的型別,比如InputStream或者ByteBuffer

LoadPath()分析

LoadPath()入參型別為<Data, TResource, Transcode>,其中<Data>是在getModelLoaders()返回的型別,例如InputStream或者ByteBuffer<TResource>是待定型別,呼叫者一般傳?,<Transcode>呼叫Glide.with().as(xxx)as()傳入的型別,Glide提供有asBitmap(),asFile(),asGif(),預設是Drawable型別;在呼叫時<TResource>是待定型別,肯定有邏輯獲取它的目標型別,下面分析getLoadPath()方法一看究竟;

  public <Data, TResource, Transcode> LoadPath<Data, TResource, Transcode> getLoadPath(
      @NonNull Class<Data> dataClass, @NonNull Class<TResource> resourceClass,
      @NonNull Class<Transcode> transcodeClass) {
    LoadPath<Data, TResource, Transcode> result =
        loadPathCache.get(dataClass, resourceClass, transcodeClass);
    if (loadPathCache.isEmptyLoadPath(result)) {
      return null;
    } else if (result == null) {
      List<DecodePath<Data, TResource, Transcode>> decodePaths =
          getDecodePaths(dataClass, resourceClass, transcodeClass);
      if (decodePaths.isEmpty()) {
        result = null;
      } else {
        result =
            new LoadPath<>(
                dataClass, resourceClass, transcodeClass, decodePaths, throwableListPool);
      }
      loadPathCache.put(dataClass, resourceClass, transcodeClass, result);
    }
    return result;
  }
複製程式碼

LoadPath()方法從loadPathCache獲取快取物件,如果不存在,呼叫getDecodePaths(),經過判斷,建立LoadPath物件,將獲取的結果放入LoadPath,最後放入loadPathCache並返回,LoadPath是對Data,TResource,TranscodeList<DecodePath<Data, TResource, Transcode>>的封裝,最終的邏輯還是再DecodePath中;

看一下getDecodePaths()方法定義:

private <Data, TResource, Transcode> List<DecodePath<Data, TResource, Transcode>> getDecodePaths(
      @NonNull Class<Data> dataClass, @NonNull Class<TResource> resourceClass,
      @NonNull Class<Transcode> transcodeClass) {
    List<DecodePath<Data, TResource, Transcode>> decodePaths = new ArrayList<>();
     //獲取所有dataClass對應的ResourceClasses
    List<Class<TResource>> registeredResourceClasses =
        decoderRegistry.getResourceClasses(dataClass, resourceClass);**程式碼1
    //遍歷registeredResourceClass
    for (Class<TResource> registeredResourceClass : registeredResourceClasses) {
        //獲取所有的registeredResourceClass對應的registeredTranscodeClasses
      List<Class<Transcode>> registeredTranscodeClasses =
          transcoderRegistry.getTranscodeClasses(registeredResourceClass, transcodeClass);**程式碼2
      //遍歷registeredTranscodeClasses
      for (Class<Transcode> registeredTranscodeClass : registeredTranscodeClasses) {
        //獲取dataClass和registeredResourceClass對應的所有ResourceDecoder
        List<ResourceDecoder<Data, TResource>> decoders =
            decoderRegistry.getDecoders(dataClass, registeredResourceClass);**程式碼3
         //獲取registeredResourceClass和registeredTranscodeClasss對應的所有ResourceTranscoder
        ResourceTranscoder<TResource, Transcode> transcoder =
            transcoderRegistry.get(registeredResourceClass, registeredTranscodeClass);**程式碼4
        @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
        //建立DecodePath,把相關資訊封裝
        DecodePath<Data, TResource, Transcode> path =
            new DecodePath<>(dataClass, registeredResourceClass, registeredTranscodeClass,
                decoders, transcoder, throwableListPool);
        //新增進集合
        decodePaths.add(path);
      }
    }
    //返回集合
    return decodePaths;
  }
複製程式碼

為了更清楚的分析程式碼,我可以將假設泛型的型別為<InputStream,?,Drawable>

我在上面程式碼中做了標記:程式碼1通過呼叫transcoderRegistry.getTranscodeClasses(),返回的型別就是泛型?所未知的具體型別;

程式碼2通過呼叫transcoderRegistry.getTranscodeClasses(),返回所有符合條件的registeredTranscodeClasses;

程式碼3通過呼叫decoderRegistry.getDecoders()獲取符合條件的List<ResourceDecoder>;

程式碼4通過呼叫transcoderRegistry.get()獲取符合條件的ResourceTranscoder

DecodePath是對Data, TResource, Transcode,decoders,transcoder的封裝;

第一層for迴圈理解 由於<TResource>是入參是未知型別,並不是使用者定義的,是Registry模組支援的中間型別,它是靠入參型別<Data>進行篩選,所以就可能有可能有多個匹配;

第二層for迴圈理解 因為<Transcode>是使用者傳入,這個泛型是一個已確定型別,通常是Drawable,但是真正註冊給transcoderRegistry可能是BitmapDrawable或則BitmapDrawable型別,這一刻還不確定是哪個Drawable,所以在這一步,registry返回給呼叫者多個;

總結:

載入過程是從getModelLoader()呼叫,資料從Model->Data;

解析過程是從getLoadPath()呼叫,中間經過decoder、transcoder,資料型別從Data->TResource->Transcode

寫快取資料轉換流程

寫快取過程分為兩類,一類是直接將原資料快取,另一類是將變化後的資料寫快取,他們分別對應的是EncoderResourceEncoder;

Encoder流程

Encoder的使用場景在SourceGenerator.cacheData(dataToCache)方法中,最終通過呼叫Registry.getSourceEncoder()獲取到Encode;

  public <X> Encoder<X> getSourceEncoder(@NonNull X data) throws NoSourceEncoderAvailableException {
    Encoder<X> encoder = encoderRegistry.getEncoder((Class<X>) data.getClass());
    if (encoder != null) {
      return encoder;
    }
    throw new NoSourceEncoderAvailableException(data.getClass());
  }
複製程式碼

上一節說過SourceGenerator是對原資料的獲取,cacheData()中拿到的 dataToCache一般是載入過程返回的Data,確切的說是InputStream或者ByteBuffer型別,而Encoder最終儲存到檔案,型別為File,所以Encoder資料是從Data->File的;執行的時機在載入之後,和解析過程並列執行;

ResourceEncoder流程

ResourceEncoder的使用場景是在資料解析完畢後,將處理過的資料進行快取,呼叫的地方在DecodeJob.onResourceDecoded()方法中,其最終通過呼叫Registry.getResultEncoder()獲取;

public <X> ResourceEncoder<X> getResultEncoder(@NonNull Resource<X> resource)
      throws NoResultEncoderAvailableException {
    ResourceEncoder<X> resourceEncoder = resourceEncoderRegistry.get(resource.getResourceClass());
    if (resourceEncoder != null) {
      return resourceEncoder;
    }
    throw new NoResultEncoderAvailableException(resource.getResourceClass());
  }

複製程式碼

ResourceEncoder資料型別是從Resource<X>->File;執行的時機在解析流程之後;

總結

最後畫一張資料簡單的資料裝換流程圖

Glide4.8原始碼拆解(三)Registry和資料轉換流程

相關文章