Glide解析四:为什么用Target而不直接用ImageVie

2019-03-18  本文已影响0人  jxiang112

阅读本篇文章,建议先阅读:
Glide解析一:Glide整体流程
在解析Glide的整体流程时,我们发现Glide用的是Target而不是ImageView,为什么不直接在图片加载Request中持有ImageView,然后在图片加载完成时在onResourceReady方法中将图片设置ImageView呢?
其实Glide涉及到ImageView的工作量还是顶多的:

public abstract class BaseTarget<Z> implements Target<Z> {

  private Request request;

  @Override
  public void setRequest(@Nullable Request request) {
    this.request = request;
  }

  @Override
  @Nullable
  public Request getRequest() {
    return request;
  }

  @Override
  public void onLoadCleared(@Nullable Drawable placeholder) {
    // Do nothing.
  }

  @Override
  public void onLoadStarted(@Nullable Drawable placeholder) {
    // Do nothing.
  }

  @Override
  public void onLoadFailed(@Nullable Drawable errorDrawable) {
    // Do nothing.
  }

  @Override
  public void onStart() {
    // Do nothing.
  }

  @Override
  public void onStop() {
    // Do nothing.
  }

  @Override
  public void onDestroy() {
    // Do nothing.
  }
}

ViewTarget
ViewTarget主要做两件事:
a、获取ImageView的大小
在解析Glide整体流程时,有提出Glide是怎么获取ImageView的大小的?其实作为App开发工程师来说,获取一个View的大小无非是getWidth()、layoutParam.width,而当View还没绘制时是拿不到大小的,那么此时通过Activity的onWindowFocusChanged或者ViewTreeObserver来监听View的绘制完成时期在调用getWidth就可以拿到大小了。
Glide获取的时期是不太可能通过onWindowFocusChanged的了,剩下就只剩下ViewTreeObserver了,对的Glide就是通过ViewTreeObserver来获取的。我们看下其实现:

public void getSize(@NonNull SizeReadyCallback cb) {
    //调用成员遍历sizeDeterminer的getSize()方法
    sizeDeterminer.getSize(cb);
  }

//SizeDeterminer.java ViewTarget的一个内部类
void getSize(@NonNull SizeReadyCallback cb) {
      //获取当前view的宽度
      int currentWidth = getTargetWidth();
      //获取当前view的高度
      int currentHeight = getTargetHeight();
      if (isViewStateAndSizeValid(currentWidth, currentHeight)) {
        //如果View的大小大于0
        //回调告知view的大小
        cb.onSizeReady(currentWidth, currentHeight);
        return;
      }

      if (!cbs.contains(cb)) {
        //添加大小观察者
        cbs.add(cb);
      }
      if (layoutListener == null) {
        //获取ViwTreeObserver
        ViewTreeObserver observer = view.getViewTreeObserver();
        layoutListener = new SizeDeterminerLayoutListener(this);
        //监听View的preDraw行为
        observer.addOnPreDrawListener(layoutListener);
      }
    }

//获取宽度
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);
    }
    //判断宽高是否大于0
    private boolean isViewStateAndSizeValid(int width, int height) {
      return isDimensionValid(width) && isDimensionValid(height);
    }
    //判断指定的大小是否大于0或者==Integer.MAX_VALUE
    private boolean isDimensionValid(int size) {
      return size > 0 || size == SIZE_ORIGINAL;
    }

这段代码的核心思想就是先判断当前View的大小是否大于0,如果大于0就直接回调onSizeReady告知View大小已知;否则通过ViewTreeObserver监听View的onPreDraw行为来获取View的大小并告知监听者view的大小已经测量好:

public boolean onPreDraw() {
        
        SizeDeterminer sizeDeterminer = sizeDeterminerRef.get();
        if (sizeDeterminer != null) {
          sizeDeterminer.checkCurrentDimens();
        }
        return true;
      }

void checkCurrentDimens() {
      if (cbs.isEmpty()) {
        return;
      }
      //获取宽度
      int currentWidth = getTargetWidth();
      //获取高度
      int currentHeight = getTargetHeight();
      if (!isViewStateAndSizeValid(currentWidth, currentHeight)) {
        //如果宽高小于0,表示view尚未测量好
        return;
      }
      //回调通知监听则view的大小已经测量好
      notifyCbs(currentWidth, currentHeight);
      //移除监听者
      clearCallbacksAndListener();
    }

b、监听与Window的绑定关系

public final ViewTarget<T, Z> clearOnDetach() {
    if (attachStateListener != null) {
      return this;
    }
    //创建绑定状态监听者
    attachStateListener = new OnAttachStateChangeListener() {
      @Override
      public void onViewAttachedToWindow(View v) {
        //绑定到window
        resumeMyRequest();
      }

      @Override
      public void onViewDetachedFromWindow(View v) {
        //从window解绑
        pauseMyRequest();
      }
    };
    maybeAddAttachStateListener();
    return this;
  }

//设置view绑定window状态的监听者
private void maybeAddAttachStateListener() {
    if (attachStateListener == null || isAttachStateListenerAdded) {
      return;
    }
    //添加绑定状态监听者
    view.addOnAttachStateChangeListener(attachStateListener);
    isAttachStateListenerAdded = true;
  }

@Synthetic void resumeMyRequest() {
    //绑定window时
    //获取图片加载对象request
    Request request = getRequest();
    if (request != null && request.isCleared()) {
      //开始请求加载
      request.begin();
    }
  }

  @SuppressWarnings("WeakerAccess")
  @Synthetic void pauseMyRequest() {
    //从window解绑时
    //获取图片加载对象request
    Request request = getRequest();
    if (request != null) {
      isClearedByUs = true;
      //取消图片加载
      request.clear();
      isClearedByUs = false;
    }
  }

通过监听view与window的绑定关系,进而调度图片加载发起加载请求或者取消加载请求。
ImageViewTarget
ImageViewTarget的主要工作有:
a、设置加载中的显示图片

public void onLoadStarted(@Nullable Drawable placeholder) {
    super.onLoadStarted(placeholder);
    setResourceInternal(null);
    setDrawable(placeholder);
  }

public void setDrawable(Drawable drawable) {
    view.setImageDrawable(drawable);
  }

设置加载中显示的图片很简单,先将原来的图片资源设置为空,在设置placeHolder为加载中显示的图片
b、设置加载失败的显示图片

public void onLoadFailed(@Nullable Drawable errorDrawable) {
    super.onLoadFailed(errorDrawable);
    setResourceInternal(null);
    setDrawable(errorDrawable);
  }

与加载中一样,加载失败时,先将原来的图片资源设置为空,在设置errorDrawable为加载失败显示的图片
c、图片加载成功的模板

public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
    if (transition == null || !transition.transition(resource, this)) {
      //没有动画
      //调用setResourceInternal设置加载成功的图片
      setResourceInternal(resource);
    } else {
      //有动画,调用maybeUpdateAnimatable实现动效
      maybeUpdateAnimatable(resource);
    }
  }

private void setResourceInternal(@Nullable Z resource) {
    //调用setResource设置图片资源
    setResource(resource);
   //执行动效
    maybeUpdateAnimatable(resource);
  }

protected abstract void setResource(@Nullable Z resource);

在图片加载成功回调时,如果没有动效调用setResource设置加载成功的图片资源,而setResource是抽象方法,其实现是在DrawableImageViewTarget和BitmapImageVIewTarget来实现的;如果有动效则使用maybeUpdateAnimatable实现动效的逻辑。
d、动画的实现
上面的代码分析指导动效的实现是在函数maybeUpdateAnimatable中,我们看下其代码实现:

private void maybeUpdateAnimatable(@Nullable Z resource) {
    if (resource instanceof Animatable) {
      animatable = (Animatable) resource;
      animatable.start();
    } else {
      animatable = null;
    }
  }

maybeUpdateAnimatable很简单,就是判断图片资源是否是Animatable的实现类,是的话就转换为Animatable,并调用start开始动效。
BitmapImageViewTarget
BitmapImageViewTarget就是以bitmap的形式设置图片的资源,在分析ImageViewTarget的时候就明确指出设置图片资源是在子类的setResource来实现,我们看下BitmapImageViewTarget的setResource方法:

protected void setResource(Bitmap resource) {
    view.setImageBitmap(resource);
  }

很简单就是调用setImageBitmap设置图片资源
DrawableImageViewTarget
DrawableImageViewTarget就是以bitmap的形式设置图片的资源,在分析ImageViewTarget的时候就明确指出设置图片资源是在子类的setResource来实现,我们看下DrawableImageViewTarget的setResource方法:

protected void setResource(@Nullable Drawable resource) {
    view.setImageDrawable(resource);
  }

很简单就是调用setImageDrawable设置图片资源

ok,总结下Target的核心:

综合起来Target的工作还是蛮多的,如果融入Request中那么就会导致Request更为复杂、乱,所以Glide单独Target模块来实现View的相关逻辑,体现了单一职责原则、高内聚低耦合的特性

上一篇下一篇

猜你喜欢

热点阅读