Glide源碼解析(三)

本篇是 Glide 系列的最后一篇,主要講一下 into 方法里面的邏輯。into 的邏輯也是最多最復(fù)雜的鬓梅,可能需要反復(fù)閱讀源碼才能搞清楚。

Glide : https://github.com/bumptech/glide

version : v4.9.0

RequestBuilder

@NonNull
public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {
  return into(target, /*targetListener=*/ null, Executors.mainThreadExecutor());
}

@NonNull
@Synthetic
<Y extends Target<TranscodeType>> Y into(
    @NonNull Y target,
    @Nullable RequestListener<TranscodeType> targetListener,
    Executor callbackExecutor) {
  return into(target, targetListener, /*options=*/ this, callbackExecutor);
}

into 方法最后都會(huì)調(diào)用 into(@NonNull Y target, @Nullable RequestListener<TranscodeType> targetListener, BaseRequestOptions<?> options, Executor callbackExecutor)

private <Y extends Target<TranscodeType>> Y into(
    @NonNull Y target,
    @Nullable RequestListener<TranscodeType> targetListener,
    BaseRequestOptions<?> options,
    Executor callbackExecutor) {
  Preconditions.checkNotNull(target);
  // 判斷有沒(méi)有調(diào)用過(guò) load 方法
  if (!isModelSet) {
    throw new IllegalArgumentException("You must call #load() before calling #into()");
  }
  // 這里根據(jù)一堆參數(shù)會(huì)去構(gòu)造圖片請(qǐng)求 SingleRequest
  Request request = buildRequest(target, targetListener, options, callbackExecutor);

  Request previous = target.getRequest();
  if (request.isEquivalentTo(previous)
      && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
    request.recycle();
    // If the request is completed, beginning again will ensure the result is re-delivered,
    // triggering RequestListeners and Targets. If the request is failed, beginning again will
    // restart the request, giving it another chance to complete. If the request is already
    // running, we can let it continue running without interruption.
    if (!Preconditions.checkNotNull(previous).isRunning()) {
      // Use the previous request rather than the new one to allow for optimizations like skipping
      // setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions
      // that are done in the individual Request.
      previous.begin();
    }
    return target;
  }

  requestManager.clear(target);
  target.setRequest(request);
  requestManager.track(target, request);

  return target;
}

構(gòu)造出請(qǐng)求 request 后谨湘,重點(diǎn)關(guān)注下 requestManager.track 方法绽快,由 requestManager 來(lái)執(zhí)行這個(gè)請(qǐng)求。

RequestManager

synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
  targetTracker.track(target);
  requestTracker.runRequest(request);
}

track 方法中調(diào)用了兩個(gè)方法:

  1. TargetTracker.track() 方法會(huì)對(duì)當(dāng)前 Target 的生命周期進(jìn)行管理;
  2. RequestTracker.runRequest() 方法對(duì)當(dāng)前請(qǐng)求進(jìn)行管理;

RequestTracker

public void runRequest(@NonNull Request request) {
  requests.add(request);
  if (!isPaused) {
    request.begin();
  } else {
    request.clear();
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      Log.v(TAG, "Paused, delaying request");
    }
    pendingRequests.add(request);
  }
}

當(dāng) Glide 未處于暫停狀態(tài)的時(shí)候紧阔,會(huì)直接使用 Request.begin() 方法開(kāi)啟請(qǐng)求坊罢。

SingleRequest

@Override
public synchronized void begin() {
  assertNotCallingCallbacks();
  stateVerifier.throwIfRecycled();
  startTime = LogTime.getLogTime();
  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;
  }

  if (status == Status.RUNNING) {
    throw new IllegalArgumentException("Cannot restart a running request");
  }

  // If we're restarted after we're complete (usually via something like a notifyDataSetChanged
  // that starts an identical request into the same Target or View), we can simply use the
  // resource and size we retrieved the last time around and skip obtaining a new size, starting a
  // new load etc. This does mean that users who want to restart a load because they expect that
  // the view size has changed will need to explicitly clear the View or Target before starting
  // the new load.
  if (status == Status.COMPLETE) {
    onResourceReady(resource, DataSource.MEMORY_CACHE);
    return;
  }

  // Restarts for requests that are neither complete nor running can be treated as new requests
  // and can run again from the beginning.
  
  // 主要來(lái)看這里,之前的都是一些對(duì)請(qǐng)求的狀態(tài)判斷
  status = Status.WAITING_FOR_SIZE;
  if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
    onSizeReady(overrideWidth, overrideHeight);
  } else {
    target.getSize(this);
  }

  if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
      && canNotifyStatusChanged()) {
    target.onLoadStarted(getPlaceholderDrawable());
  }
  if (IS_VERBOSE_LOGGABLE) {
    logV("finished run method in " + LogTime.getElapsedMillis(startTime));
  }
}

在 begin 方法中擅耽,重點(diǎn)關(guān)注下

  if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
    onSizeReady(overrideWidth, overrideHeight);
  } else {
    target.getSize(this);
  }

這里會(huì)根據(jù)有沒(méi)有強(qiáng)制設(shè)置圖片寬度來(lái)分為兩部分:

  • 設(shè)置了活孩,直接調(diào)用 onSizeReady
  • 沒(méi)設(shè)置,會(huì)去調(diào)用 target.getSize 乖仇。做的事情就是給 view 添加 addOnPreDrawListener 憾儒。這樣的話,在繪制之前獲取到了 view 的寬高这敬,然后再回調(diào) onSizeReady

所以航夺,說(shuō)到底,最后都是會(huì)調(diào)用 onSizeReady 的崔涂。

@Override
public synchronized void onSizeReady(int width, int height) {
  stateVerifier.throwIfRecycled();
  if (IS_VERBOSE_LOGGABLE) {
    logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
  }
  if (status != Status.WAITING_FOR_SIZE) {
    return;
  }
  status = Status.RUNNING;

  float sizeMultiplier = requestOptions.getSizeMultiplier();
  this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
  this.height = maybeApplySizeMultiplier(height, sizeMultiplier);

  if (IS_VERBOSE_LOGGABLE) {
    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.isScaleOnlyOrNoTransform(),
          requestOptions.getOptions(),
          requestOptions.isMemoryCacheable(),
          requestOptions.getUseUnlimitedSourceGeneratorsPool(),
          requestOptions.getUseAnimationPool(),
          requestOptions.getOnlyRetrieveFromCache(),
          this,
          callbackExecutor);

  // This is a hack that's only useful for testing right now where loads complete synchronously
  // even though under any executor running on any thread but the main thread, the load would
  // have completed asynchronously.
  if (status != Status.RUNNING) {
    loadStatus = null;
  }
  if (IS_VERBOSE_LOGGABLE) {
    logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
  }
}

在 onSizeReady 中將狀態(tài)更改為 Status.RUNNING 阳掐,并調(diào)用 engine 的 load() 方法。

Engine

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,
    boolean isScaleOnlyOrNoTransform,
    Options options,
    boolean isMemoryCacheable,
    boolean useUnlimitedSourceExecutorPool,
    boolean useAnimationPool,
    boolean onlyRetrieveFromCache,
    ResourceCallback cb,
    Executor callbackExecutor) {
  long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
  // 構(gòu)造此圖片的緩存 key
  EngineKey key =
      keyFactory.buildKey(
          model,
          signature,
          width,
          height,
          transformations,
          resourceClass,
          transcodeClass,
          options);

  EngineResource<?> memoryResource;
  synchronized (this) {
    memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
    // 查看有沒(méi)有圖片緩存冷蚂,如果沒(méi)有的話就開(kāi)啟新的任務(wù)加載
    if (memoryResource == null) {
      return waitForExistingOrStartNewJob(
          glideContext,
          model,
          signature,
          width,
          height,
          resourceClass,
          transcodeClass,
          priority,
          diskCacheStrategy,
          transformations,
          isTransformationRequired,
          isScaleOnlyOrNoTransform,
          options,
          isMemoryCacheable,
          useUnlimitedSourceExecutorPool,
          useAnimationPool,
          onlyRetrieveFromCache,
          cb,
          callbackExecutor,
          key,
          startTime);
    }
  }

  // Avoid calling back while holding the engine lock, doing so makes it easier for callers to
  // deadlock.
  // 如果有的話缭保,直接回調(diào) SingleRequest 的 onResourceReady 方法
  cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
  return null;
}

在 load 中,Glide 會(huì)先去取緩存蝙茶,當(dāng)緩存中不存在的時(shí)候就準(zhǔn)備使用新創(chuàng)建一個(gè)任務(wù)來(lái)加載圖片艺骂。我們這里就當(dāng)作是第一次加載圖片了,所以跟進(jìn) waitForExistingOrStartNewJob 方法中看看隆夯。

private <R> LoadStatus waitForExistingOrStartNewJob(
    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,
    boolean isScaleOnlyOrNoTransform,
    Options options,
    boolean isMemoryCacheable,
    boolean useUnlimitedSourceExecutorPool,
    boolean useAnimationPool,
    boolean onlyRetrieveFromCache,
    ResourceCallback cb,
    Executor callbackExecutor,
    EngineKey key,
    long startTime) {

  EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
  if (current != null) {
    current.addCallback(cb, callbackExecutor);
    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Added to existing load", startTime, key);
    }
    return new LoadStatus(cb, current);
  }

  EngineJob<R> engineJob =
      engineJobFactory.build(
          key,
          isMemoryCacheable,
          useUnlimitedSourceExecutorPool,
          useAnimationPool,
          onlyRetrieveFromCache);

  DecodeJob<R> decodeJob =
      decodeJobFactory.build(
          glideContext,
          model,
          key,
          signature,
          width,
          height,
          resourceClass,
          transcodeClass,
          priority,
          diskCacheStrategy,
          transformations,
          isTransformationRequired,
          isScaleOnlyOrNoTransform,
          onlyRetrieveFromCache,
          options,
          engineJob);

  jobs.put(key, engineJob);

  engineJob.addCallback(cb, callbackExecutor);
  engineJob.start(decodeJob);

  if (VERBOSE_IS_LOGGABLE) {
    logWithTimeAndKey("Started new load", startTime, key);
  }
  return new LoadStatus(cb, engineJob);
}

上面方法中創(chuàng)建了兩個(gè)對(duì)象钳恕,一個(gè)是 DecodeJob别伏、一個(gè)是 EngineJob。它們之間的關(guān)系是忧额,EngineJob 內(nèi)部維護(hù)了線程池厘肮,用來(lái)管理資源加載,已經(jīng)當(dāng)資源加載完畢的時(shí)候通知回調(diào)睦番。 DecodeJob 繼承了 Runnable类茂,是線程池當(dāng)中的一個(gè)任務(wù)。就像上面那樣托嚣,我們通過(guò)調(diào)用 engineJob.start(decodeJob) 來(lái)開(kāi)始執(zhí)行圖片加載的任務(wù)巩检。

EngineJob

public synchronized void start(DecodeJob<R> decodeJob) {
  this.decodeJob = decodeJob;
  GlideExecutor executor =
      decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();
  executor.execute(decodeJob);
}

執(zhí)行 decodeJob 。所以直接看 DecodeJob 的 run 方法示启。

DecodeJob

@Override
public void run() {
  // This should be much more fine grained, but since Java's thread pool implementation silently
  // swallows all otherwise fatal exceptions, this will at least make it obvious to developers
  // that something is failing.
  GlideTrace.beginSectionFormat("DecodeJob#run(model=%s)", model);
  // Methods in the try statement can invalidate currentFetcher, so set a local variable here to
  // ensure that the fetcher is cleaned up either way.
  DataFetcher<?> localFetcher = currentFetcher;
  try {
    if (isCancelled) {
      notifyFailed();
      return;
    }
    runWrapped();
  } catch (CallbackException e) {
    // If a callback not controlled by Glide throws an exception, we should avoid the Glide
    // specific debug logic below.
    throw e;
  } catch (Throwable t) {
    // Catch Throwable and not Exception to handle OOMs. Throwables are swallowed by our
    // usage of .submit() in GlideExecutor so we're not silently hiding crashes by doing this. We
    // are however ensuring that our callbacks are always notified when a load fails. Without this
    // notification, uncaught throwables never notify the corresponding callbacks, which can cause
    // loads to silently hang forever, a case that's especially bad for users using Futures on
    // background threads.
    if (Log.isLoggable(TAG, Log.DEBUG)) {
      Log.d(
          TAG,
          "DecodeJob threw unexpectedly" + ", isCancelled: " + isCancelled + ", stage: " + stage,
          t);
    }
    // When we're encoding we've already notified our callback and it isn't safe to do so again.
    if (stage != Stage.ENCODE) {
      throwables.add(t);
      notifyFailed();
    }
    if (!isCancelled) {
      throw t;
    }
    throw t;
  } finally {
    // Keeping track of the fetcher here and calling cleanup is excessively paranoid, we call
    // close in all cases anyway.
    if (localFetcher != null) {
      localFetcher.cleanup();
    }
    GlideTrace.endSection();
  }
}

在 run 方法中兢哭,當(dāng)前任務(wù)沒(méi)有被取消的話,會(huì)進(jìn)入到 runWrapped() 方法丑搔。

private void runWrapped() {
  switch (runReason) {
    case INITIALIZE: // 一進(jìn)來(lái)是 INITIALIZE 狀態(tài)的
      stage = getNextStage(Stage.INITIALIZE); // 獲取 Stage.INITIALIZE 的下一步
      currentGenerator = getNextGenerator(); // 根據(jù)獲取到的 stage 來(lái)選擇不同的 Generator
      runGenerators(); // 運(yùn)行 Generator
      break;
    case SWITCH_TO_SOURCE_SERVICE:
      runGenerators();
      break;
    case DECODE_DATA:
      decodeFromRetrievedData();
      break;
    default:
      throw new IllegalStateException("Unrecognized run reason: " + runReason);
  }
}

這里一開(kāi)始進(jìn)入的時(shí)候厦瓢,runReason 是 INITIALIZE 狀態(tài)的。在 getNextStage 中啤月,因?yàn)闆](méi)有圖片緩存所以得到是的 Stage.SOURCE 。那么就接著到了 getNextGenerator 劳跃。

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

stage 是 SOURCE 谎仲,所以獲取到的就是 SourceGenerator 。接著就調(diào)用 runGenerators() 刨仑。

private void runGenerators() {
  currentThread = Thread.currentThread();
  startFetchTime = LogTime.getLogTime();
  boolean isStarted = false;
  while (!isCancelled
      && currentGenerator != null
      && !(isStarted = currentGenerator.startNext())) { // 這里執(zhí)行 currentGenerator.startNext
    stage = getNextStage(stage);
    currentGenerator = getNextGenerator();

    if (stage == Stage.SOURCE) {
      reschedule();
      return;
    }
  }
  // We've run out of stages and generators, give up.
  if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
    notifyFailed();
  }

  // Otherwise a generator started a new load and we expect to be called back in
  // onDataFetcherReady.
}

會(huì)先去執(zhí)行 currentGenerator.startNext() 郑诺。所以接著就跳轉(zhuǎn)到 SourceGenerator.startNext() 。

SourceGenerator

@Override
public boolean startNext() {
  // 一開(kāi)始肯定沒(méi)有數(shù)據(jù)來(lái)緩存的杉武,所以往下走
  if (dataToCache != null) {
    Object data = dataToCache;
    dataToCache = null;
    cacheData(data);
  }

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

  loadData = null;
  boolean started = false;
  while (!started && hasNextModelLoader()) {
    // 使用 DecodeHelper 的 getLoadData() 方法從注冊(cè)的映射表中找出當(dāng)前的圖片類(lèi)型對(duì)應(yīng)的 ModelLoader辙诞;
    loadData = helper.getLoadData().get(loadDataListIndex++);
    if (loadData != null
        && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
            || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
      started = true;
      // 這里調(diào)用 ModelLoader 中的 fetcher 去加載數(shù)據(jù)
      loadData.fetcher.loadData(helper.getPriority(), this);
    }
  }
  return started;
}

因?yàn)橹拔覀兊南敕ㄊ羌虞d網(wǎng)絡(luò)上的 url 圖片,所以這里的 loadData 就對(duì)應(yīng)著 HttpGlideUrlLoader
轻抱, fetcher 就是 HttpUrlFetcher 飞涂。

HttpUrlFetcher

@Override
public void loadData(
    @NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
  long startTime = LogTime.getLogTime();
  try {
    // 在這里獲取網(wǎng)絡(luò)上的圖片數(shù)據(jù)
    InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
    // 回調(diào) onDataReady
    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));
    }
  }
}

loadDataWithRedirects 中會(huì)去調(diào)用 HttpURLConnection 加載網(wǎng)絡(luò)上的圖片數(shù)據(jù)。

加載完之后祈搜,會(huì)回調(diào) onDataReady 方法较店。這個(gè)回調(diào)一直從 HttpUrlFetcher 中一直回調(diào)到 SourceGenerator 中。所以下面就來(lái)看看 SourceGenerator.onDataReady

SourceGenerator

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

在 onDataReady 中容燕,會(huì)去判斷如果 data 不為空并且磁盤(pán)緩存可以緩存的情況下梁呈,會(huì)調(diào)用 cb.reschedule(); 。這其實(shí)是調(diào)用了 DecodeJob 的 reschedule 方法蘸秘。

DecodeJob

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

在這里官卡,設(shè)置 runReason 為 RunReason.SWITCH_TO_SOURCE_SERVICE 蝗茁,這很關(guān)鍵,在下面代碼中會(huì)用到寻咒。然后再調(diào)用 callback.reschedule(this) 评甜。其實(shí)就是調(diào)用了 EngineJob 的 reschedule 方法。

EngineJob

@Override
public void reschedule(DecodeJob<?> job) {
  // Even if the job is cancelled here, it still needs to be scheduled so that it can clean itself
  // up.
  getActiveSourceExecutor().execute(job);
}

reschedule 方法擺明了就是讓 DecodeJob 把 run 方法再跑一遍仔涩。之前說(shuō)過(guò)忍坷,DecodeJob 的 run 方法里面大部分的邏輯其實(shí)是在 runWrapped 中的。

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

這代碼很熟悉熔脂,之前我們的流程到過(guò)這里佩研。不同的是,之前的 runReason 是 INITIALIZE 霞揉。而現(xiàn)在的 runReason 是 SWITCH_TO_SOURCE_SERVICE 旬薯。

接著 SWITCH_TO_SOURCE_SERVICE 的邏輯是直接調(diào)用 runGenerators 方法。

private void runGenerators() {
  currentThread = Thread.currentThread();
  startFetchTime = LogTime.getLogTime();
  boolean isStarted = false;
  while (!isCancelled
      && currentGenerator != null
      && !(isStarted = currentGenerator.startNext())) {
    stage = getNextStage(stage);
    currentGenerator = getNextGenerator();

    if (stage == Stage.SOURCE) {
      reschedule();
      return;
    }
  }
  // We've run out of stages and generators, give up.
  if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
    notifyFailed();
  }

  // Otherwise a generator started a new load and we expect to be called back in
  // onDataFetcherReady.
}

這里的 currentGenerator 還是之前的 SourceGenerator 适秩,所以還是調(diào)用 SourceGenerator.startNext 绊序。

SourceGenerator

@Override
public boolean startNext() {
  // 不同的是,這里的 dataToCache 不再是空的了秽荞,而是之前從網(wǎng)絡(luò)上下載獲取到的 InputStream
  // 所以這里會(huì)去走創(chuàng)建緩存的邏輯
  if (dataToCache != null) {
    Object data = dataToCache;
    dataToCache = null;
    cacheData(data);
  }

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

  loadData = null;
  boolean started = false;
  while (!started && hasNextModelLoader()) {
    loadData = helper.getLoadData().get(loadDataListIndex++);
    if (loadData != null
        && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
            || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
      started = true;
      loadData.fetcher.loadData(helper.getPriority(), this);
    }
  }
  return started;
}

這次調(diào)用 SourceGenerator.startNext 其實(shí)是建立磁盤(pán)緩存骤公,直接來(lái)看 cacheData 方法。

private void cacheData(Object dataToCache) {
  long startTime = LogTime.getLogTime();
  try {
    // 建立緩存
    Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
    DataCacheWriter<Object> writer =
        new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
    originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
    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();
  }
  // 注意扬跋,這里 sourceCacheGenerator 創(chuàng)建一個(gè)對(duì)象阶捆,所以 sourceCacheGenerator 不再是 null 
  sourceCacheGenerator =
      new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
}

這里的主要邏輯是構(gòu)建一個(gè)用于將數(shù)據(jù)緩存到磁盤(pán)上面的 DataCacheGenerator。DataCacheGenerator 的流程基本與 SourceGenerator 一致钦听,也就是根據(jù)資源文件的類(lèi)型找到 ModelLoader洒试,然后使用 DataFetcher 加載緩存的資源。與之前不同的是朴上,這次是用 DataFecher 來(lái)加載 File 類(lèi)型的資源垒棋。也就是說(shuō),當(dāng)我們從網(wǎng)絡(luò)中拿到了數(shù)據(jù)之后 Glide 會(huì)先將其緩存到磁盤(pán)上面痪宰,然后再?gòu)拇疟P(pán)上面讀取圖片并將其顯示到控件上面叼架。所以,當(dāng)從網(wǎng)絡(luò)打開(kāi)了輸入流之后 SourceGenerator 的任務(wù)基本結(jié)束了酵镜,而后的顯示的任務(wù)都由 DataCacheGenerator 來(lái)完成碉碉。

再回過(guò)頭來(lái)看看 SourceGenerator.startNext 方法,在 cacheData 后面會(huì)對(duì) sourceCacheGenerator 進(jìn)行判斷淮韭。由于上面已經(jīng)把 sourceCacheGenerator 對(duì)象 new 出來(lái)了垢粮。所以接著就直接走 DataCacheGenerator 的 startNext 方法了。所以上面這段話就很好理解了靠粪。

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

那么蜡吧,這里我們對(duì) DataCacheGenerator 的邏輯就省略了毫蚓。DataCacheGenerator 中的流程最終會(huì)走到
ByteBufferFetcher 。

之前的 SourceGenerator 對(duì)應(yīng)著 HttpUrlFetcher 昔善,而 DataCacheGenerator 對(duì)應(yīng)著 ByteBufferFetcher 元潘。

ByteBufferFetcher

@Override
public void loadData(
    @NonNull Priority priority, @NonNull DataCallback<? super ByteBuffer> callback) {
  ByteBuffer result;
  try {
    // 把圖片的字節(jié)流再?gòu)拇疟P(pán)緩存中讀取出來(lái)
    result = ByteBufferUtil.fromFile(file);
  } catch (IOException e) {
    if (Log.isLoggable(TAG, Log.DEBUG)) {
      Log.d(TAG, "Failed to obtain ByteBuffer for file", e);
    }
    callback.onLoadFailed(e);
    return;
  }
  // 回調(diào)給 DataCacheGenerator 的 onDataReady 方法
  callback.onDataReady(result);
}

磁盤(pán)緩存好之后,再?gòu)奈募凶x取圖片的字節(jié)流君仆◆娓牛回調(diào)給 DataCacheGenerator

DataCacheGenerator

@Override
public void onDataReady(Object data) {
  cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.DATA_DISK_CACHE, sourceKey);
}

DataCacheGenerator 會(huì)回調(diào) DecodeJob 的 onDataFetcherReady 方法。

DecodeJob

@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 {
    GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
    try {
      decodeFromRetrievedData();
    } finally {
      GlideTrace.endSection();
    }
  }
}

runReason 已經(jīng)改成 RunReason.DECODE_DATA 返咱,說(shuō)明已經(jīng)進(jìn)行到解碼圖片數(shù)據(jù)的環(huán)節(jié)了钥庇。

接著調(diào)用 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);
    throwables.add(e);
  }
  // 圖片資源獲取到后咖摹,通知已經(jīng)任務(wù)完成
  if (resource != null) {
    notifyEncodeAndRelease(resource, currentDataSource);
  } else {
    runGenerators();
  }
}

這里我們可以看下评姨,當(dāng) resource 最終獲取到后,是通過(guò) notifyEncodeAndRelease 來(lái)通知任務(wù)完成的萤晴。這在后面的代碼解析中會(huì)講到吐句。

現(xiàn)在,我們就來(lái)看看關(guān)鍵的邏輯店读,接著調(diào)用 decodeFromData 嗦枢。

private <Data> Resource<R> decodeFromData(
    DataFetcher<?> fetcher, Data data, DataSource dataSource) throws GlideException {
  try {
    if (data == null) {
      return null;
    }
    long startTime = LogTime.getLogTime();
    Resource<R> result = decodeFromFetcher(data, dataSource);
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey("Decoded result " + result, startTime);
    }
    return result;
  } finally {
    fetcher.cleanup();
  }
}

調(diào)用 decodeFromFetcher 方法。

@SuppressWarnings("unchecked")
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);
}

調(diào)用了 runLoadPath 两入。

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 {
    // ResourceType in DecodeCallback below is required for compilation to work with gradle.
    return path.load(
        rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));
  } finally {
    rewinder.cleanup();
  }
}

調(diào)用 path.load 方法净宵。

LoadPath

public Resource<Transcode> load(
    DataRewinder<Data> rewinder,
    @NonNull Options options,
    int width,
    int height,
    DecodePath.DecodeCallback<ResourceType> decodeCallback)
    throws GlideException {
  List<Throwable> throwables = Preconditions.checkNotNull(listPool.acquire());
  try {
    return loadWithExceptionList(rewinder, options, width, height, decodeCallback, throwables);
  } finally {
    listPool.release(throwables);
  }
}

直接調(diào)用 loadWithExceptionList 方法。

private Resource<Transcode> loadWithExceptionList(
    DataRewinder<Data> rewinder,
    @NonNull Options options,
    int width,
    int height,
    DecodePath.DecodeCallback<ResourceType> decodeCallback,
    List<Throwable> exceptions)
    throws GlideException {
  Resource<Transcode> result = null;
  //noinspection ForLoopReplaceableByForEach to improve perf
  for (int i = 0, size = decodePaths.size(); i < size; i++) {
    DecodePath<Data, ResourceType, Transcode> path = decodePaths.get(i);
    try {
      result = path.decode(rewinder, width, height, options, decodeCallback);
    } catch (GlideException e) {
      exceptions.add(e);
    }
    if (result != null) {
      break;
    }
  }

  if (result == null) {
    throw new GlideException(failureMessage, new ArrayList<>(exceptions));
  }

  return result;
}

然后會(huì)調(diào)用 DecodePath 的 decode 方法裹纳。

DecodePath

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);
  Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
  return transcoder.transcode(transformed, options);
}

在 decode 中做了三件事:

  1. decodeResource 將原始數(shù)據(jù)轉(zhuǎn)換成我們?cè)紙D片的過(guò)程;
  2. callback.onResourceDecoded 是當(dāng)?shù)玫搅嗽紙D片之后對(duì)圖片繼續(xù)處理過(guò)程;
  3. transcoder.transcode 會(huì)使用 BitmapDrawableTranscoder 包裝一層,即對(duì) Drawable 進(jìn)行延遲初始化處理紧武。

那么我們接著跟進(jìn) decodeResource 方法剃氧。

@NonNull
private Resource<ResourceType> decodeResource(
    DataRewinder<DataType> rewinder, int width, int height, @NonNull Options options)
    throws GlideException {
  List<Throwable> exceptions = Preconditions.checkNotNull(listPool.acquire());
  try {
    return decodeResourceWithList(rewinder, width, height, options, exceptions);
  } finally {
    listPool.release(exceptions);
  }
}

主要邏輯在 decodeResourceWithList 中。

@NonNull
private Resource<ResourceType> decodeResourceWithList(
    DataRewinder<DataType> rewinder,
    int width,
    int height,
    @NonNull Options options,
    List<Throwable> exceptions)
    throws GlideException {
  Resource<ResourceType> result = null;
  //noinspection ForLoopReplaceableByForEach to improve perf
  for (int i = 0, size = decoders.size(); i < size; i++) {
    ResourceDecoder<DataType, ResourceType> decoder = decoders.get(i);
    try {
      DataType data = rewinder.rewindAndGet();
      if (decoder.handles(data, options)) {
        data = rewinder.rewindAndGet();
        result = decoder.decode(data, width, height, options);
      }
      // Some decoders throw unexpectedly. If they do, we shouldn't fail the entire load path, but
      // instead log and continue. See #2406 for an example.
    } catch (IOException | RuntimeException | OutOfMemoryError e) {
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Failed to decode data for " + decoder, e);
      }
      exceptions.add(e);
    }

    if (result != null) {
      break;
    }
  }

  if (result == null) {
    throw new GlideException(failureMessage, new ArrayList<>(exceptions));
  }
  return result;
}

ResourceDecoder 具有多個(gè)實(shí)現(xiàn)類(lèi)阻星,比如 BitmapDrawableDecoder朋鞍、ByteBufferBitmapDecoder等。從名字也可以看出來(lái)是用來(lái)將一個(gè)類(lèi)型轉(zhuǎn)換成另一個(gè)類(lèi)型的妥箕。

在這里會(huì)使用 ByteBufferBitmapDecoder 來(lái)將 ByteBuffer 專(zhuān)成 Bitmap 滥酥。

ByteBufferBitmapDecoder

@Override
public Resource<Bitmap> decode(
    @NonNull ByteBuffer source, int width, int height, @NonNull Options options)
    throws IOException {
  InputStream is = ByteBufferUtil.toStream(source);
  return downsampler.decode(is, width, height, options);
}

它最終會(huì)在 Downsampler 的 decodeStream() 方法中調(diào)用 BitmapFactory 的 decodeStream() 方法來(lái)從輸入流中得到 Bitmap。

在 Downsampler 內(nèi)部還會(huì)維持一個(gè) BitmapPool 畦幢,用來(lái)復(fù)用 Bitmap 坎吻。有興趣的同學(xué)可以看下這一塊的代碼,這里就不過(guò)多展示了宇葱。

接下來(lái)瘦真,就來(lái)看看上面 callback.onResourceDecoded 的邏輯刊头。callback.onResourceDecoded 會(huì)調(diào)用 DecodeJob.onResourceDecoded 方法。

DecodeJob

@Synthetic
@NonNull
<Z> Resource<Z> onResourceDecoded(DataSource dataSource, @NonNull Resource<Z> decoded) {
  @SuppressWarnings("unchecked")
  Class<Z> resourceSubClass = (Class<Z>) decoded.get().getClass();
  Transformation<Z> appliedTransformation = null;
  Resource<Z> transformed = decoded;
  if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
    appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
    transformed = appliedTransformation.transform(glideContext, decoded, width, height);
  }
  // TODO: Make this the responsibility of the Transformation.
  if (!decoded.equals(transformed)) {
    decoded.recycle();
  }

  final EncodeStrategy encodeStrategy;
  final ResourceEncoder<Z> encoder;
  if (decodeHelper.isResourceEncoderAvailable(transformed)) {
    encoder = decodeHelper.getResultEncoder(transformed);
    encodeStrategy = encoder.getEncodeStrategy(options);
  } else {
    encoder = null;
    encodeStrategy = EncodeStrategy.NONE;
  }

  Resource<Z> result = transformed;
  boolean isFromAlternateCacheKey = !decodeHelper.isSourceKey(currentSourceKey);
  if (diskCacheStrategy.isResourceCacheable(
      isFromAlternateCacheKey, dataSource, encodeStrategy)) {
    if (encoder == null) {
      throw new Registry.NoResultEncoderAvailableException(transformed.get().getClass());
    }
    final Key key;
    switch (encodeStrategy) {
      case SOURCE:
        key = new DataCacheKey(currentSourceKey, signature);
        break;
      case TRANSFORMED:
        key =
            new ResourceCacheKey(
                decodeHelper.getArrayPool(),
                currentSourceKey,
                signature,
                width,
                height,
                appliedTransformation,
                resourceSubClass,
                options);
        break;
      default:
        throw new IllegalArgumentException("Unknown strategy: " + encodeStrategy);
    }

    LockedResource<Z> lockedResult = LockedResource.obtain(transformed);
    deferredEncodeManager.init(key, encoder, lockedResult);
    result = lockedResult;
  }
  return result;
}

主要的邏輯是根據(jù)我們?cè)O(shè)置的參數(shù)進(jìn)行變化诸尽。也就是說(shuō)原杂,如果我們使用了 centerCrop 等參數(shù),那么這里將會(huì)對(duì)其進(jìn)行處理您机。這里的 Transformation 是一個(gè)接口穿肄,它的一系列的實(shí)現(xiàn)都是對(duì)應(yīng)于 scaleType 等參數(shù)的。

到了這里际看, Glide 所有加載圖片咸产、處理圖片的邏輯都講完了。剩下的仿村,就是將圖片顯示到 ImageView 上面了锐朴。

我們?cè)倩剡^(guò)頭來(lái)看之前講到 DecodeJob.notifyEncodeAndRelease 方法

DecodeJob

private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
  if (resource instanceof Initializable) {
    ((Initializable) resource).initialize();
  }

  Resource<R> result = resource;
  LockedResource<R> lockedResource = null;
  if (deferredEncodeManager.hasResourceToEncode()) {
    lockedResource = LockedResource.obtain(resource);
    result = lockedResource;
  }

  notifyComplete(result, dataSource);

  stage = Stage.ENCODE;
  try {
    if (deferredEncodeManager.hasResourceToEncode()) {
      deferredEncodeManager.encode(diskCacheProvider, options);
    }
  } finally {
    if (lockedResource != null) {
      lockedResource.unlock();
    }
  }
  // Call onEncodeComplete outside the finally block so that it's not called if the encode process
  // throws.
  onEncodeComplete();
}

來(lái)看 notifyComplete(result, dataSource); 方法

private void notifyComplete(Resource<R> resource, DataSource dataSource) {
  setNotifiedOrThrow();
  callback.onResourceReady(resource, dataSource);
}

回調(diào) EngineJob 的 onResourceReady 方法。

EngineJob

@Override
public void onResourceReady(Resource<R> resource, DataSource dataSource) {
  synchronized (this) {
    this.resource = resource;
    this.dataSource = dataSource;
  }
  notifyCallbacksOfResult();
}

關(guān)鍵在 notifyCallbacksOfResult 中蔼囊。

@Synthetic
void notifyCallbacksOfResult() {
  ResourceCallbacksAndExecutors copy;
  Key localKey;
  EngineResource<?> localResource;
  synchronized (this) {
    stateVerifier.throwIfRecycled();
    if (isCancelled) {
      // TODO: Seems like we might as well put this in the memory cache instead of just recycling
      // it since we've gotten this far...
      resource.recycle();
      release();
      return;
    } else if (cbs.isEmpty()) {
      throw new IllegalStateException("Received a resource without any callbacks to notify");
    } else if (hasResource) {
      throw new IllegalStateException("Already have resource");
    }
    engineResource = engineResourceFactory.build(resource, isCacheable, key, resourceListener);
    // Hold on to resource for duration of our callbacks below so we don't recycle it in the
    // middle of notifying if it synchronously released by one of the callbacks. Acquire it under
    // a lock here so that any newly added callback that executes before the next locked section
    // below can't recycle the resource before we call the callbacks.
    hasResource = true;
    copy = cbs.copy();
    incrementPendingCallbacks(copy.size() + 1);

    localKey = key;
    localResource = engineResource;
  }

  engineJobListener.onEngineJobComplete(this, localKey, localResource);

  for (final ResourceCallbackAndExecutor entry : copy) {
    entry.executor.execute(new CallResourceReady(entry.cb));
  }
  decrementPendingCallbacks();
}

在代碼的最后焚志,

    for (final ResourceCallbackAndExecutor entry : copy) {
    entry.executor.execute(new CallResourceReady(entry.cb));
  }

可以看到這里會(huì)執(zhí)行 CallResourceReady 。

CallResourceReady

private class CallResourceReady implements Runnable {

  private final ResourceCallback cb;

  CallResourceReady(ResourceCallback cb) {
    this.cb = cb;
  }

  @Override
  public void run() {
    // Make sure we always acquire the request lock, then the EngineJob lock to avoid deadlock
    // (b/136032534).
    synchronized (cb) {
      synchronized (EngineJob.this) {
        if (cbs.contains(cb)) {
          // Acquire for this particular callback.
          engineResource.acquire();
          callCallbackOnResourceReady(cb);
          removeCallback(cb);
        }
        decrementPendingCallbacks();
      }
    }
  }
}


@Synthetic
@GuardedBy("this")
void callCallbackOnResourceReady(ResourceCallback cb) {
  try {
    // This is overly broad, some Glide code is actually called here, but it's much
    // simpler to encapsulate here than to do so at the actual call point in the
    // Request implementation.
    cb.onResourceReady(engineResource, dataSource);
  } catch (Throwable t) {
    throw new CallbackException(t);
  }
}

最后還是用回調(diào)調(diào)用了 SingleRequest 的 onResourceReady 方法畏鼓。

SingleRequest

@Override
public synchronized void onResourceReady(Resource<?> resource, DataSource dataSource) {
  stateVerifier.throwIfRecycled();
  loadStatus = null;
  if (resource == null) {
    GlideException exception =
        new GlideException(
            "Expected to receive a Resource<R> with an "
                + "object of "
                + transcodeClass
                + " inside, but instead got null.");
    onLoadFailed(exception);
    return;
  }

  Object received = resource.get();
  if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
    releaseResource(resource);
    GlideException exception =
        new GlideException(
            "Expected to receive an object of "
                + transcodeClass
                + " but instead"
                + " got "
                + (received != null ? received.getClass() : "")
                + "{"
                + received
                + "} inside"
                + " "
                + "Resource{"
                + resource
                + "}."
                + (received != null
                    ? ""
                    : " "
                        + "To indicate failure return a null Resource "
                        + "object, rather than a Resource object containing null data."));
    onLoadFailed(exception);
    return;
  }

  if (!canSetResource()) {
    releaseResource(resource);
    // We can't put the status to complete before asking canSetResource().
    status = Status.COMPLETE;
    return;
  }

  onResourceReady((Resource<R>) resource, (R) received, dataSource);
}

最后調(diào)用 onResourceReady((Resource<R>) resource, (R) received, dataSource);

private synchronized void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
  // We must call isFirstReadyResource before setting status.
  boolean isFirstResource = isFirstReadyResource();
  status = Status.COMPLETE;
  this.resource = resource;

  if (glideContext.getLogLevel() <= Log.DEBUG) {
    Log.d(
        GLIDE_TAG,
        "Finished loading "
            + result.getClass().getSimpleName()
            + " from "
            + dataSource
            + " for "
            + model
            + " with size ["
            + width
            + "x"
            + height
            + "] in "
            + LogTime.getElapsedMillis(startTime)
            + " ms");
  }

  isCallingCallbacks = true;
  try {
    boolean anyListenerHandledUpdatingTarget = false;
    if (requestListeners != null) {
      for (RequestListener<R> listener : requestListeners) {
        anyListenerHandledUpdatingTarget |=
            listener.onResourceReady(result, model, target, dataSource, isFirstResource);
      }
    }
    anyListenerHandledUpdatingTarget |=
        targetListener != null
            && targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);
    // 重點(diǎn)在這里=闯辍!T平谩膳沽!
    if (!anyListenerHandledUpdatingTarget) {
      Transition<? super R> animation = animationFactory.build(dataSource, isFirstResource);
      target.onResourceReady(result, animation);
    }
  } finally {
    isCallingCallbacks = false;
  }

  notifyLoadSuccess();
}

發(fā)現(xiàn)上面的代碼調(diào)用了 target.onResourceReady(result, animation);

這里的 target 一般都是 ImageViewTarget 。ImageViewTarget 是個(gè)抽象類(lèi)让禀。

ImageViewTarget

@Override
public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
  if (transition == null || !transition.transition(resource, this)) {
    setResourceInternal(resource);
  } else {
    maybeUpdateAnimatable(resource);
  }
}

private void setResourceInternal(@Nullable Z resource) {
  // Order matters here. Set the resource first to make sure that the Drawable has a valid and
  // non-null Callback before starting it.
  setResource(resource);
  maybeUpdateAnimatable(resource);
}

setResource(resource) 是抽象方法挑社,我們到子類(lèi)中看看。我們挑 DrawableImageViewTarget 來(lái)看看吧巡揍。

DrawableImageViewTarget

@Override
protected void setResource(@Nullable Drawable resource) {
  view.setImageDrawable(resource);
}

終于痛阻,我們看到了 ImageView.setImageDrawable 來(lái)顯示圖片了,不容易啊腮敌。

總結(jié)

真的沒(méi)想到阱当,短短的一句 Glide.with(context).load("http://www.xxxx.com/xx.jpg").into(imageView) 代碼內(nèi)部竟然隱藏著如此龐大的邏輯。相信你看完這一系列的文章糜工,對(duì) Glide 會(huì)刮目相看吧弊添。

當(dāng)然,本系列還有很多 Glide 中沒(méi)講到的知識(shí)點(diǎn)捌木,比如緩存具體的應(yīng)用等油坝,如果想了解的同學(xué)可以自行去閱讀下源碼。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市免钻,隨后出現(xiàn)的幾起案子彼水,更是在濱河造成了極大的恐慌,老刑警劉巖极舔,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凤覆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡拆魏,警方通過(guò)查閱死者的電腦和手機(jī)盯桦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)渤刃,“玉大人拥峦,你說(shuō)我怎么就攤上這事÷糇樱” “怎么了略号?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)洋闽。 經(jīng)常有香客問(wèn)我玄柠,道長(zhǎng),這世上最難降的妖魔是什么诫舅? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任羽利,我火速辦了婚禮,結(jié)果婚禮上刊懈,老公的妹妹穿的比我還像新娘这弧。我一直安慰自己,他們只是感情好虚汛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布匾浪。 她就那樣靜靜地躺著,像睡著了一般卷哩。 火紅的嫁衣襯著肌膚如雪户矢。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天殉疼,我揣著相機(jī)與錄音,去河邊找鬼捌年。 笑死瓢娜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的礼预。 我是一名探鬼主播眠砾,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼托酸!你這毒婦竟也來(lái)了褒颈?” 一聲冷哼從身側(cè)響起柒巫,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谷丸,沒(méi)想到半個(gè)月后堡掏,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡刨疼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年泉唁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片揩慕。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡亭畜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出迎卤,到底是詐尸還是另有隱情拴鸵,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布蜗搔,位于F島的核電站劲藐,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏碍扔。R本人自食惡果不足惜瘩燥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望不同。 院中可真熱鬧厉膀,春花似錦、人聲如沸二拐。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)百新。三九已至企软,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間饭望,已是汗流浹背仗哨。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铅辞,地道東北人厌漂。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像斟珊,于是被迫代替她去往敵國(guó)和親苇倡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容