1. 程式人生 > >Android Glide原始碼解析

Android Glide原始碼解析

功能介紹

由於這篇文章使用glide的老版本,因此有些使用方法可能不太一致了。
本文基於github上Glide最新程式碼4.0.0版本做解析。
最基本的使用方式如下:

Glide.with(this)
                .asDrawable()
                .load("http://i6.topit.me/6/5d/45/1131907198420455d6o.jpg")
                .apply(fitCenterTransform(this))
                .apply(placeholderOf(R.drawable
.skyblue_logo_wechatfavorite_checked)) .into(imageView);

Glide使用了現在非常流行的流氏編碼方式,方便了開發者的使用,簡明、扼要。
接下來主要對上面這一段流氏操作做拆分。

Glide 主入口

這個類有點像門臉模式的統一代理入口,不過實際作用在4.0.0中很多功能都被放到後面的其他類中,此類關注的點就很少了。雖然整個libray的所有需要的組建都在這個類中,但是實際也只是一個統一初始化的地方。

RequestManager(Glide.with(…))

這個類主要用於管理和啟動Glide的所有請求,可以使用activity,fragment或者連線生命週期的事件去智慧的停止,啟動,和重啟請求。也可以檢索或者通過例項化一個新的物件,或者使用靜態的Glide去利用構建在Activity和Fragment生命週期處理中。它的方法跟你的Fragment和Activity的是同步的。

RequestBuilder

通用類,可以處理設定選項,並啟動負載的通用資源型別。

在這個類中,主要是應用請求的很多選項(如下的選項從字面都能看出具體的用處,在ImageView控制元件中經常也能看到,另外之前版本可不是這麼使用的):

public final class RequestOptions extends BaseRequestOptions<RequestOptions> {

  private static RequestOptions skipMemoryCacheTrueOptions;
  private static RequestOptions skipMemoryCacheFalseOptions;
  private
static RequestOptions fitCenterOptions; private static RequestOptions centerCropOptions; private static RequestOptions circleCropOptions; private static RequestOptions noTransformOptions; private static RequestOptions noAnimationOptions; // ...省略... }

RequestBuilder transition(TransitionOptions transitionOptions){} 這個方法主要是用於載入物件從佔位符(placeholder)或者縮圖(thumbnail)到真正物件載入完成的轉場動畫。

RequestBuilder load(…){}多太方法中,這裡可以載入很多型別的資料物件,可以是String,Uri,File,resourceId,byte[]這些。當然這些後面對應的編碼方式也是不一樣的。

Target into(…){}這個方法是觸發Request真正啟動的地方,在上邊的示例中最後就是呼叫這個方法發起請求。

不得不說的registry域,這個域掛載了很多元件,該註冊器中囊括了模組載入器(ModelLoader)、編碼器(Encoder)、資源解碼器(ResourceDecoder)、資源編碼器(ResourceEncoder)、資料迴轉器(DataRewinder)、轉碼器(Transcoder)。這些都是Glide在對資源編解碼中既是基礎又是核心功能。

程式碼結構

這裡主要列舉一下一些重要的元件以及他們的結構關係:
ModelLoader
ModelLoader

DataFetcher
DataFetcher

Target
Target

Resource
Resource

ResourceTransformation
ResourceTransformation

Pool
Pool

Cache
Cache

Decoder
Decoder

Encoder
Encoder
把這些元件程式碼結構列舉出來主要是為了讓讀者和使用者一目瞭然的看到自己需要的一些功能。

執行流程

1、根據不同版本的Fragment建立RequestManagerFragment或者SupportRequestManagerFragment,並加入到對應的FragmentManager中。這兩種Fragment是不帶有任何介面的,主要是用於同步生命週期。具體實現如下:

public static RequestManager with(Context context) {
    RequestManagerRetriever retriever = RequestManagerRetriever.get();
    return retriever.get(context);
  }

// RequestManagerRetriever.get(...)  
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
  public RequestManager get(Activity activity) {
    if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
      return get(activity.getApplicationContext());
    } else {
      assertNotDestroyed(activity);
      android.app.FragmentManager fm = activity.getFragmentManager();
      return fragmentGet(activity, fm, null);
    }
  }
  @TargetApi(Build.VERSION_CODES.HONEYCOMB)
  RequestManager fragmentGet(Context context, android.app.FragmentManager fm,
      android.app.Fragment parentHint) {
    RequestManagerFragment current = getRequestManagerFragment(fm, parentHint);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
      requestManager =
          new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
      current.setRequestManager(requestManager);
    }
    return requestManager;
  }

  @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
  RequestManagerFragment getRequestManagerFragment(
      final android.app.FragmentManager fm, android.app.Fragment parentHint) {
    RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
    if (current == null) {
      current = pendingRequestManagerFragments.get(fm);
      if (current == null) {
        current = new RequestManagerFragment();
        current.setParentFragmentHint(parentHint);
        pendingRequestManagerFragments.put(fm, current);
        fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
        handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
      }
    }
    return current;
  }

2、建立一個RequestBuilder,並新增一個DrawableTransitionOptions型別的轉場動畫

public RequestBuilder<Drawable> asDrawable() {
    return as(Drawable.class).transition(new DrawableTransitionOptions());
  }

3、載入物件(model域)

public RequestBuilder<TranscodeType> load(@Nullable Object model) {
    return loadGeneric(model);
  }

private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
    this.model = model;
    isModelSet = true;
    return this;
  }

4、裝載物件(包含請求的發起點)。

public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {
    Util.assertMainThread();
    Preconditions.checkNotNull(target);
    if (!isModelSet) {
      throw new IllegalArgumentException("You must call #load() before calling #into()");
    }

    Request previous = target.getRequest();

    if (previous != null) {
      requestManager.clear(target);
    }

    requestOptions.lock();
    Request request = buildRequest(target);
    target.setRequest(request);
    requestManager.track(target, request);

    return target;
  }

一般而言,大部分使用者都是用來裝載圖片的,因此都會呼叫如下這個方法:

public Target<TranscodeType> into(ImageView view) {
    Util.assertMainThread();
    Preconditions.checkNotNull(view);

    if (!requestOptions.isTransformationSet()
        && requestOptions.isTransformationAllowed()
        && view.getScaleType() != null) {
      if (requestOptions.isLocked()) {
        requestOptions = requestOptions.clone();
      }
      switch (view.getScaleType()) {
        case CENTER_CROP:
          requestOptions.optionalCenterCrop(context);
          break;
        case FIT_CENTER:
        case FIT_START:
        case FIT_END:
          requestOptions.optionalFitCenter(context);
          break;
        //$CASES-OMITTED$
        default:
          // Do nothing.
      }
    }

    return into(context.buildImageViewTarget(view, transcodeClass));
  }

這裡針對ImageView的填充方式做了篩選並對應設定到requestOptions上。最終的是通過ImageView和轉碼型別(transcodeClass)建立不通過的Target(例如Bitmap對應的BitmapImageViewTarget和Drawable對應的DrawableImageViewTarget)

4.1 Request的建立buildRequest(target)。
在Request的建立中會針對是否有縮圖來建立不同尺寸的請求,縮圖方法可以使用RequestBuilder.thumbnail(…)方法來新增上。
Glide中的Request都是使用了SingleRequest類,當然縮圖採用的是ThumbnailRequestCoordinator類:

private Request obtainRequest(Target<TranscodeType> target,
      BaseRequestOptions<?> requestOptions, RequestCoordinator requestCoordinator,
      TransitionOptions<?, ? super TranscodeType> transitionOptions, Priority priority,
      int overrideWidth, int overrideHeight) {
    requestOptions.lock();

    return SingleRequest.obtain(
        context,
        model,
        transcodeClass,
        requestOptions,
        overrideWidth,
        overrideHeight,
        priority,
        target,
        requestListener,
        requestCoordinator,
        context.getEngine(),
        transitionOptions.getTransitionFactory());
  }

比較值得推崇的是SingleRequest.obtain寫法,個人認為比new關鍵字更簡潔明瞭吧。

target.setRequest(request)也是一個比較值得注意的地方,如果target是ViewTarget,那麼request會被設定到View的tag上。這樣其實是有一個好處,每一個View有一個自己的Request,如果有重複請求,那麼都會先去拿到上一個已經繫結的Request,並且從RequestManager中清理回收掉。這應該是去重的功能。

4.2 requestManager.track(target, request)
這個方法非常的複雜,主要用於觸發請求、編解碼、裝載和快取這些功能。下面就一步一步來看吧:

4.2.1 快取target,並啟動Request

void track(Target<?> target, Request request) {
    targetTracker.track(target);
    requestTracker.runRequest(request);
  }
  /**
   * Starts tracking the given request.
   */
  public void runRequest(Request request) {
    requests.add(request); //新增記憶體快取
    if (!isPaused) {
      request.begin(); // 開始
    } else {
      pendingRequests.add(request); // 掛起請求
    }
  }

繼續看一下SingleRequest中的begin方法:

@Override
  public void begin() {
    stateVerifier.throwIfRecycled();
    startTime = LogTime.getLogTime();
    // 如果model空的,那麼是不能執行的。 這裡的model就是前面提到的RequestBuilder中的model
    if (model == null) {
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        width = overrideWidth;
        height = overrideHeight;
      }
      // Only log at more verbose log levels if the user has set a fallback drawable, because
      // fallback Drawables indicate the user expects null models occasionally.
      int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
      onLoadFailed(new GlideException("Received null model"), logLevel);
      return;
    }

    status = Status.WAITING_FOR_SIZE;
    // 如果當前的View尺寸已經載入獲取到了,那麼就會進入真正的載入流程。
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
      onSizeReady(overrideWidth, overrideHeight);
    } else {
    // 反之,當前View還沒有畫出來,那麼是沒有尺寸的。
    // 這裡會呼叫到ViewTreeObserver.addOnPreDrawListener。
    // 等待View的尺寸都ok,才會繼續
      target.getSize(this);
    }

    // 如果等待和正在執行狀態,那麼當前會載入佔位符Drawable
    if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
        && canNotifyStatusChanged()) {
      target.onLoadStarted(getPlaceholderDrawable());
    }
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logV("finished run method in " + LogTime.getElapsedMillis(startTime));
    }
  }

接下來是target.getSize(this)方法。這裡主要說一下尺寸未加載出來的情況(ViewTarget.java):

void getSize(SizeReadyCallback cb) {
      int currentWidth = getViewWidthOrParam();
      int currentHeight = getViewHeightOrParam();
      if (isSizeValid(currentWidth) && isSizeValid(currentHeight)) {
        cb.onSizeReady(currentWidth, currentHeight);
      } else {
        // We want to notify callbacks in the order they were added and we only expect one or two
        // callbacks to
        // be added a time, so a List is a reasonable choice.
        if (!cbs.contains(cb)) {
          cbs.add(cb);
        }
        if (layoutListener == null) {
          final ViewTreeObserver observer = view.getViewTreeObserver();
          layoutListener = new SizeDeterminerLayoutListener(this);
          // 繪畫之前加入尺寸的監聽。這一點我想大部分Android開發同學應該都知道。
          // 接下來在看看系統觸發該Listener時target又幹了些什麼。
          observer.addOnPreDrawListener(layoutListener);
        }
      }
    }

private static class SizeDeterminerLayoutListener implements ViewTreeObserver
        .OnPreDrawListener {
        // 注意這裡是弱引用
      private final WeakReference<SizeDeterminer> sizeDeterminerRef;

      public SizeDeterminerLayoutListener(SizeDeterminer sizeDeterminer) {
        sizeDeterminerRef = new WeakReference<>(sizeDeterminer);
      }

      @Override
      public boolean onPreDraw() {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
          Log.v(TAG, "OnGlobalLayoutListener called listener=" + this);
        }
        SizeDeterminer sizeDeterminer = sizeDeterminerRef.get();
        if (sizeDeterminer != null) {
         // 通知SizeDeterminer去重新檢查尺寸,並觸發後續操作。
         // SizeDeterminer有點像工具類,又作為尺寸回撥的檢測介面
          sizeDeterminer.checkCurrentDimens();
        }
        return true;
      }
    }

ok,繼續回到SingleRequest.onSizeReady方法,主要就是Engine發起load操作

public void onSizeReady(int width, int height) {
    stateVerifier.throwIfRecycled();
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
    if (status != Status.WAITING_FOR_SIZE) {
      return;
    }
    status = Status.RUNNING;

    float sizeMultiplier = requestOptions.getSizeMultiplier();
    this.width = Math.round(sizeMultiplier * width);
    this.height = Math.round(sizeMultiplier * height);

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
    }
    loadStatus = engine.load(
        glideContext,
        model,
        requestOptions.getSignature(),
        this.width,
        this.height,
        requestOptions.getResourceClass(),
        transcodeClass,
        priority,
        requestOptions.getDiskCacheStrategy(),
        requestOptions.getTransformations(),
        requestOptions.isTransformationRequired(),
        requestOptions.getOptions(),
        requestOptions.isMemoryCacheable(),
        this);
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
  }

特別的,所有的操作都是來之唯一一個Engine,它的建立是來至於Glide的初始化。如果有需要修改快取配置的同學可以繼續看一下diskCacheFactory的建立:

if (engine == null) {
      engine = new Engine(memoryCache, diskCacheFactory, diskCacheExecutor, sourceExecutor);
    }

繼續看一下Engine.load的詳細過程:

public <R> LoadStatus load(
      GlideContext glideContext,
      Object model,
      Key signature,
      int width,
      int height,
      Class<?> resourceClass,
      Class<R> transcodeClass,
      Priority priority,
      DiskCacheStrategy diskCacheStrategy,
      Map<Class<?>, Transformation<?>> transformations,
      boolean isTransformationRequired,
      Options options,
      boolean isMemoryCacheable,
      ResourceCallback cb) {
    Util.assertMainThread();
    long startTime = LogTime.getLogTime();

    // 建立key,這是給每次載入資源的唯一標示。
    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);

    // 通過key查詢快取資源 (PS 這裡的快取主要是記憶體中的快取,切記,可以檢視MemoryCache)
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
     // 如果有,那麼直接利用當前快取的資源。
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return null;
    }

    // 這是一個二級記憶體的快取引用,很簡單用了一個Map<Key, WeakReference<EngineResource<?>>>裝載起來的。
    // 這個快取主要是誰來放進去呢? 可以參考上面一級記憶體快取loadFromCache方法。
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return null;
    }

    // 根據key獲取快取的job。
    EngineJob current = jobs.get(key);
    if (current != null) {
      current.addCallback(cb); // 給當前job新增上回調Callback
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }

    // 建立job
    EngineJob<R> engineJob = engineJobFactory.build(key, isMemoryCacheable);
    DecodeJob<R> decodeJob = decodeJobFactory.build(
        glideContext,
        model,
        key,
        signature,
        width,
        height,
        resourceClass,
        transcodeClass,
        priority,
        diskCacheStrategy,
        transformations,
        isTransformationRequired,
        options,
        engineJob);
    jobs.put(key, engineJob);
    engineJob.addCallback(cb);
    // 放入執行緒池,執行
    engineJob.start(decodeJob);

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
  }

上面有一些值得注意的地方:

  1. 記憶體快取:在Glide中預設是LruResourceCache。當然你也可以自定義;
  2. 為何要兩級記憶體快取(loadFromActiveResources)。個人理解是一級快取採用LRU演算法進行快取,並不能保證全部能命中,新增二級快取提高命中率之用;
  3. EngineJob和DecodeJob各自職責:EngineJob充當了管理和排程者,主要負責載入和各類回撥通知;DecodeJob是真正幹活的勞動者,這個類實現了Runnable介面。

下面來看看DecodeJob是如何執行的:

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;
      default:
        throw new IllegalStateException("Unrecognized run reason: " + runReason);
    }
  }

// 這裡的階段策略首先是從resource中尋找,然後再是data,,再是source
private Stage getNextStage(Stage current) {
    switch (current) {
      case INITIALIZE: 
      // 根據定義的快取策略來回去下一個狀態
      // 快取策略來之於RequestBuilder的requestOptions域
      // 如果你有自定義的策略,可以呼叫RequestBuilder.apply方法即可
      // 詳細的可用快取策略請參看DiskCacheStrategy.java
        return diskCacheStrategy.decodeCachedResource()
            ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
      case RESOURCE_CACHE:
        return diskCacheStrategy.decodeCachedData()
            ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
      case DATA_CACHE:
        return Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        return Stage.FINISHED;
      default:
        throw new IllegalArgumentException("Unrecognized stage: " + current);
    }

// 根據Stage找到資料抓取生成器。
private DataFetcherGenerator getNextGenerator() {
    switch (stage) {
      case RESOURCE_CACHE:
       // 產生含有降低取樣/轉換資源資料快取檔案的DataFetcher。
        return new ResourceCacheGenerator(decodeHelper, this);
      case DATA_CACHE:
       // 產生包含原始未修改的源資料快取檔案的DataFetcher。
        return new DataCacheGenerator(decodeHelper, this);
      case SOURCE:
      // 生成使用註冊的ModelLoader和載入時提供的Model獲取源資料規定的DataFetcher。
      // 根據不同的磁碟快取策略,源資料可首先被寫入到磁碟,然後從快取檔案中載入,而不是直接返回。
        return new SourceGenerator(decodeHelper, this);
      case FINISHED:
        return null;
      default:
        throw new IllegalStateException("Unrecognized stage: " + stage);
    }
  }

經過很多流程,最後來到了發起實際請求的地方SourceGenerator.startNext()方法:

  public boolean startNext() {
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      cacheData(data);
    }

    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
      return true;
    }
    sourceCacheGenerator = null;

    loadData = null;
    boolean started = false;
    // 查詢ModelLoader
    while (!started && hasNextModelLoader()) {
      loadData = helper.getLoadData().get(loadDataListIndex++);
      if (loadData != null
          && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
          || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
        started = true;
        根據model的fetcher載入資料
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
    return started;
  }

這裡的Model必須是實現了GlideModule介面的,fetcher是實現了DataFetcher介面。有興趣同學可以繼續看一下integration中的okhttp和volley工程。Glide主要採用了這兩種網路libray來下載圖片。

4.2.2 資料下載完成後的快取處理SourceGenerator.onDataReady

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

有些小夥伴可能看不太明白為什麼就一個dataToCache = data就完了…其實cb.reschedule()很重要,這裡的cb就是DecodeJob.reschedule():

public void reschedule() {
    runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
    callback.reschedule(this);
  }

這裡又有一個Callback,繼續追蹤,這裡的Callback介面是定義在DecodeJob內的,而實現是在外部的Engine中(這裡會用執行緒池重新啟動當前job,那為什麼要這樣做呢?原始碼中的解釋是為了不同執行緒的切換,因為下載都是借用第三方網路庫,而實際的編解碼是在Glide自定義的執行緒池中進行的):

  public void reschedule(DecodeJob<?> job) {
    if (isCancelled) {
      MAIN_THREAD_HANDLER.obtainMessage(MSG_CANCELLED, this).sendToTarget();
    } else {
      sourceExecutor.execute(job);
    }
  }

接下來繼續DecodeJob.runWrapped()方法。這個時候的runReason是SWITCH_TO_SOURCE_SERVICE,因此直接執行runGenerators(),這裡繼續執行SourceGenerator.startNext()方法,值得注意的dataToCache域,因為上一次執行的時候是下載,因此再次執行的時候記憶體快取已經存在,因此直接快取資料cacheData(data):

 private void cacheData(Object dataToCache) {
    long startTime = LogTime.getLogTime();
    try {
    // 根據不同的資料獲取註冊的不同Encoder
      Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
      DataCacheWriter<Object> writer =
          new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
      originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
      // 這裡的DiskCache實現是Engine中LazyDiskCacheProvider提供的DiskCacheAdapter。
      helper.getDiskCache().put(originalKey, writer);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished encoding source to cache"
            + ", key: " + originalKey
            + ", data: " + dataToCache
            + ", encoder: " + encoder
            + ", duration: " + LogTime.getElapsedMillis(startTime));
      }
    } finally {
      loadData.fetcher.cleanup();
    }

    // 建立針對快取的Generator
    sourceCacheGenerator =
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
  }

繼續回到SourceGenerator.startNext()方法,這個時候已經有了sourceCacheGenerator,那麼直接執行DataCacheGenerator.startNext()方法:

public boolean startNext() {
    while (modelLoaders == null || !hasNextModelLoader()) {
      sourceIdIndex++;
      if (sourceIdIndex >= cacheKeys.size()) {
        return false;
      }

      Key sourceId = cacheKeys.get(sourceIdIndex);
      Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
      cacheFile = helper.getDiskCache().get(originalKey);
      if (cacheFile != null) {
        this.sourceKey = sourceId;
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }

    loadData = null;
    boolean started = false;
    // 這裡會通過model尋找註冊過的ModelLoader
    while (!started && hasNextModelLoader()) {
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
      loadData =
          modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(),
              helper.getOptions());
      // 通過FileLoader繼續載入資料
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
        started = true;
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
    return started;
  }

這裡的ModelLoader跟之前提到過的Register的模組載入器(ModelLoader)對應是modelLoaderRegistry域,具體執行的操作是Registry.getModelLoaders(…)方法如下:

 public <Model> List<ModelLoader<Model, ?>> getModelLoaders(Model model) {
    List<ModelLoader<Model, ?>> result = modelLoaderRegistry.getModelLoaders(model);
    if (result.isEmpty()) {
      throw new NoModelLoaderAvailableException(model);
    }
    return result;
  }

繼續回到DataCacheGenerator.startNext()方法,找到了ModelLoader,這裡筆者跟蹤到的是FileLoader類(FileFetcher.loadData(…)方法):

 public void loadData(Priority priority, DataCallback<? super Data> callback) {
        // 讀取檔案資料
      try {
        data = opener.open(file);
      } catch (FileNotFoundException e) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
          Log.d(TAG, "Failed to open file", e);
        }
        //失敗
        callback.onLoadFailed(e);
        return;
      }
      // 成功
      callback.onDataReady(data);
    }

4.2.3 裝載流程
回撥通知這裡就不打算多講了,主要線路如下:

-->DataCacheGenerator.onDataReady
  -->SourceGenerator.onDataFetcherReady
    -->DecodeJob.onDataFetcherReady
    -->DecodeJob.decodeFromRetrievedData
    -->DecodeJob.notifyEncodeAndRelease
    -->DecodeJob.notifyComplete
      -->EngineJob.onResourceReady

Debug流程圖:
裝載流程Debug流程圖
需要說明的就是在EngineJob中有一個Handler叫MAIN_THREAD_HANDLER。為了實現在主UI中裝載資源的作用,ok繼續上邊的流程:

      -->EngineJob.handleResultOnMainThread
        -->SingleRequest.onResourceReady
          -->ImageViewTarget.onResourceReady
          -->ImageViewTarget.setResource
            -->ImageView.setImageDrawable/ImageView.setImageBitmap

Debug流程圖2:
裝載流程Debug流程圖2
資料的裝載過程中有一個很重要的步驟就是decode,這個操作發生在DecodeJob.decodeFromRetrievedData的時候,繼續看程式碼:

private void decodeFromRetrievedData() {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey("Retrieved data", startFetchTime,
          "data: " + currentData
          + ", cache key: " + currentSourceKey
          + ", fetcher: " + currentFetcher);
    }
    Resource<R> resource = null;
    try {
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);
    } catch (GlideException e) {
      e.setLoggingDetails(currentAttemptingKey, currentDataSource);
      exceptions.add(e);
    }
    if (resource != null) {
      notifyEncodeAndRelease(resource, currentDataSource);
    } else {
      runGenerators();
    }
  }

這中間發生了很多轉換主要流程:

-->DecodeJob.decodeFromData
-->DecodeJob.decodeFromFetcher
-->DecodeJob.runLoadPath
  -->LoadPath.load
  -->LoadPath.loadWithExceptionList
  -->LoadPath.decode
  -->LoadPath.decodeResource
  -->LoadPath.decodeResourceWithList
    -->ResourceDecoder.handles
    -->ResourceDecoder.decode  

這裡講到了decode,那麼encode發生在什麼時候呢?直接通過Encoder介面呼叫發現,在資料快取的時候才會觸發編碼。具體呼叫在DiskLruCacheWrapper和DataCacheWriter中。一些值得參考的寫法例如BitmapEncoder對Bitmap的壓縮處理。

結束語

最近看開源庫Glide關注度一直比較高,因此打算一探究竟。 由於時間比較緊,因此一些應該有的時序圖沒有畫,這裡也只能簡單用箭頭代替。不過個人認為整體執行流程已經表達清楚了。

  1. 總體來說程式碼寫的挺漂亮的,單從使用者角度來說入手是比較容易的。
  2. 原始碼使用了大量的工廠方法來建立物件,就像String.valueof(…)方法一樣,這也體現編碼的優雅。
  3. 不過想要對這個庫進行改造,可能並非易事,筆者在跟蹤程式碼的過程中發現很多地方有Callback這樣的介面,來來回回查詢幾次很容易就暈頭轉向了。。。
  4. 另外一個感覺難受的地方就是構造方法帶入引數太多,就拿SingleRequest來說就是12個構造引數。
  5. 單例的使用感覺還是有些模糊,就比如GlideContext,有些時候通過Glide.get(context).getGlideContext()獲取,而有些類中採用構造傳入。個人覺得既然讓Glide作為單例,那麼還這樣傳入引數是不是有點多餘?程式碼的編寫都是可以折中考慮,不過如果整個專案擬定好了一個規則的話,我想最好還是遵循它。另外再吐槽一下單例,很多開發人員喜歡用單例,如果你是有程式碼潔癖的開發者,那麼你肯定很討厭這樣,單例很容易造成程式碼的散落和結構不清晰。

思考

原始碼的解析只是把最重要的載入流程走了一遍,有一些比較細節的地方沒有關注,如果你有需要,可以自己跟著這個主線debug一下就能查詢到。

  1. 為何要使用額外的無介面的Fragment?
  2. 如果開發者要使用這個libray作為圖片載入庫,而且專案本身對App的記憶體佔用和Size都是有要求的話,那麼Register是否有過重的嫌疑?