[Glide4源碼解析系列] — 3.Glide數(shù)據(jù)解碼與轉(zhuǎn)碼

Glide

Glide4源碼解析系列

[Glide4源碼解析系列]--1.Glide初始化
[Glide4源碼解析系列]--2.Glide數(shù)據(jù)模型轉(zhuǎn)換與數(shù)據(jù)抓取
[Glide4源碼解析系列]--3.Glide數(shù)據(jù)解碼與轉(zhuǎn)碼


一、簡介

1. 寫在前面的廢話

繼上一篇文章[Glide4源碼解析系列]--2.Glide數(shù)據(jù)模型轉(zhuǎn)換與數(shù)據(jù)抓取之后,已經(jīng)過去幾個(gè)月的時(shí)間,期間由于學(xué)習(xí)其他東西和項(xiàng)目的原因(其實(shí)是懶癌發(fā)作~),本文被擱置了很久,期間還有網(wǎng)友私信問什么時(shí)候會把“解碼與轉(zhuǎn)碼”部分寫好,想起曾經(jīng)信誓旦旦要將這個(gè)坑補(bǔ)好,終于愧疚地重新看了Glide源碼,把剩下的部分補(bǔ)上,對默默等待的朋友表示歉意。

2. 承前啟后

上一篇文章,分析了Glide利用其強(qiáng)大的數(shù)據(jù)轉(zhuǎn)換思維,根據(jù)不同類型數(shù)據(jù)的模型和數(shù)據(jù)抓取器的組合,可以實(shí)現(xiàn)對幾乎任意圖片數(shù)據(jù)類型無縫轉(zhuǎn)換。主要的加載流程如下,接下來我們重點(diǎn)來看其中數(shù)據(jù)的解碼和轉(zhuǎn)碼過程。

model(數(shù)據(jù)源)-->data(轉(zhuǎn)換數(shù)據(jù))-->decode(解碼)-->transformed(縮放)-->transcoded(轉(zhuǎn)碼)-->encoded(編碼保存到本地)

二、解碼器與轉(zhuǎn)碼器

上一篇文章,我們以從網(wǎng)絡(luò)上加載一張圖片為例子,分析了整個(gè)數(shù)據(jù)轉(zhuǎn)換的過程,在最后,我們知道,Glide會現(xiàn)將網(wǎng)絡(luò)獲取的數(shù)據(jù)緩存到本地。最后通過DataCacheGenerator的startNext方法,啟動了本地?cái)?shù)據(jù)的解析流程,其實(shí)整個(gè)過程與前文分析的過程基本是一致的,不再細(xì)說。

這里,我們忽略該過程,而直接從不緩存的情況來看后面的解碼過程,因?yàn)榻?jīng)過本地圖片的數(shù)據(jù)抓取后,最后一樣會來到解碼/轉(zhuǎn)碼的步驟。

因此,仍然回到SourceGenerator中,在抓取到數(shù)據(jù)之后,如果不緩存的情況下,進(jìn)入else分支:

  //SourceGenerator.java
  
  @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);
    }
  }

此時(shí),會調(diào)用一個(gè)回調(diào)接口,這個(gè)接口的實(shí)現(xiàn)就是DecodeJob,即啟動整個(gè)加載任務(wù)的對象。直接進(jìn)入onDataFetcherReady

  //DecodeJob.java

  @Override
  public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
      DataSource dataSource, Key attemptedKey) {
    this.currentSourceKey = sourceKey;
    this.currentData = data;
    this.currentFetcher = fetcher;
    this.currentDataSource = dataSource;
    this.currentAttemptingKey = attemptedKey;
    if (Thread.currentThread() != currentThread) {
      runReason = RunReason.DECODE_DATA;
      callback.reschedule(this);
    } else {
      TraceCompat.beginSection("DecodeJob.decodeFromRetrievedData");
      try {
        //解碼數(shù)據(jù)
        decodeFromRetrievedData();
      } finally {
        TraceCompat.endSection();
      }
    }
  }

如果仍在同一個(gè)線程中,進(jìn)入最后的分支

  //DecodeJob.java
  
  private void decodeFromRetrievedData() {
    Resource<R> resource = null;
    try {
      //解碼數(shù)據(jù)
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);
    } catch (GlideException e) {
      e.setLoggingDetails(currentAttemptingKey, currentDataSource);
      throwables.add(e);
    }
    if (resource != null) {
      notifyEncodeAndRelease(resource, currentDataSource);
    } else {
      runGenerators();
    }
  }
  
  private <Data> Resource<R> decodeFromData(DataFetcher<?> fetcher,
          Data data, DataSource dataSource) throws GlideException {
    try {
      if (data == null) {
        return null;
      }
      //解碼數(shù)據(jù)
      Resource<R> result = decodeFromFetcher(data, dataSource);
      return result;
    } finally {
      fetcher.cleanup();
    }
  }
  
  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);
  }

以上代碼一步步深入,其實(shí)最重要是最后一個(gè)方法,首先獲取了LoadPath,上一篇文章提到,該對象主要功能就是解碼和轉(zhuǎn)碼數(shù)據(jù),那么,進(jìn)入DecodeHelper看下是如何生成該對象的。(可參考代碼中的注釋)

  //DecodeHelper.java
  
  <Data> LoadPath<Data, ?, Transcode> getLoadPath(Class<Data> dataClass) {
    return glideContext
           .getRegistry()
           .getLoadPath(dataClass, resourceClass, transcodeClass);
  }
  
  //獲取LoadPath
  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) {
      //1. 獲取解碼器和轉(zhuǎn)碼器,并存放在DecodePath中
      List<DecodePath<Data, TResource, Transcode>> decodePaths =
          getDecodePaths(dataClass, resourceClass, transcodeClass);
      if (decodePaths.isEmpty()) {
        result = null;
      } else {
        //2. 轉(zhuǎn)載解碼器和轉(zhuǎn)碼器到LoadPath中
        result =
            new LoadPath<>(
                dataClass, resourceClass, transcodeClass, decodePaths, throwableListPool);
      }
      loadPathCache.put(dataClass, resourceClass, transcodeClass, result);
    }
    return result;
  }
  
  //對應(yīng)上面的1,獲取解碼器和轉(zhuǎn)碼器
  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<>();
    List<Class<TResource>> registeredResourceClasses =
        decoderRegistry.getResourceClasses(dataClass, resourceClass);
        
    //獲取所有可能解碼器和轉(zhuǎn)碼器
    for (Class<TResource> registeredResourceClass : registeredResourceClasses) {
      List<Class<Transcode>> registeredTranscodeClasses =
          transcoderRegistry.getTranscodeClasses(registeredResourceClass, transcodeClass);

      for (Class<Transcode> registeredTranscodeClass : registeredTranscodeClasses) {
        
        //1. 獲取解碼器
        List<ResourceDecoder<Data, TResource>> decoders =
            decoderRegistry.getDecoders(dataClass, registeredResourceClass);
            
        //2. 獲取轉(zhuǎn)碼器
        ResourceTranscoder<TResource, Transcode> transcoder =
            transcoderRegistry.get(registeredResourceClass, registeredTranscodeClass);
            
        //3. 把解碼器和轉(zhuǎn)碼器都馮導(dǎo)DecodePath中
        @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
        DecodePath<Data, TResource, Transcode> path =
            new DecodePath<>(dataClass, registeredResourceClass, registeredTranscodeClass,
                decoders, transcoder, throwableListPool);
        //4. 把DecodePath放到列表中
        decodePaths.add(path);
      }
    }
    return decodePaths;
  }

第一個(gè)方法中,又看到了一個(gè)熟悉的東西,那就是這個(gè)Registry,這個(gè)注冊器就是Glide在初始化的時(shí)候,進(jìn)行一系列解碼器/轉(zhuǎn)碼器注冊的東東,通過這個(gè)注冊器就可以獲取到可以解碼dataClass這個(gè)數(shù)據(jù)類型的解碼器(不清楚可以再看下第一篇文章)。

getDecodePaths方法中,分別獲取了已經(jīng)注冊的解碼器和轉(zhuǎn)碼器,并放到DecodePath中。

看下基本的解碼/轉(zhuǎn)碼器包括哪些(在第一篇文章也有詳細(xì)說明):

解碼器 功能
ByteBufferGifDecoder 將ByteBuffer解碼為GifDrawable
ByteBufferBitmapDecoder 將ByteBuffer解碼為Bitmap
ResourceDrawableDecoder 將資源Uri解碼為Drawable
ResourceBitmapDecoder 將資源ID解碼為Bitmap
BitmapDrawableDecoder 將數(shù)據(jù)解碼為BitmapDrawable
StreamBitmapDecoder 將InputStreams解碼為Bitmap
StreamGifDecoder 將InputStream數(shù)據(jù)轉(zhuǎn)換為BtyeBuffer,再解碼為GifDrawable
GifFrameResourceDecoder 解碼gif幀
FileDecoder 包裝File成為FileResource
UnitDrawableDecoder 將Drawable包裝為DrawableResource
UnitBitmapDecoder 包裝Bitmap成為BitmapResource
VideoDecoder 將本地視頻文件解碼為Bitmap
轉(zhuǎn)碼器 功能
BitmapDrawableTranscoder 將Bitmap轉(zhuǎn)碼為BitmapDrawable
BitmapBytesTranscoder 將Bitmap轉(zhuǎn)碼為Byte arrays
DrawableBytesTranscoder 將BitmapDrawable轉(zhuǎn)碼為Byte arrays
GifDrawableBytesTranscoder 將GifDrawable轉(zhuǎn)碼為Byte arrays

重點(diǎn)來看StreamBitmapDecoder和BitmapDrawableTranscoder。

在Glide抓取到數(shù)據(jù)后,會轉(zhuǎn)換成為==InputStream==,此時(shí),通過類型模型轉(zhuǎn)換的思想,從解碼注冊器中,找到可以解碼InputStream的解碼器,有StreamBitmapDecoder和StreamGifDecoder,我們知道最后最有==StreamBitmapDecoder==可以順利解碼器數(shù)據(jù),成為一張Bitmap數(shù)據(jù)。

我們在Glide.with(this).load(url).into(iv_img);中知道(以下代碼),我們的目標(biāo)是獲取一個(gè)Drawable,即==transcodeClass==實(shí)際上是==Drawable.class==,因此,通過匹配尋找,獲取到==BitmapDrawableTranscoder==轉(zhuǎn)碼器。

  public RequestBuilder<Drawable> load(@Nullable String string) {
    return asDrawable().load(string);
  }
  
  public RequestBuilder<Drawable> asDrawable() {
    return as(Drawable.class);
  }
  
  public <ResourceType> RequestBuilder<ResourceType> as(Class<ResourceType> resourceClass) {
    return new RequestBuilder<>(glide, this, resourceClass, context);
  }

三、轉(zhuǎn)碼和解碼

接下來,我們就具體看下,Glide是如何解碼的。

首先,回到最初的解碼入口

  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);
  }

  private <Data, ResourceType> Resource<R> runLoadPath(Data data, DataSource dataSource,
      LoadPath<Data, ResourceType, R> path) throws GlideException {
    Options options = getOptionsWithHardwareConfig(dataSource);
    DataRewinder<Data> rewinder = glideContext.getRegistry().getRewinder(data);
    try {
      //調(diào)用LoadPath的load方法
      return path.load(
          rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));
    } finally {
      rewinder.cleanup();
    }
  }

在獲取到LoadPath后,調(diào)用了它的load方法,在經(jīng)過層層調(diào)用后,最后會調(diào)用以下方法,限于篇幅,中間部分就省略了,不影響流程的理解和分析。

  public Resource<Transcode> decode(DataRewinder<DataType> rewinder, int width, int height,
      @NonNull Options options, DecodeCallback<ResourceType> callback) throws GlideException {
    //解碼
    Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
    //轉(zhuǎn)碼
    Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
    return transcoder.transcode(transformed, options);
  }

可以看到,先是解碼了數(shù)據(jù),然后再轉(zhuǎn)碼。以加載網(wǎng)絡(luò)圖片為例子,即現(xiàn)將數(shù)據(jù)解碼成為Bitmap,再轉(zhuǎn)碼成為Drawable。

最后

在解碼到一個(gè)可用于顯示的資源后,將會通過回調(diào),將數(shù)據(jù)回傳給ImageView進(jìn)行顯示。

當(dāng)然,在這里沒有詳細(xì)去分析整個(gè)解碼和轉(zhuǎn)碼的過程,這個(gè)過程其實(shí)也是比較復(fù)雜,特別是Glide對于數(shù)據(jù)的緩存/復(fù)用,以及Bitmap復(fù)用,用來避免大量申請和釋放內(nèi)存導(dǎo)致的內(nèi)存抖動等等,是非常值得去學(xué)習(xí)的,這也算是另外的話題了,有機(jī)會深入學(xué)習(xí)后,再專門寫一篇文章吧(又給自己挖了個(gè)坑 -_-! 希望可以早日填坑,哈哈 )。

以上,感謝閱讀,歡迎指正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容