Glide源码分析之获取View大小

Glide可以智能的根据View的大小来合适的设置图片需要显示的大小,这样可以有效的减小内存使用。那么要优化需要显示的图片大小,前提条件肯定是知道target(view)的大小,这样才能进行合适的裁剪。今天这篇文章主要来分析下Glide怎么动态测量view的大小(本文分析源码来自Glide-4.8.0版本)。

一般使用Glide方式如下:

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

如果要获取view的大小可以给Target设置一个回调,Glide会把计算得到的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中看下源码,会调用buildImageViewTarget来构造Target:

// RequestBuilder.java
  public ViewTarget 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  ViewTarget buildImageViewTarget(
      @NonNull ImageView imageView, @NonNull Class transcodeClass) {
    return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
  }

接着会走到ImageViewTargetFactory中,在我们这里就返回DrawableImageViewTarget, 如果在构造RequestBuilder过程中调用操作asBitmap,那么这里就会返回BitmapImageViewTarget

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

再返回到上面的into(ImageView)方法中,最后会调用内部私有方法into(Target, RequestListener, RequestOptions), 该方法首先通过buildRequest构造一个Request,我们这里没有设置thumbnail,默认会返回SingleRequest,

接下来如果这个target中有request在进行会先clear掉,然后再加载本次的Request:

  private > Y into(
      @NonNull Y target,
      @Nullable RequestListener 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中会释放Resource,然后回调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):

第一行代码很简单,就是把这次的target加入到Glide的Targets管理集合中

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

真正的逻辑在runRequest中,调用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);
    }
  }

那么饶了一大圈还只是做一些准备工作,还没开始真正开始测量,前面说到这里的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));
    }
  }

首先,如果我们在Glide加载的时候通过override给了固定的宽高,那么这里就会立马回调onSizeReady函数,这个函数代码我们后面一起看,这里先接着往下看。在我们这里会走到else逻辑中,调用getSize函数,这个主流程后面再分析,先把begin函数看完。如果没有给定宽高,那么往下走就会回调targetonLoadStarted接口,在这里可以显示占位符。到这里into的逻辑就走完了,那加载工作在哪里开始?没错,就是在上面留下的target.getSize函数中。

前面知道这里的TargetDrawableImageViewTarget,该方法在它的父类ViewTarget中,

  • 该方法首先调用View.getWidth()/View.getHeight(),如果其中一个或者两个为0,
  • 那么接着检查View's LayoutParams,
  • 如果有其中一个或者两个<=0,
  • 那么就会添加一个OnPreDrawListener接口,在回调接口SizeDeterminerLayoutListener中就再调用checkCurrentDimens方法重复一遍获取view的宽高,如果有效就回调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 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));
    }
  }

到这里就调用Engine.load方法开始漫长的加载流程,这里可以参考我之前的Glide缓存流程.

所以Glide在真正加载之前会先去确定View的尺寸,如果没有通过override方法设置view的固定尺寸,那么会分别通过getWidth()/getHeight()和View's LayoutParams方法获取尺寸,如果这两个方法还不能获取有效尺寸,就会通过OnPreDrawListener`添加回调接口来获取尺寸。

你可能感兴趣的:(Glide源码分析之获取View大小)