[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)過去幾個月的時間剃袍,期間由于學(xué)習(xí)其他東西和項(xiàng)目的原因(其實(shí)是懶癌發(fā)作~)犹赖,本文被擱置了很久榴鼎,期間還有網(wǎng)友私信問什么時候會把“解碼與轉(zhuǎn)碼”部分寫好,想起曾經(jīng)信誓旦旦要將這個坑補(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ò)上加載一張圖片為例子枢步,分析了整個數(shù)據(jù)轉(zhuǎn)換的過程沉删,在最后,我們知道醉途,Glide會現(xiàn)將網(wǎng)絡(luò)獲取的數(shù)據(jù)緩存到本地矾瑰。最后通過DataCacheGenerator的startNext方法,啟動了本地?cái)?shù)據(jù)的解析流程隘擎,其實(shí)整個過程與前文分析的過程基本是一致的殴穴,不再細(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);
    }
  }

此時,會調(diào)用一個回調(diào)接口忙厌,這個接口的實(shí)現(xiàn)就是DecodeJob凫岖,即啟動整個加載任務(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();
      }
    }
  }

如果仍在同一個線程中慰毅,進(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í)最重要是最后一個方法,首先獲取了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;
  }

第一個方法中呵哨,又看到了一個熟悉的東西,那就是這個Registry轨奄,這個注冊器就是Glide在初始化的時候孟害,進(jìn)行一系列解碼器/轉(zhuǎn)碼器注冊的東東,通過這個注冊器就可以獲取到可以解碼dataClass這個數(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==朝巫,此時,通過類型模型轉(zhuǎn)換的思想石景,從解碼注冊器中劈猿,找到可以解碼InputStream的解碼器拙吉,有StreamBitmapDecoder和StreamGifDecoder,我們知道最后最有==StreamBitmapDecoder==可以順利解碼器數(shù)據(jù)糙臼,成為一張Bitmap數(shù)據(jù)庐镐。

我們在Glide.with(this).load(url).into(iv_img);中知道(以下代碼)恩商,我們的目標(biāo)是獲取一個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。

最后

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

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

以上镇匀,感謝閱讀,歡迎指正袜啃。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末汗侵,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子群发,更是在濱河造成了極大的恐慌晰韵,老刑警劉巖,帶你破解...
    沈念sama閱讀 223,002評論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件熟妓,死亡現(xiàn)場離奇詭異雪猪,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)起愈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評論 3 400
  • 文/潘曉璐 我一進(jìn)店門只恨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抬虽,你說我怎么就攤上這事官觅。” “怎么了斥赋?”我有些...
    開封第一講書人閱讀 169,787評論 0 365
  • 文/不壞的土叔 我叫張陵缰猴,是天一觀的道長。 經(jīng)常有香客問我疤剑,道長滑绒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,237評論 1 300
  • 正文 為了忘掉前任隘膘,我火速辦了婚禮疑故,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘弯菊。我一直安慰自己纵势,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,237評論 6 398
  • 文/花漫 我一把揭開白布管钳。 她就那樣靜靜地躺著钦铁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪才漆。 梳的紋絲不亂的頭發(fā)上牛曹,一...
    開封第一講書人閱讀 52,821評論 1 314
  • 那天,我揣著相機(jī)與錄音醇滥,去河邊找鬼黎比。 笑死超营,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的阅虫。 我是一名探鬼主播演闭,決...
    沈念sama閱讀 41,236評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼颓帝!你這毒婦竟也來了米碰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,196評論 0 277
  • 序言:老撾萬榮一對情侶失蹤躲履,失蹤者是張志新(化名)和其女友劉穎见间,沒想到半個月后聊闯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體工猜,經(jīng)...
    沈念sama閱讀 46,716評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,794評論 3 343
  • 正文 我和宋清朗相戀三年菱蔬,在試婚紗的時候發(fā)現(xiàn)自己被綠了篷帅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,928評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡拴泌,死狀恐怖魏身,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蚪腐,我是刑警寧澤箭昵,帶...
    沈念sama閱讀 36,583評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站回季,受9級特大地震影響家制,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜泡一,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,264評論 3 336
  • 文/蒙蒙 一颤殴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鼻忠,春花似錦涵但、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,755評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至塑娇,卻和暖如春澈侠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背钝吮。 一陣腳步聲響...
    開封第一講書人閱讀 33,869評論 1 274
  • 我被黑心中介騙來泰國打工埋涧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留板辽,地道東北人。 一個月前我還...
    沈念sama閱讀 49,378評論 3 379
  • 正文 我出身青樓棘催,卻偏偏與公主長得像劲弦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子醇坝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,937評論 2 361