Glide之EngineJob和EngineResource

EngineJob中的代碼不多,整體思路也比較簡單。

A class that manages a load by adding and removing callbacks for for the load and notifying callbacks when the load completes.

EngineJob管理加載,維護着callback的添加和移除,當加載完成時通知維護的所有callback。

上面是官方對EngineJob的註釋,基本描述清楚了EngineJob的用途,需要補充的是,EngineJob還負責在請求過程中的線程切換,比如,一開始從主線程切換到DiskCacheExecutor線程,嘗試從磁盤獲取數據,如果獲取不到,線程切換到SourceExecutor中的線程去網絡獲取,獲取到又切換了一次線程,只不過用的還是SourceExecutor中的線程去把數據存到磁盤,取出數據,然後回調給EngineJob,最後切換到主線程,這樣主線程中就可以拿到數據了。

不過在詳細分析EngineJob過程中,有涉及到EngineResource的緩存和釋放邏輯,我們分析一下:

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

    listener.onEngineJobComplete(this, localKey, localResource);

    for (final ResourceCallbackAndExecutor entry : copy) {
      entry.executor.execute(new CallResourceReady(entry.cb));
    }
    decrementPendingCallbacks();
  }
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.

上面這段英文嘗試翻譯一下

在下面所有回調期間保持resource不釋放,所以,假如其中一個回調同步釋放,我們也要保證不回收resouce。我們先獲取resource,使得在我們調用完所有callback之前,任何callback都不能釋放resource。

 

我們先說明一下resouce是怎麼釋放的,這裏的resouce是EngineResource,看看EngineResouce的release方法

  void release() {
    // To avoid deadlock, always acquire the listener lock before our lock so that the locking
    // scheme is consistent (Engine -> EngineResource). Violating this order leads to deadlock
    // (b/123646037).
    synchronized (listener) {
      synchronized (this) {
        if (acquired <= 0) {
          throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
        }
        if (--acquired == 0) {
          listener.onResourceReleased(key, this);
        }
      }
    }
  }

acquired變量表示有多少地方引用了這Resource,如果acquired等於0的話,就把EngineResource真正釋放,onResourceReleased在Engine裏面,我們在緩存文章中會分析。

我們在看看EngineJob中callback執行的代碼,entry.executor.execute(new CallResourceReady(entry.cb));這塊代碼就是到主線程裏面執行callback,我們看看CallResourceRready這個類


  private class CallResourceReady implements Runnable {

    private final ResourceCallback cb;

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

    @Override
    public void run() {
      synchronized (EngineJob.this) {
        if (cbs.contains(cb)) {
          // Acquire for this particular callback.
          engineResource.acquire();
          callCallbackOnResourceReady(cb);
          removeCallback(cb);
        }
        decrementPendingCallbacks();
      }
    }
  }

我們看engineResource.acquire(); 這句話,就是把acquired的變量加1,表示有一個地方引用了EngineResource。callCallbackOnResourceReady裏面調用了回調,我們考慮一種情況,假如在callback中調用EngineReource的release。這樣的話,就可能出問題,這樣一次執行,acquired變成了0,導致EngineReouse被釋放了,這是不對的,因爲還可能有其它callback。

所以,想了一招,在執行callback之前,先把有多少個callback記下,pendingCallbacks,同時調用engineResource.acquire();先把acquired加1,這樣在callback中調用EngineReource的release也不怕了,因爲acquired不會變成0了,每執行完一個callback把pendingCallbacks數量減1,直到pendingCallbacks等於0了,調用一次EngineReource的release,讓acquired變成真實的值。

第二部分

我們再看看notifyCallbacksOfResult中的 listener.onEngineJobComplete(this, localKey, localResource);這句話,listener是Engine

  @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.setResourceListener(key, this);

      if (resource.isCacheable()) {
        activeResources.activate(key, resource);
      }
    }

    jobs.removeIfCurrent(key, engineJob);
  }

就是把EngineResource放到ActiveResources緩存裏面。

第三部分

我們再來看看EngineResource被釋放的時候,具體做了什麼,看EngineResource release中的listener.onResourceReleased(key, this);這句話,listener是Engine

  @Override
  public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    activeResources.deactivate(cacheKey);
    if (resource.isCacheable()) {
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
  }

就是把EngineResource中ActiveResources緩存中移除,放到LruResourceCache緩存裏面。

第四部分

我們上面還差一部分沒說,就是EngineResource的acquire是怎麼變成0的?

這塊內容就涉及到Glide的生命週期了,我們知道Glide的請求過程是能夠感知到宿主的生命週期的。

我們看看RequestManager的onDestroy方法:

  public synchronized void onDestroy() {
    targetTracker.onDestroy();
    for (Target<?> target : targetTracker.getAll()) {
      clear(target);
    }
    targetTracker.clear();
    requestTracker.clearRequests();
    lifecycle.removeListener(this);
    lifecycle.removeListener(connectivityMonitor);
    mainHandler.removeCallbacks(addSelfToLifecycle);
    glide.unregisterRequestManager(this);
  }

我們知道一個FragmentManager有唯一的一個RequestManager,調用RequestManager的load方法會new一個RequestBuilder,看這個名字就知道它的使命是實例化Request,事實上也是調用RequestBuilder的into方法就會實例化一個SingleRequest,一個Request就代表一次請求。

需要說明的是,每次請求和目標對象都是由RequestManager維護的,RequestManager中有RequestTracker維護和這個RM相關的所有Request,有TargetTracker維護和這個RM相關的所有Target。

說到這裏我們再看RequestManager的onDestroy方法,requestTracker.clearRequests()就是在頁面退出的時候,把所有的Request清除掉的

  /**
   * Cancels all requests and clears their resources.
   *
   * <p>After this call requests cannot be restarted.
   */
  public void clearRequests() {
    for (Request request : Util.getSnapshot(requests)) {
      // It's unsafe to recycle the Request here because we don't know who might else have a
      // reference to it.
      clearRemoveAndMaybeRecycle(request, /*isSafeToRecycle=*/ false);
    }
    pendingRequests.clear();
  }


  private boolean clearRemoveAndMaybeRecycle(@Nullable Request request, boolean isSafeToRecycle) {
     if (request == null) {
       // If the Request is null, the request is already cleared and we don't need to search further
       // for its owner.
      return true;
    }
    boolean isOwnedByUs = requests.remove(request);
    // Avoid short circuiting.
    isOwnedByUs = pendingRequests.remove(request) || isOwnedByUs;
    if (isOwnedByUs) {
      request.clear();
      if (isSafeToRecycle) {
        request.recycle();
      }
    }
    return isOwnedByUs;
  }

看request.clear()調用

  /**
   * Cancels the current load if it is in progress, clears any resources held onto by the request
   * and replaces the loaded resource if the load completed with the placeholder.
   *
   * <p>Cleared requests can be restarted with a subsequent call to {@link #begin()}
   *
   * @see #cancel()
   */
  @Override
  public synchronized void clear() {
    assertNotCallingCallbacks();
    stateVerifier.throwIfRecycled();
    if (status == Status.CLEARED) {
      return;
    }
    cancel();
    // Resource must be released before canNotifyStatusChanged is called.
    if (resource != null) {
      releaseResource(resource);
    }
    if (canNotifyCleared()) {
      target.onLoadCleared(getPlaceholderDrawable());
    }

    status = Status.CLEARED;
  }

  private void releaseResource(Resource<?> resource) {
    engine.release(resource);
    this.resource = null;
  }

繼續看engine.release(resource)

  public void release(Resource<?> resource) {
    if (resource instanceof EngineResource) {
      ((EngineResource<?>) resource).release();
    } else {
      throw new IllegalArgumentException("Cannot release anything but an EngineResource");
    }
  }

看到沒?調用了EngineResource的release方法,上面分析過了。

 

總結:

通過上面的分析,我們知道:

(1)數據Resource被請求回來包裝成EngineResource後,是先放到ActiveResource內存緩存裏面的。

(2)只要EngineResource有被持有,也就是acquire不等於0,它就一直緩存在ActiveResources中。

(3)EngineResource沒有被持有了,也就是acquire等於0了,它就被緩存到LruResourceCache,或者被釋放,根據設置條件來的。

 

學習內容:

(1)Request在RequestTracker是怎麼維護的

(2)Request是怎麼被回收掉的

是在RequestManager的onDestroy中處理的,調用了Request的recycle方法,把Request回收到了對象池裏面。

(3)EngineJob是怎麼被回收的

EngineJob用到了對象池,具體分析看Glide(Android)之對象池。當EngineJob中註冊的所有callback被回調完成就會回收EngineJob,EngineJob的回調分析看Glide之DecodeJob decode、transcode過程和EngineJob回調的處理。我們看看EngineJob回收的地方

  @Synthetic
  synchronized void decrementPendingCallbacks() {
    stateVerifier.throwIfRecycled();
    Preconditions.checkArgument(isDone(), "Not yet complete!");
    int decremented = pendingCallbacks.decrementAndGet();
    Preconditions.checkArgument(decremented >= 0, "Can't decrement below 0");
    if (decremented == 0) {
      if (engineResource != null) {
        engineResource.release();
      }

      release();
    }
  }

  private synchronized void release() {
    if (key == null) {
      throw new IllegalArgumentException();
    }
    cbs.clear();
    key = null;
    engineResource = null;
    resource = null;
    hasLoadFailed = false;
    isCancelled = false;
    hasResource = false;
    decodeJob.release(/*isRemovedFromQueue=*/ false);
    decodeJob = null;
    exception = null;
    dataSource = null;
    pool.release(this);
  }

看到沒?在回收EngineJob之前先回收了DecodeJob,DecodeJob也使用了對象池。

(4)一次網絡請求回來,哪些對象還活着?哪些對象被回收了?哪些對象被釋放了?

EngineJob被回收到了對象池Pools.Pool裏面

DecodeJob也被回收到了對象池Pools.Pool裏面(是在EngineJob被回收前),DecodeJob中持有的對象都被釋放了

SingleRequest是活着的,它被RequestManager中的RequestTracker維護着,SingleRequest中持有請求回來的數據。

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章