Glide源碼分析之獲取View大小

Glide可以智能的根據(jù)View的大小來合適的設(shè)置圖片需要顯示的大小,這樣可以有效的減小內(nèi)存使用。那么要優(yōu)化需要顯示的圖片大小,前提條件肯定是知道target(view)的大小,這樣才能進(jìn)行合適的裁剪。今天這篇文章主要來分析下Glide怎么動(dòng)態(tài)測(cè)量view的大小(本文分析源碼來自Glide-4.8.0版本)。

一般使用Glide方式如下:

Glide.with(this).load(URL).into(imageview)

如果要獲取view的大小可以給Target設(shè)置一個(gè)回調(diào),Glide會(huì)把計(jì)算得到的width和height返回:

Glide.with(this).load(URL).into(imageview).getSize(new SizeReadyCallback() {
            @Override
            public void onSizeReady(int width, int height) {
                Log.i(TAG, "width = " + width + ", height = " + height);
            }
        });

首先跟到into中看下源碼,會(huì)調(diào)用buildImageViewTarget來構(gòu)造Target:

// RequestBuilder.java
  public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
    Util.assertMainThread();
    Preconditions.checkNotNull(view);

    RequestOptions requestOptions = this.requestOptions;
    if (!requestOptions.isTransformationSet()
        && requestOptions.isTransformationAllowed()
        && view.getScaleType() != null) {
      switch (view.getScaleType()) {
        case CENTER_CROP:
          requestOptions = requestOptions.clone().optionalCenterCrop();
          break;
        case CENTER_INSIDE:
          requestOptions = requestOptions.clone().optionalCenterInside();
          break;
        case FIT_CENTER:
        case FIT_START:
        case FIT_END:
          requestOptions = requestOptions.clone().optionalFitCenter();
          break;
        case FIT_XY:
          requestOptions = requestOptions.clone().optionalCenterInside();
          break;
        case CENTER:
        case MATRIX:
        default:
          // Do nothing.
      }
    }

    return into(
        glideContext.buildImageViewTarget(view, transcodeClass),
        /*targetListener=*/ null,
        requestOptions);
  }

// GlideContext
  @NonNull
  public <X> ViewTarget<ImageView, X> buildImageViewTarget(
      @NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
    return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
  }

接著會(huì)走到ImageViewTargetFactory中,在我們這里就返回DrawableImageViewTarget, 如果在構(gòu)造RequestBuilder過程中調(diào)用操作asBitmap,那么這里就會(huì)返回BitmapImageViewTarget

// ImageViewTargetFactory
public class ImageViewTargetFactory {
  @NonNull
  @SuppressWarnings("unchecked")
  public <Z> ViewTarget<ImageView, Z> buildTarget(@NonNull ImageView view,
      @NonNull Class<Z> clazz) {
    if (Bitmap.class.equals(clazz)) {
      return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
    } else if (Drawable.class.isAssignableFrom(clazz)) {
      return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
    } else {
      throw new IllegalArgumentException(
          "Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
    }
  }
}

再返回到上面的into(ImageView)方法中,最后會(huì)調(diào)用內(nèi)部私有方法into(Target, RequestListener, RequestOptions), 該方法首先通過buildRequest構(gòu)造一個(gè)Request,我們這里沒有設(shè)置thumbnail,默認(rèn)會(huì)返回SingleRequest,

接下來如果這個(gè)target中有request在進(jìn)行會(huì)先clear掉,然后再加載本次的Request:

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

    options = options.autoClone();
    Request request = buildRequest(target, targetListener, options);

    Request previous = target.getRequest();
    if (request.isEquivalentTo(previous)
        && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
      request.recycle();
        previous.begin();
      }
      return target;
    }

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

    return target;
  }

其中主要的邏輯是下面的兩行代碼:

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

在clear中會(huì)釋放Resource,然后回調(diào)onLoadCleared:

  public void clear() {
    Util.assertMainThread();
    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;
  }

接著看下面的requestManager.track(target, request):

第一行代碼很簡(jiǎn)單,就是把這次的target加入到Glide的Targets管理集合中

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

真正的邏輯在runRequest中,調(diào)用Requestbegin開始工作。

// RequestTracker.java
  /**
   * Starts tracking the given request.
   */
  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);
    }
  }

那么饒了一大圈還只是做一些準(zhǔn)備工作,還沒開始真正開始測(cè)量,前面說到這里的requestSingleRequest,接著往下看:

// SingleRequest.java
  public void begin() {
    ...
    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.

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

首先,如果我們?cè)贕lide加載的時(shí)候通過override給了固定的寬高,那么這里就會(huì)立馬回調(diào)onSizeReady函數(shù),這個(gè)函數(shù)代碼我們后面一起看,這里先接著往下看。在我們這里會(huì)走到else邏輯中,調(diào)用getSize函數(shù),這個(gè)主流程后面再分析,先把begin函數(shù)看完。如果沒有給定寬高,那么往下走就會(huì)回調(diào)targetonLoadStarted接口,在這里可以顯示占位符。到這里into的邏輯就走完了,那加載工作在哪里開始?沒錯(cuò),就是在上面留下的target.getSize函數(shù)中。

前面知道這里的TargetDrawableImageViewTarget,該方法在它的父類ViewTarget中,

  • 該方法首先調(diào)用View.getWidth()/View.getHeight(),如果其中一個(gè)或者兩個(gè)為0,
  • 那么接著檢查View's LayoutParams,
  • 如果有其中一個(gè)或者兩個(gè)<=0,
  • 那么就會(huì)添加一個(gè)OnPreDrawListener接口,在回調(diào)接口SizeDeterminerLayoutListener中就再調(diào)用checkCurrentDimens方法重復(fù)一遍獲取view的寬高,如果有效就回調(diào)SizeReadyCallback
// DrawableImageViewTarget.java
  @CallSuper
  @Override
  public void getSize(@NonNull SizeReadyCallback cb) {
    sizeDeterminer.getSize(cb);
  }

static final class SizeDeterminer {
            void getSize(@NonNull SizeReadyCallback cb) {
      int currentWidth = getTargetWidth();
      int currentHeight = getTargetHeight();
      if (isViewStateAndSizeValid(currentWidth, currentHeight)) {
        cb.onSizeReady(currentWidth, currentHeight);
        return;
      }
      if (!cbs.contains(cb)) {
        cbs.add(cb);
      }
      if (layoutListener == null) {
        ViewTreeObserver observer = view.getViewTreeObserver();
        layoutListener = new SizeDeterminerLayoutListener(this);
        observer.addOnPreDrawListener(layoutListener);
      }
    } 
  
      void checkCurrentDimens() {
      if (cbs.isEmpty()) {
        return;
      }

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

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

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

SingleRequest中知道這里SizeReadyCallback就是SingleRequest

// SingleRequest.java
  @Override
  public void onSizeReady(int width, int height) {
    stateVerifier.throwIfRecycled();
    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);

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

到這里就調(diào)用Engine.load方法開始漫長(zhǎng)的加載流程,這里可以參考我之前的Glide緩存流程.

所以Glide在真正加載之前會(huì)先去確定View的尺寸,如果沒有通過override方法設(shè)置view的固定尺寸,那么會(huì)分別通過getWidth()/getHeight()和View's LayoutParams方法獲取尺寸,如果這兩個(gè)方法還不能獲取有效尺寸,就會(huì)通過OnPreDrawListener`添加回調(diào)接口來獲取尺寸。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容