1. 程式人生 > >Glide 4.x之請求網路圖片資料流程解析

Glide 4.x之請求網路圖片資料流程解析

在《Glide工作總體執行流程概述》一篇博文中簡單分析了glide的工作流程,簡而言之就是Glide先構建RequestManager物件,然後RequestManager物件構建ReqeustBuilder物件,再由RequsetBuilder物件建立一個Requset對像,最後將Request交給RequsetMangaer管理,然後RequestManager通知request呼叫begin發起請求將生成的圖片資源交給具體的Target的過程。只不過上篇文章只是簡單的做了梳理工作,並沒有對詳細的說明,本篇就承接上文來繼續分析。所以建議讀此篇博文的童鞋大致瞅一眼《Glide工作執行流程概述

本篇還是以SingleRequest為例來說明,在資源還沒有載入好的時候,SingleRequest的begin方法會呼叫一個onSizeReady方法:

public void begin() {
    //省略部分程式碼
    status = Status.WAITING_FOR_SIZE;
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
      onSizeReady(overrideWidth, overrideHeight);
    } 
   //省略部分程式碼

  }

進入onSizeReady內部翻看一翻,仍然剔除暫時與本文無關的程式碼:

private Engine engine;
public void onSizeReady(int width, int height) {
    //省略部分程式碼
    status = Status.RUNNING;

    loadStatus = engine.load(glideContext,model,//url
        //省略一大堆引數);

  }

也就是說onSizeReady方法內部也就是主要時呼叫來Engine物件的load方法,那麼這個engine物件是什麼鬼呢?先看看這個物件是時候初始化的,該物件是在初始化Glide物件的時候進行來初始化,具體的可在GlideBuilder的build方法找到:

 public Glide build(Context context) {

   //省略本分程式碼

    //如果客戶端沒有配置自己的Engine
    if (engine == null) {
      engine = new Engine(memoryCache, diskCacheFactory, diskCacheExecutor, sourceExecutor,
          GlideExecutor.newUnlimitedSourceExecutor());
    }

    return new Glide(context,engine,//省略一大堆引數);
  }

從Engine類的構造起所需要的引數來看,Engine負責記憶體快取,磁碟快取等等管理工作,這些記憶體和磁碟快取等相關的類可以通過Builder模式讓客戶端自己構建自己的快取邏輯,這也是Builder模式的強大之處,該模式也算是常用的設計模式之一,其設計理念還是很值得借鑑的,到此為止Engine的初始化已經建立完畢。下面繼續沿著SingleRequest的流程分析engine.load方法:

//返回一個LoadStatus物件
 public <R> LoadStatus load(//一大堆引數) {
    //1、生成一個EngineKey物件
    EngineKey key = keyFactory.buildKey(model,//url
     signature, width, height, transformations,
        resourceClass,
         transcodeClass, //Drawable.class
         options);
   //2、從快取載入圖片資源
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
           return null;
    }

   //3、此處也可以理解為快取邏輯
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      return null;
    }

    //4、從map中獲取一個EngineJob物件
    EngineJob<?> current = jobs.get(key);
    if (current != null) {
      current.addCallback(cb);
      return new LoadStatus(cb, current);
    }

    //5、建立一個EngineJob物件
    EngineJob<R> engineJob = engineJobFactory.build(key, isMemoryCacheable,
        useUnlimitedSourceExecutorPool);
    //6、建立一個DecodeJob物件    
    DecodeJob<R> decodeJob = decodeJobFactory.build(
        glideContext,
        model,//url
        key,
        //省略一大堆引數,
        transcodeClass,
        ,
        engineJob);
    //將engineJob放入map快取    
    jobs.put(key, engineJob);
    //新增一個callback
    engineJob.addCallback(cb);
    //8、開始engineJob
    engineJob.start(decodeJob);

    //返回一個LoadStatus物件
    return new LoadStatus(cb, engineJob);
  }

load方法的程式碼很長,1至4是與快取有關的邏輯,本篇暫時拋開不談,在第五步的時候建立來一個EngineJob物件這是一個DecodeJob.Callback介面的實現類,後面會提到此處可以留意下,現在暫且可以理解為EngineJob是Engine這個引擎的最小工作單元。然後第六步又建立來一個DecodeJob物件(該物件是一個Runnable),第八步 engineJob.start(decodeJob);開始來工作,所以此處需要進入start方法探究一二:

 public void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
     //獲取一個GlideExecutor物件,為ThreadPoolExecutor的子類
    GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();
    //執行decodeJob這個ruanable
    executor.execute(decodeJob);
  }

EngineJob的start方法只是獲取一個ThreadPoolExecutor物件執行DecodeJob這個Runnable而已(當然其內部還是比較複雜的),也就是說EngineJob只是一個殼子,其核心作用的還是DecodeJob這個物件。所以我們來看看DecodeJob這個Runalbe的run方法都做了些神馬:

public void run() {
     DataFetcher<?> localFetcher = currentFetcher;
    try {
      if (isCancelled) {
        notifyFailed();
        return;
      }
      //runWrapped方法是重點
      runWrapped();
    } catch (RuntimeException e) {
       //省略部分程式碼  
    } finally {
      //省略部分程式碼
    }
  }

拋開run方法的枝枝蔓蔓,其內部呼叫來runWrapped方法,感覺距離真相越來越近了有木有,這是真的嗎?:

  private void runWrapped() {
     switch (runReason) {
      //本篇分析這個分支
      case INITIALIZE:
        stage = getNextStage(Stage.INITIALIZE);
        currentGenerator = getNextGenerator();
        runGenerators();
        break;
      case SWITCH_TO_SOURCE_SERVICE:
        runGenerators();
        break;
      case DECODE_DATA:
        decodeFromRetrievedData();
        break;

    }
  }

上面有三個case分支,但是弱水三千只取一瓢,博主通過追蹤內部邏輯準備只分析INITIALIZE分支,正如glide註釋所說該分支INITIALIZE的意思是”The first time we’ve been submitted”,鑑於英語水平差,就不翻譯了(事實上如果你打斷點debug的話也會進入這個分支)。該case分支先呼叫了getNextGenerator方法,所以還是來看看該方法是幹了啥牛逼的事兒,在分析runWrapped的時候有山重水複疑無路的感覺,現在感覺距離最終目的地尚有西天還有十萬八千里呢!
這裡寫圖片描述

 private DataFetcherGenerator getNextGenerator() {
    switch (stage) {
      case RESOURCE_CACHE://快取相關
        return new ResourceCacheGenerator(decodeHelper, this);
      case DATA_CACHE://快取相關
        return new DataCacheGenerator(decodeHelper, this);
      case SOURCE://分析該分支
        return new SourceGenerator(decodeHelper, this);
      case FINISHED:
        return null;
    }
  }

因為上文說過,本篇步分析與快取有關的東西,只分析資料的最初來源(也就是伺服器資料),所以本篇博文就分析SOURCE分支,該分支返回裡一個SourceGenerator物件,該物件需要decodeHelper物件,decodeHelper物件是在初始化DecodeJob的時候對decodeHelper進行初始化的:

//初始化decodeHelper
 DecodeHelper<R> decodeHelper = new DecodeHelper<>();

 DecodeJob<R> init(
     GlideContext glideContext,
      Object model,//url
      //省略了一大堆引數
      ) {
      //呼叫decodeHelper的init方法初始化相關引數
    decodeHelper.init(
        glideContext,
        model,//url
        //省略了一大堆引數
        );

   //省略了無關程式碼
    return this;
  }

呼叫完getNextGenerator方法之後currentGenerator引用指向的物件就是SourceGenerator物件了,緊接著INITIALIZE的case分支又呼叫了runGenerators() 方法:

private void runGenerators() {

    boolean isStarted = false;

    while (!isCancelled && currentGenerator != null
        && !(isStarted = currentGenerator.startNext())) {
      stage = getNextStage(stage);
      //鏈式呼叫
      currentGenerator = getNextGenerator();

      if (stage == Stage.SOURCE) {
        reschedule();
        return;
      }
    }
    //省略了部分程式碼
  }

上面的方法中有一個while迴圈,while迴圈的條件題裡面有一句:

//currentGenerator為SourceGenerator物件
isStarted = currentGenerator.startNext()

如果該方法執行返回了false,則滿足進入迴圈的條件之一,所以我們看看SourceGenerator的startNext都多了些什麼:

 public boolean startNext() {
     //省略了快取邏輯

    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      //1 、獲取一個loadData對像獲取LoadData物件
      loadData = helper.getLoadData().get(loadDataListIndex++);

     //省略部分程式碼
      //呼叫loadData的fetcher物件獲取資料
        loadData.fetcher.loadData(helper.getPriority(), this);

    }
    return started;
  }

上面的程式碼主要做了兩個邏輯:
1、先通過getLoadData獲取ModelLoder物件的集合,然後獲取一個loadData物件
2、通過loadData物件的fetcher物件的loadData方法來獲取圖片資料。
其實這裡簡單打個斷點,然後debug一下就知道LoadData物件和fetcher都是什麼:
這裡寫圖片描述
上面說的在這裡先埋疑問:
ModeLoder 是什麼?什麼時候初始化的?
LoadData 有是什麼時候初始化的?
Fetcher有是幹什麼,有是怎麼初始化的?
以上三個問題將留在下篇部落格說明,本篇為了保持主題不偏方向,先不談。

這個LoadData是ModelLoader介面的一個內部類:

 class LoadData<Data> {
    public final Key sourceKey;
    public final List<Key> alternateKeys;
    //真正獲取資料的類
    public final DataFetcher<Data> fetcher;

  }

loaddata 物件內部有一個DataFetcher,該類就是Glide實際獲取資料的類,如上圖我們得到的是一個HttpUrlFetcher 物件,所以我們直接進入HttpUrlFetcher物件的loadData方法看看:

public void loadData(Priority priority, DataCallback<? super InputStream> callback) {

     final InputStream result;
      //發起網路請求,講URL對應的資料轉換成InputStream
      result = loadDataWithRedirects(glideUrl.toURL(), 0     , null ,glideUrl.getHeaders());

   //將圖片資料輸入流交給callback處理
    callback.onDataReady(result);//第一個callback
  }

loaddata方法是真正獲取圖片資料的地方:
1、呼叫loadDataWithRedirects方法,發起網路請求,講URL指定的圖片資料轉換成InputStream輸入流,該方法內部就是通過URLConnection物件來完成的(詳細的讀者可以自行到HttpUrlFetcher類的loadDataWithRedirects檢視)。
2、將圖片資料InputStream通過callback的onDataReady來傳遞,此處是我們遇到的第一個callback

也就是說通過HttpUrlFecther處理之後,我們的圖片資料來源轉換成來InputStream物件了。
那麼上文中的callback 是什麼呢?,該callback是一個DataCallback型別的介面(這是我們遇到的第一個callback,後面還有好多,希望讀者不要繞暈了)。HttpUrlFecther的loadData方法的第二個引數就是一個DataCallback型別的引數,在SourceGenerator種我們傳的是this,也就是說我們這個callback就是SourceGenerator物件,所以看看SourceGenerator物件的onDataReady方法都做了什麼:

   //第二個callback
   private final FetcherReadyCallback cb;
  //此處data就是InputStream
  public void onDataReady(Object data) {

  //省略快取邏輯

  //第二個callback
      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
          loadData.fetcher.getDataSource(), originalKey);
    }
  }

剔除掉快取的相關邏輯後,我們迎來了第二個callback,呼叫了該回調的onDataFetcherReady方法後,我們又將資料傳給了這個callback。那麼這個callback有是什麼呢?該callback是FetcherReadyCallback的一個介面實現。那麼這個callback 又是什麼時候初始化的呢?當然是初始化SourceGenerator物件的時候,見上文我們是在DecodeJob這個類種初始化SourceGenerator的,DecodeJob就是這個callback的實現類,所以我們進入其onDataFetcherReady方法看看:

  @Override
  public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
      DataSource dataSource, Key attemptedKey) {
   //將資料交給DecodeJob的currentData持有
    this.currentData = data;//InputStream
     //省略部分程式碼
        decodeFromRetrievedData();

    }
  }

然後我們看看decodeFromRetrievedData方法:

 private void decodeFromRetrievedData() {
    //1、將Inpustream轉換成Resource物件
    Resource<R> resource = 
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);
      //2、將資料繼續傳遞
       if (resource != null) {
      notifyEncodeAndRelease(resource, currentDataSource);
    } else {
      runGenerators();
    }
  }

上面的方法做了兩件事:
1、將圖片資料InputStream流轉換成Resource物件
2、呼叫notifyEncodeAndRelease將資料繼續傳遞。
此時我們的圖片資料由InputStream轉換成了Resource物件。看看notifyEncodeAndRelease都做了寫什麼:

  private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {

    Resource<R> result = resource;
    //省略部分程式碼

    notifyComplete(result, dataSource);

   //省略部分程式碼
    }
  }

省略了與本文無關的程式碼之後,如上面所示我們來到了DecodeJob類的notifyComplete方法:

 private Callback<R> callback;
private void notifyComplete(Resource<R> resource, DataSource dataSource) {
   //省略一行程式碼
    //第三個callback
    callback.onResourceReady(resource, dataSource);
  }

notifyComplete方法中我們見到了第三callback,並且呼叫其onResourceReady方法,那麼這個 callback的具體實現類是什麼呢?該物件通過觀察原始碼發現是在初始化DecodeJob初始化的,而根據上文DecodeJob的初始化是在Engine 的load方法裡面完成的,在該類初始化的時候我們還初始化了EngineJob這個類,這個類就是這個第三個callback的具體實現(見上文engine.load 的講解),所以看看EngineJob的onResourceReady方法:

  public void onResourceReady(Resource<R> resource, DataSource dataSource) {
    //原始資料
    this.resource = resource;
    this.dataSource = dataSource;
    MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
  }

finally,見到了Handler的身影,說明我們距離終點不遠了,上文的handler傳送了MSG_COMPLETE訊息該handler來處理,handler的初始化如下:

private static final Handler MAIN_THREAD_HANDLER =
      new Handler(Looper.getMainLooper(), new MainThreadCallback());

所以我們進入MainThreadcallback來看看MSG_COMPLETE訊息的處理邏輯是什麼,關於handler的具體工作流程參考博主《android訊息處理機制詳解》:

 //MainThreadcallback
 public boolean handleMessage(Message message) {
      EngineJob<?> job = (EngineJob<?>) message.obj;
      switch (message.what) {
        case MSG_COMPLETE:
          job.handleResultOnMainThread();
          break;

      }
      return true;
    }

僅僅是呼叫了EngineJob的handleResultOnMainThread方法,此時我們已經將圖片資料切換到了UI執行緒:

  void handleResultOnMainThread() {
    //省略部分程式碼
    //將資料轉換成engineResource
    engineResource = engineResourceFactory.build(resource, isCacheable);
    hasResource = true;

  //省略部分程式碼
    for (ResourceCallback cb : cbs) {
      if (!isInIgnoredCallbacks(cb)) {
       //特麼的第四個callback
        cb.onResourceReady(engineResource, dataSource);
      }
    }

  }

該方法做了兩件事:
1、將resource交給engineResource來持有
2、將engineResource交給本文的**第四個callback**

那麼這第四個callback又是什麼?是ResourceCallback介面,該介面是通過初始化EngineJob的時候呼叫EngineJob的 addCallback(ResourceCallback cb)方法傳進來的。且EngineJob 的初始化是在Engine的load 方法中,而load 的方法的一個引數就是又這個callback, 且load的呼叫又是在文章開頭的SingleReqeust方法,順藤摸瓜發現第四個callback就是SingleReqeust,所以我們進入該SingleReqeust的onResourceReady方法:

 public void onResourceReady(Resource<?> resource, DataSource dataSource) {
    //呼叫過載方法:
    onResourceReady((Resource<R>) resource, (R) received, dataSource);
  }

private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {

    this.resource = resource;
    //最終呼叫target的onResourceReady
     target.onResourceReady(result, animation);

  }

如果你讀過博主的《Glide工作總體執行流程概述》這篇文章的話,就可以知道此處的target就是DrawableImageViewTarget,該target的onResourceReady方法的最總邏輯就是呼叫:

imageView.setImageDrawable(resource);

來完成圖片的展示。

到此為止,本篇分析完畢,分析原始碼的過程中四個callback來回排程可把我繞壞了,為了本篇博文的主題連續性,基本上省略了好多東西沒講,什麼ModelLoader啦,LoadData啦,Fetcher啦,將在後續博文中講解,敬請期待,如有不當之處,歡迎批評指正