Glide 源码阅读笔记(四)

Glide 源码阅读笔记(四)

在第一篇文章中我简单介绍了 Glide 实例的创建过程,重点介绍了 Glide 的内存缓存实现和磁盘的缓存实现:Glide 源码阅读笔记(一)

在第二篇文章中介绍了 Glide 对生命周期的管理: Glide 源码阅读笔记(二)

在第三篇文章中介绍了 RequestRequestCoordinator,还简单介绍了 Registry 中的核心组件:Glide 源码阅读笔记(三)

今天的内容主要是介绍 SingleRequest 是如何处理一个图片加载的任务。

源码阅读基于 Glide 4.16.0 版本

SingleRequest

我们在前面的文章中有说到真正实现图片任务加载的是 SingleRequest,我们就以 SingleRequest#begin() 方法作为开端来分析:

Java 复制代码
  @Override
  public void begin() {
    synchronized (requestLock) {
      assertNotCallingCallbacks();
      stateVerifier.throwIfRecycled();
      startTime = LogTime.getLogTime();
      // 如何 model 为空,直接回调失败,同时返回方法
      if (model == null) {
        if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
          width = overrideWidth;
          height = overrideHeight;
        }
        int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
        onLoadFailed(new GlideException("Received null model"), logLevel);
        return;
      }
      
      // 如果当前的状态是 RUNNING,直接抛出异常
      if (status == Status.RUNNING) {
        throw new IllegalArgumentException("Cannot restart a running request");
      }
      
      // 如果当前的状态是 COMPLETE,直接回调成功,同时返回方法
      if (status == Status.COMPLETE) {
        onResourceReady(
            resource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
        return;
      }

      experimentalNotifyRequestStarted(model);

      cookie = GlideTrace.beginSectionAsync(TAG);
      // 更新状态
      status = Status.WAITING_FOR_SIZE;
      // 如果当前的尺寸合法
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        // 通知尺寸已经准备好
        onSizeReady(overrideWidth, overrideHeight);
      } else {
        // 等待 target (ImageView)完成尺寸的测量。
        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));
      }
    }
  }
 
  private enum Status {
    /** Created but not yet running. */
    PENDING,
    /** In the process of fetching media. */
    RUNNING,
    /** Waiting for a callback given to the Target to be called to determine target dimensions. */
    WAITING_FOR_SIZE,
    /** Finished loading media successfully. */
    COMPLETE,
    /** Failed to load media, may be restarted. */
    FAILED,
    /** Cleared by the user with a placeholder set, may be restarted. */
    CLEARED,
  }

这里简单说明一下上面的代码:

  1. 如何 model (加载的 url 就属于 model) 为空,直接回调失败,同时返回方法。
  2. 如果当前的状态是 RUNNING,直接抛出异常。
  3. 如果当前的状态是 COMPLETE,直接回调成功,同时返回方法。
  4. 检查当前的 withheight 是否合法,如果合法就直接调用 onSizeReady() 方法,如果不合法需要等待 target 完成测量,也就是等待 ImageView 完成测量。
  5. 最后回调加载开始。

我们来看看 ViewTarget#getSize() 是如何获取 View 尺寸的。

Java 复制代码
  @CallSuper
  @Override
  public void getSize(@NonNull SizeReadyCallback cb) {
    sizeDeterminer.getSize(cb);
  }

继续调用 SizeDeterminer#getSize() 方法。

Java 复制代码
   void getSize(@NonNull SizeReadyCallback cb) {
      // 获取当前 View 的尺寸
      int currentWidth = getTargetWidth();
      int currentHeight = getTargetHeight();
      // 如果当前 View 的尺寸合法,直接回调成功
      if (isViewStateAndSizeValid(currentWidth, currentHeight)) {
        cb.onSizeReady(currentWidth, currentHeight);
        return;
      }
      
      // 添加到监听中
      if (!cbs.contains(cb)) {
        cbs.add(cb);
      }
      if (layoutListener == null) {
        // 在 ViewTreeObserver 中添加 PreDraw 时的监听。
        ViewTreeObserver observer = view.getViewTreeObserver();
        layoutListener = new SizeDeterminerLayoutListener(this);
        observer.addOnPreDrawListener(layoutListener);
      }
    }
    
    private int getTargetHeight() {
      int verticalPadding = view.getPaddingTop() + view.getPaddingBottom();
      LayoutParams layoutParams = view.getLayoutParams();
      int layoutParamSize = layoutParams != null ? layoutParams.height : PENDING_SIZE;
      return getTargetDimen(view.getHeight(), layoutParamSize, verticalPadding);
    }

    private int getTargetWidth() {
      int horizontalPadding = view.getPaddingLeft() + view.getPaddingRight();
      LayoutParams layoutParams = view.getLayoutParams();
      int layoutParamSize = layoutParams != null ? layoutParams.width : PENDING_SIZE;
      return getTargetDimen(view.getWidth(), layoutParamSize, horizontalPadding);
    }
    
    private int getTargetDimen(int viewSize, int paramSize, int paddingSize) {
      int adjustedParamSize = paramSize - paddingSize;
      if (adjustedParamSize > 0) {
        return adjustedParamSize;
      }

      if (waitForLayout && view.isLayoutRequested()) {
        return PENDING_SIZE;
      }

      int adjustedViewSize = viewSize - paddingSize;
      if (adjustedViewSize > 0) {
        return adjustedViewSize;
      }

      if (!view.isLayoutRequested() && paramSize == LayoutParams.WRAP_CONTENT) {
        if (Log.isLoggable(TAG, Log.INFO)) {
          // ...
        }
        return getMaxDisplayLength(view.getContext());
      }
      
      return PENDING_SIZE;
    }
  

获取 View 的尺寸在我们的实际的开发中也是经常遇到的,我们来看看 Glide 是怎么做的,它给我们写了一个非常好的样板代码。

  1. 获取当前的 View 的尺寸,并判断尺寸是否合法(其实就是大于 0 就合法),他获取当前尺寸的方式有两种,首先从 ViewLayoutParams 中获取尺寸,如果这个尺寸合法,就直接使用,如果 LayoutParams 中的尺寸不合法,就从 View 中去获取,计算可用的尺寸时都会减去 Padding 值。

  2. 如果当前 View 的尺寸不可用,在 ViewTreeObserver 中添加一个 OnPreDrawListener 去监听,它的实现类是 SizeDeterminerLayoutListener

我们继续看看 SizeDeterminerLayoutListener 的代码:

Java 复制代码
    private static final class SizeDeterminerLayoutListener
        implements ViewTreeObserver.OnPreDrawListener {
      private final WeakReference<SizeDeterminer> sizeDeterminerRef;

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

      @Override
      public boolean onPreDraw() {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
          Log.v(TAG, "OnGlobalLayoutListener called attachStateListener=" + this);
        }
        SizeDeterminer sizeDeterminer = sizeDeterminerRef.get();
        if (sizeDeterminer != null) {
          sizeDeterminer.checkCurrentDimens();
        }
        return true;
      }
    }

注意这里它持有的 SizeDeterminer 是弱引用,防止内存泄漏。当 onPreDraw() 方法回调时,会调用 SizeDeterminer#checkCurrentDimens() 方法,我们看看这个方法的实现:

Java 复制代码
    @Synthetic
    void checkCurrentDimens() {
      if (cbs.isEmpty()) {
        return;
      }

      int currentWidth = getTargetWidth();
      int currentHeight = getTargetHeight();
      if (!isViewStateAndSizeValid(currentWidth, currentHeight)) {
        return;
      }

      notifyCbs(currentWidth, currentHeight);
      clearCallbacksAndListener();
    }

先获取 View 的尺寸,同样先是获取 LayoutParams 中的,如果获取失败再去直接拿 View 中的,如果获取到的尺寸是可用的,通知每个 Callback,然后移除所有的 Callback

我们再看看 SingleRequest 中的 onSizeReady() 方法是如何处理的:

Java 复制代码
  @Override
  public void onSizeReady(int width, int height) {
    stateVerifier.throwIfRecycled();
    synchronized (requestLock) {
      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);

      if (status != Status.RUNNING) {
        loadStatus = null;
      }
      if (IS_VERBOSE_LOGGABLE) {
        logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
      }
    }
  }

上面的代码比较简单,就是直接调用 Engine#load() 方法,其中返回值 loadStatus 可以用来控制加载的任务。

Engine

我们继续看看 Engine#load() 方法中具体做了什么事情:

Java 复制代码
  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;

    EngineKey key =
        keyFactory.buildKey(
            model,
            signature,
            width,
            height,
            transformations,
            resourceClass,
            transcodeClass,
            options);

    EngineResource<?> memoryResource;
    synchronized (this) {
      // 先尝试从内存缓存中加载
      memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);

      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);
      }
    }
    // 内存缓存获取成功,直接通知回调
    cb.onResourceReady(
        memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
    return null;
  }

上面代码比较简单,通过 loadFromMemory() 去加载内存中的缓存,如果内存中的缓存为空,再通过 waitForExistingOrStartNewJob() 方法去加载磁盘中的缓存或者通过网络去加载。

加载内存缓存

我们先看看 loadFromMemory() 方法是如何加载内存中的缓存的:

Java 复制代码
  @Nullable
  private EngineResource<?> loadFromMemory(
      EngineKey key, boolean isMemoryCacheable, long startTime) {
    if (!isMemoryCacheable) {
      return null;
    }
    
    // 加载存活的资源
    EngineResource<?> active = loadFromActiveResources(key);
    if (active != null) {
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return active;
    }
    // 加载缓存中的资源
    EngineResource<?> cached = loadFromCache(key);
    if (cached != null) {
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return cached;
    }

    return null;
  }

内存中的缓存分为两种,存活的资源和缓存中的资源。你可能有点懵,我来解释一下。

  • 存活的资源
    存活的资源其实就是别的地方正在使用的资源,例如 ImageViewA 加载了一个资源 X,而且已经显示在屏幕上了,这个时候 ImageViewB 也去加载资源 X,这个时候就不需要再去加载,直接复用 ImageViewA 加载的资源 X 就好了。
  • 缓存中的资源
    这个就是内存缓存的资源,我们在第一篇文章中就有介绍内存的缓存类,它的实现类是 LruResourceCache,缓存的大小是 2 倍屏幕大小图片所占用的内存,如果忘记了的同学可以看看我的第一篇文章。

我们来看看 loadFromActiveResources() 方法是如何加载存活的资源的:

Java 复制代码
  @Nullable
  private EngineResource<?> loadFromActiveResources(Key key) {
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
      active.acquire();
    }

    return active;
  }

通过 ActivityResources#get() 方法来获取资源的,资源用 EngineResource 来封装,获取到的资源然后回调用 EngineResource#acquire() 方法,表示增加一次它的引用。

我们来看看 ActivityResources#get() 方法的实现:

Java 复制代码
  @VisibleForTesting final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
  
  @Nullable
  synchronized EngineResource<?> get(Key key) {
    ResourceWeakReference activeRef = activeEngineResources.get(key);
    if (activeRef == null) {
      return null;
    }

    EngineResource<?> active = activeRef.get();
    if (active == null) {
      cleanupActiveReference(activeRef);
    }
    return active;
  }

它是直接储存在 HashMap 中,Value 是用 ResourceWeakReference 封装,他继承于 WeakReference,也就是我们常说的弱引用。

ActivityResources#activate() 方法是用来添加资源对象,而 ActivityResources#deactivate() 方法就是用来回收资源对象,我们来看看他们的实现:

Java 复制代码
  synchronized void activate(Key key, EngineResource<?> resource) {
    ResourceWeakReference toPut =
        new ResourceWeakReference(
            key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);

    ResourceWeakReference removed = activeEngineResources.put(key, toPut);
    if (removed != null) {
      removed.reset();
    }
  }
  
  synchronized void deactivate(Key key) {
    ResourceWeakReference removed = activeEngineResources.remove(key);
    if (removed != null) {
      removed.reset();
    }
  }

上面的代码比较简单我就不再说明了,不过这里还有一个关键点,就是在构建 ResourceWeakReference 弱引用时添加了一个引用队列 resourceReferenceQueue,被移除了的弱引用会被添加到这个引用队列中,不清楚的同学可以去查找一下相关的资料。

在构造函数中会在一个线程池中监听 resourceReferenceQueue 的状态:

Java 复制代码
  @VisibleForTesting
  ActiveResources(
      boolean isActiveResourceRetentionAllowed, Executor monitorClearedResourcesExecutor) {
    this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed;
    this.monitorClearedResourcesExecutor = monitorClearedResourcesExecutor;

    monitorClearedResourcesExecutor.execute(
        new Runnable() {
          @Override
          public void run() {
            cleanReferenceQueue();
          }
        });
  }

我们再来看看 cleanReferenceQueue() 方法的实现:

Java 复制代码
  @Synthetic
  void cleanReferenceQueue() {
    while (!isShutdown) {
      try {
        ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
        cleanupActiveReference(ref);

        // This section for testing only.
        DequeuedResourceCallback current = cb;
        if (current != null) {
          current.onResourceDequeued();
        }
        // End for testing only.
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
      }
    }
  }

其实就是监控被移除的 ResourceWeakReference,然后再做一些清理工作。

到这里 ActivityResources 我们就介绍完了。前面我们说到 EngineResource 会调用 acquire() 方法来增加引用次数。我们来看看它:

Java 复制代码
  synchronized void acquire() {
    if (isRecycled) {
      throw new IllegalStateException("Cannot acquire a recycled resource");
    }
    ++acquired;
  }

直接把 acquired 变量加 1。我们再看看减少引用次数的方法 release() 的实现:

Java 复制代码
  void release() {
    boolean release = false;
    synchronized (this) {
      if (acquired <= 0) {
        throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
      }
      if (--acquired == 0) {
        release = true;
      }
    }
    if (release) {
      listener.onResourceReleased(key, this);
    }
  }

直接把 acquired 变量减 1,如果 acquired 变成 0 了就表示没有引用了,那么就会调用 listener.onResourceReleased() 方法,这个 listener 其实就是 Engine,我们继续看看 Engine#onResourceReleased() 方法怎么处理:

Java 复制代码
  @Override
  public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    activeResources.deactivate(cacheKey);
    if (resource.isMemoryCacheable()) {
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource, /* forceNextFrame= */ false);
    }
  }

上面代码简单,如果允许使用内存缓存,直接缓存到 LruResourceCache 中,如果不允许缓存直接通过 ResourceRecycler#recycle() 方法回收。

到这里存活的资源的管理方式就介绍完了。

我们继续看看 loadFromCache() 方法获取缓存中的资源:

Java 复制代码
  private EngineResource<?> loadFromCache(Key key) {
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      cached.acquire();
      activeResources.activate(key, cached);
    }
    return cached;
  }

  private EngineResource<?> getEngineResourceFromCache(Key key) {
    Resource<?> cached = cache.remove(key);

    final EngineResource<?> result;
    if (cached == null) {
      result = null;
    } else if (cached instanceof EngineResource) {
      // Save an object allocation if we've cached an EngineResource (the typical case).
      result = (EngineResource<?>) cached;
    } else {
      result =
          new EngineResource<>(
              cached,
              /* isMemoryCacheable= */ true,
              /* isRecyclable= */ true,
              key,
              /* listener= */ this);
    }
    return result;
  }

上面的代码非常简单,直接从 LruResourceCache 获取就行了,同时会移除对应的缓存,如果获取到的缓存不是 EngineResource 类型,还会重新用 EngineResource 封装一下,获取到的缓存还会被添加到 ActiveResources 中,表示当前存活的资源。

加载磁盘中的缓存

我们继续看看 Engine#load() 方法中调用的 waitForExistingOrStartNewJob() 是如何工作的:

Java 复制代码
  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);
  }

所有的任务都会被添加到 jobs 成员变量中,这里会创建两个 Job,分别是 DecodeJobEngineJob,他们都互相持有对方的引用,EngineJob 主要负责线程调度和通知上层 Engine 各种加载状态,而 DecodeJob 主要负责图片加载任务的处理。最后返回一个 LoadStatus 对象,SingleRequest 就可以通过这个对象来取消任务。

我们看看 EngineJob#start() 方法的代码:

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

如果允许磁盘缓存,DecodeJob 就会被添加到 diskCacheExecutor 中执行。

我们继续看看 DecodeJob#run() 方法:

Java 复制代码
  @Override
  public void run() {
    GlideTrace.beginSectionFormat("DecodeJob#run(reason=%s, model=%s)", runReason, model);
    DataFetcher<?> localFetcher = currentFetcher;
    try {
      if (isCancelled) {
        notifyFailed();
        return;
      }
      runWrapped();
    } catch (CallbackException e) {
      throw e;
    } catch (Throwable t) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(
            TAG,
            "DecodeJob threw unexpectedly" + ", isCancelled: " + isCancelled + ", stage: " + stage,
            t);
      }
      if (stage != Stage.ENCODE) {
        throwables.add(t);
        notifyFailed();
      }
      if (!isCancelled) {
        throw t;
      }
      throw t;
    } finally {
      if (localFetcher != null) {
        localFetcher.cleanup();
      }
      GlideTrace.endSection();
    }
  }

忽略各种错误处理和通知,这里会调用 runWrapped() 方法,我们继续跟进:

Java 复制代码
  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);
    }
  }
  
  /** Why we're being executed again. */
  private enum RunReason {
    /** The first time we've been submitted. */
    INITIALIZE,
    /** We want to switch from the disk cache service to the source executor. */
    SWITCH_TO_SOURCE_SERVICE,
    /**
     * We retrieved some data on a thread we don't own and want to switch back to our thread to
     * process the data.
     */
    DECODE_DATA,
  }

默认的 RunReasonINITIALIZE,所以首先会调用 getNextState(),然后调用 getNextGenerator(),最后调用 runGenerators(),分别看看上面的方法:

Java 复制代码
  private Stage getNextStage(Stage current) {
    switch (current) {
      case INITIALIZE:
        // 判断当前是否支持 ResourceCache,如果支持下一个状态就是 RESOURCE_CACHE,如果不支持就重新计算
        return diskCacheStrategy.decodeCachedResource()
            ? Stage.RESOURCE_CACHE
            : getNextStage(Stage.RESOURCE_CACHE);
      case RESOURCE_CACHE:
        // 判断是否支持 DataCache,如果支持下一个状态就是 RESOURCE_CACHE,如果不支持久重新计算。
        return diskCacheStrategy.decodeCachedData()
            ? Stage.DATA_CACHE
            : getNextStage(Stage.DATA_CACHE);
      case DATA_CACHE:
        // Skip loading from source if the user opted to only retrieve the resource from cache.
        // 判断是不是只能使用磁盘缓存,如果是下个状态就是 FINISHED,反之就是 SOURCE。
        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        return Stage.FINISHED;
      default:
        throw new IllegalArgumentException("Unrecognized stage: " + current);
    }
  }

我相信很多人对 RESOUCE_CACHEDATA_CACHE 可能分不清,我这里简单解释一下上面几个比较重要的状态。

  • RESOUCE_CACHE
    缓存处理过的原来的网络图片,比如图片的尺寸等等优化过,也可能根据 ScaleType 裁剪过。
  • DATA_CACHE
    缓存未经过原来处理过的网络图片,也就是图片原来是什么样子,缓存就是什么样子。
  • SOURCE
    通过网络去获取图片资源。

Glide 默认的磁盘缓存策略是 AUTOMATIC,默认的网络图片是使用的 DATA_CACHE

我们继续看 getNextGenerator() 方法的实现:

Java 复制代码
  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);
    }
  }

上面代码非常简单,RESOURCE_CACHEResourceCacheGenerator 来处理;DATA_CACHEDataCacheGenerator 来处理;SOURCESourceGenerator 来处理。

我们继续看看 runGenerators() 方法:

Java 复制代码
  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(RunReason.SWITCH_TO_SOURCE_SERVICE);
        return;
      }
    }
    // We've run out of stages and generators, give up.
    if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
      notifyFailed();
    }
  }

上面方法也是比较简单直接调用 Generator#startNext() 方法来执行对应的任务,如果方法返回 false 表示继续执行下一个状态的 Generator,最后直到执行完所有的 Generator,注意这里如果状态是 SOURCE,也就是网络请求的状态,会调用 reschedule() 方法,同时 RunReasonSWITCH_TO_SOURCE_SERVICE,其实就是网络请求的 Generator 会在另外的线程执行。

看看 reschedule() 方法:

Java 复制代码
  private void reschedule(RunReason runReason) {
    this.runReason = runReason;
    callback.reschedule(this);
  }

这个 callback 其实就是 EngineJob,我们再看看 EngineJob#reschedule()

Java 复制代码
@Override  
public void reschedule(DecodeJob<?> job) {  
   getActiveSourceExecutor().execute(job);  
}

我们看到这里会切换至 SourceExecutor 继续执行后续的任务。

我们来看看 ResourceCacheGenerator#startNext() 是如何执行加载 RESOURCE_CACHE 缓存任务的:

Java 复制代码
  @Override
  public boolean startNext() {
    GlideTrace.beginSection("ResourceCacheGenerator.startNext");
    try {
      // 获取所有的支持的 ModelLoader 中的 LoadData 对象的 Keys。
      List<Key> sourceIds = helper.getCacheKeys();
      if (sourceIds.isEmpty()) {
        return false;
      }
      // 获取所有的可用的 Transcoder 转换后的数据的 Class 对象(我们的 demo 中是 Drawable)
      List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();
      if (resourceClasses.isEmpty()) {
        if (File.class.equals(helper.getTranscodeClass())) {
          return false;
        }
        // 如果没有可用的 Transcoder 直接抛出异常。
        throw new IllegalStateException(
            "Failed to find any load path from "
                + helper.getModelClass()
                + " to "
                + helper.getTranscodeClass());
      }
      // 遍历所有的 ModelLoader 和 ResourceClasses 对象生成的 key,通过这个 key 去查找到一个可用的缓存文件对应的 ModelLoader。
      while (modelLoaders == null || !hasNextModelLoader()) {
        resourceClassIndex++;
        if (resourceClassIndex >= resourceClasses.size()) {
          sourceIdIndex++;
          if (sourceIdIndex >= sourceIds.size()) {
            return false;
          }
          resourceClassIndex = 0;
        }
        // 获取对应的 ModelLoader 中的 Key
        Key sourceId = sourceIds.get(sourceIdIndex);
        // 获取对应 Transcoder 输出的 class
        Class<?> resourceClass = resourceClasses.get(resourceClassIndex);
        // 获取裁剪方式的 Transformation
        Transformation<?> transformation = helper.getTransformation(resourceClass);
        // 通过上面的参数生成各种 Key。
        currentKey =
            new ResourceCacheKey( // NOPMD AvoidInstantiatingObjectsInLoops
                helper.getArrayPool(),
                sourceId,
                helper.getSignature(),
                helper.getWidth(),
                helper.getHeight(),
                transformation,
                resourceClass,
                helper.getOptions());
        // 通过生成的 Key 从 DiskLruCache 中去查找缓存文件.        
        cacheFile = helper.getDiskCache().get(currentKey);
        if (cacheFile != null) {
          // 缓存文件不为空,去查找能够处理 File 类型的 ModelLoaders。
          sourceKey = sourceId;
          modelLoaders = helper.getModelLoaders(cacheFile);
          modelLoaderIndex = 0;
        }
      }

      loadData = null;
      boolean started = false;
      // 遍历找到的所有的 ModelLoader,找到一个可用的去加载 File 缓存文件。
      while (!started && hasNextModelLoader()) {
        ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
        loadData =
            modelLoader.buildLoadData(
                cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions());
        if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
          started = true;
          通过 ModelLoader 中 loadData 中的 fetcher 去加载缓存的 File
          loadData.fetcher.loadData(helper.getPriority(), this);
        }
      }
      // 最后的返回结果表示是否拿到可用的缓存.
      return started;
    } finally {
      GlideTrace.endSection();
    }
  }

如果对 Registry 中的核心组件不熟悉的同学可以看看我前面的一篇文章,总的来说处理 model (url) 的流程就是:ModelLoader -> Decoder -> Transcoder

上面的代码稍微有点小小的复杂,总的来说就是遍历所有的可用的 ModelLoaderTrascoder 最后的输出 ResourceClass 对象生成的 Key,然后通过这个 Key 从本地缓存中去查找对应的文件,如果有查找到对应的文件,然后去查找处理 File 类型的 ModelLoader,然后通过 ModelLoader 去加载对应的 File

这里得注意一下查找缓存文件所对应的 ResourceCacheKey,它是由 ModelLoader ( Url )、withheightResourceClassTrascoder 转换后的对象 Class)和 Signature。我这里要单独说明一下这个 Signature,除了别的参数外,我们可以简单的理解为通过 model (url) 来生成缓存文件 Key 的,大多数情况下我们开发中也是一个 url 对应的一个图片,不过这里有例外,一个 url 可能对应多个图片,比如我有这样一个 url 表示今日美图: https://img.tans.com。也就是这个 url 会根据日期去返回不同的图片。如果是这样 Glide 默认的缓存机制就会出现问题,当我今天加载图片并缓存后,第二天还是会使用这个缓存,而不会去真正去请求网络,这不是我们希望看到的结果。所以我们需要自定义一个 Signature 来生成组合的 Key,我们就可以使用日期来作为 Signature,这样我们就能够解决上面遇到的问题。

上面多说了一点题外的话,我们接着看加载 FileModelLoader 加载成功后接着怎么处理,加载成功的回调方法是 ResourceCacheGenerator#onDataReady()

Java 复制代码
  @Override
  public void onDataReady(Object data) {
    cb.onDataFetcherReady(
        sourceKey, data, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE, currentKey);
  }

继续回调 DecodeJob#onDataFetcherReady() 方法:

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;
    this.isLoadingFromAlternateCacheKey = sourceKey != decodeHelper.getCacheKeys().get(0);
    // 只有执行了网络加载才会切线程,缓存不用切线程
    if (Thread.currentThread() != currentThread) {
      reschedule(RunReason.DECODE_DATA);
    } else {
      GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
      try {
      
        // 缓存的代码执行这里
        decodeFromRetrievedData();
      } finally {
        GlideTrace.endSection();
      }
    }
  }

后续的代码继续调用 decodeFromRetrievedData() 方法:

Java 复制代码
  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);
    }
    if (resource != null) {
      // 通知解码成功
      notifyEncodeAndRelease(resource, currentDataSource, isLoadingFromAlternateCacheKey);
    } else {
      runGenerators();
    }
  }

首先通过 decodeFromData() 方法来解码数据,如果解码成功,通过 notifyEncodeAndRelease() 通知成功。

我们来看看 decodeFromData() 方法:

Java 复制代码
  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();
    }
  }

接着往下调用 decodeFromFetcher() 方法:

Java 复制代码
  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);
  }

上面的 DecodeHelper#getLoadPath() 方法就是查找到一个能够处理当前 dataLoadPath,它其中封装了对应的 DecoderTranscoder 等等关键组件,我们继续追踪 runLoadPath() 这个方法:

Java 复制代码
  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();
    }
  }

上面的 DataRewinder 就是用来让我们的 InputStream 可以重置,然后重复读取的处理对象。通过 LoadPath#load() 就能够获取到最后的结果了,这个他还添加了一个 DecodeCallback 回调。

Java 复制代码
    @NonNull
    @Override
    public Resource<Z> onResourceDecoded(@NonNull Resource<Z> decoded) {
      return DecodeJob.this.onResourceDecoded(dataSource, decoded);
    }

这个时候会回调 DecodeJob#onResourceDecoded() 方法,这个方法中会处理磁盘缓存的写入。我们讲网络加载的时候再介绍这个方法。

然后再看看 notifyEncodeAndRelease() 加载资源成功后的处理:

Java 复制代码
  private void notifyEncodeAndRelease(
      Resource<R> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {
    GlideTrace.beginSection("DecodeJob.notifyEncodeAndRelease");
    try {
      // ...

      notifyComplete(result, dataSource, isLoadedFromAlternateCacheKey);

      // ...
    } finally {
      GlideTrace.endSection();
    }
  }

然后继续调用 notifyComplete() 方法,我注释掉了缓存相关的代码,后续讲网络的时候再看。

Java 复制代码
  private void notifyComplete(
      Resource<R> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {
    setNotifiedOrThrow();
    callback.onResourceReady(resource, dataSource, isLoadedFromAlternateCacheKey);
  }

这个 callback 就是 EngineJob,我们来看看它的 onResourceReady() 方法:

Java 复制代码
  @Override
  public void onResourceReady(
      Resource<R> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {
    synchronized (this) {
      this.resource = resource;
      this.dataSource = dataSource;
      this.isLoadedFromAlternateCacheKey = isLoadedFromAlternateCacheKey;
    }
    notifyCallbacksOfResult();
  }

继续看看 notifyCallbacksOfResult() 方法:

Java 复制代码
  void notifyCallbacksOfResult() {
    ResourceCallbacksAndExecutors copy;
    Key localKey;
    EngineResource<?> localResource;
    synchronized (this) {
      stateVerifier.throwIfRecycled();
      if (isCancelled) {
        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);
     
      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();
  }

EngineJobListener 就是直接通知 Engine,对应的 Entry 就是通知 SingleRequest

我们先来看看 Engine#onEngineJobComplete() 是怎么处理的:

Java 复制代码
  @Override
  public synchronized void onEngineJobComplete(
      EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
    // A null resource indicates that the load failed, usually due to an exception.
    if (resource != null && resource.isMemoryCacheable()) {
      activeResources.activate(key, resource);
    }

    jobs.removeIfCurrent(key, engineJob);
  }

这里做两件事情,把当前的资源添加到 ActiveResources 中去,也就是存活的内存资源,前面讲过,然后把当前任务从 jobs 中移除。

我们继续看看 SingleRequest#onResourceReady()

Java 复制代码
  @Override
  public void onResourceReady(
      Resource<?> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {
    stateVerifier.throwIfRecycled();
    Resource<?> toRelease = null;
    try {
      synchronized (requestLock) {
        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())) {
          toRelease = resource;
          this.resource = null;
          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()) {
          toRelease = resource;
          this.resource = null;
          // We can't put the status to complete before asking canSetResource().
          status = Status.COMPLETE;
          GlideTrace.endSectionAsync(TAG, cookie);
          return;
        }

        onResourceReady(
            (Resource<R>) resource, (R) received, dataSource, isLoadedFromAlternateCacheKey);
      }
    } finally {
      if (toRelease != null) {
        engine.release(toRelease);
      }
    }
  }

代码都看吐了,处理很简单就是更新一下状态,然后继续向 parent 通知状态。然后还会调用 Target#onResourceReady() 方法:

我们看看 ImageViewTarget#onReousrceReady() 是怎么处理的:

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

这其实就分两种情况,一种是有动画(所谓的动画也是不断修改 ImageView 中的 Drawable 而已,我就不分析了),一种是没有动画:

Java 复制代码
  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);
  }
  
  protected void setResource(@Nullable Drawable resource) {
    view.setImageDrawable(resource);
  }

终于把加载 RESOUCE_CACHE 的逻辑讲完了,不过后续的 DATA_CACHE 的加载与 SOURCE 的加载很多的逻辑都是和这个是一样的,我们只需要看看不同的地方就行了。

我们再来看看 DataCacheGenerator#startNext() (DATA_CACHE) 怎么处理的:

Java 复制代码
  @Override
  public boolean startNext() {
    GlideTrace.beginSection("DataCacheGenerator.startNext");
    try {
      while (modelLoaders == null || !hasNextModelLoader()) {
        sourceIdIndex++;
        if (sourceIdIndex >= cacheKeys.size()) {
          return false;
        }

        Key sourceId = cacheKeys.get(sourceIdIndex);
        @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
        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;
      while (!started && hasNextModelLoader()) {
        ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
        loadData =
            modelLoader.buildLoadData(
                cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions());
        if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
          started = true;
          loadData.fetcher.loadData(helper.getPriority(), this);
        }
      }
      return started;
    } finally {
      GlideTrace.endSection();
    }
  }

几乎和 ResourceCacheGenerator 中的处理方式一模一样,只是查找缓存文件的 KeyDataCacheKey,他只由 ModelLoaderSignature 构成。而没有了前面的 withheight 等等参数,这也是 RESOURCE_CACHEDATA_CACHE 的区别。

最后

Glide 的代码真的有点累,本篇文章就只看了内存缓存和磁盘缓存的逻辑都又写了这么多了,本来还要写 SOURCE 网络加载部分的,这样就只有留到下一篇文章再分析了。

相关推荐
服装学院的IT男2 小时前
【Android 13源码分析】Activity生命周期之onCreate,onStart,onResume-2
android
Arms2062 小时前
android 全面屏最底部栏沉浸式
android
服装学院的IT男2 小时前
【Android 源码分析】Activity生命周期之onStop-1
android
ChinaDragonDreamer5 小时前
Kotlin:2.0.20 的新特性
android·开发语言·kotlin
网络研究院7 小时前
Android 安卓内存安全漏洞数量大幅下降的原因
android·安全·编程·安卓·内存·漏洞·技术
凉亭下7 小时前
android navigation 用法详细使用
android
小比卡丘10 小时前
C语言进阶版第17课—自定义类型:联合和枚举
android·java·c语言
前行的小黑炭11 小时前
一篇搞定Android 实现扫码支付:如何对接海外的第三方支付;项目中的真实经验分享;如何高效对接,高效开发
android
落落落sss12 小时前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
代码敲上天.13 小时前
数据库语句优化
android·数据库·adb