美文网首页
Glide源码分析之获取View大小

Glide源码分析之获取View大小

作者: juexingzhe | 来源:发表于2020-07-23 14:25 被阅读0次

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

    接着会走到ImageViewTargetFactory中,在我们这里就返回DrawableImageViewTarget, 如果在构造RequestBuilder过程中调用操作asBitmap,那么这里就会返回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)方法中,最后会调用内部私有方法into(Target, RequestListener, RequestOptions), 该方法首先通过buildRequest构造一个Request,我们这里没有设置thumbnail,默认会返回SingleRequest,

    接下来如果这个target中有request在进行会先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中会释放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<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));
        }
      }
    

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

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

    相关文章

      网友评论

          本文标题:Glide源码分析之获取View大小

          本文链接:https://www.haomeiwen.com/subject/ngqhlktx.html