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 网络加载部分的,这样就只有留到下一篇文章再分析了。

相关推荐
Estar.Lee1 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
找藉口是失败者的习惯1 小时前
从传统到未来:Android XML布局 与 Jetpack Compose的全面对比
android·xml
Jinkey3 小时前
FlutterBasic - GetBuilder、Obx、GetX<Controller>、GetxController 有啥区别
android·flutter·ios
大白要努力!4 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟5 小时前
Android音频采集
android·音视频
小白也想学C6 小时前
Android 功耗分析(底层篇)
android·功耗
曙曙学编程6 小时前
初级数据结构——树
android·java·数据结构
闲暇部落9 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
诸神黄昏EX11 小时前
Android 分区相关介绍
android
大白要努力!12 小时前
android 使用SQLiteOpenHelper 如何优化数据库的性能
android·数据库·oracle