美文网首页
Glide解析四:为什么用Target而不直接用ImageVie

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

作者: jxiang112 | 来源:发表于2019-03-18 14:46 被阅读0次

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

    • 获取ImageView的大小
    • 根据ImageView的生命周期调度重新加载、取消加载图片
    • ImageView的动画效果
    • ImageView加载过程中显示的默认图片、加载失败时显示的图片、加载成功时显示的是Bitmap还是Drawable以及动效效果
      如果将这些功能都放到图片加载Request中实现,那么就违背了设计模式里的单一职责原则,而且会比较冗余而设Rquest变得复杂、混乱等不良后果。所以Glide秉承单一职责,Request只处理图片加载的逻辑;至于ImageView相关将其包装为Target,使用Target实现ImageView的逻辑。
      我们先看下Target的主要相关的类图关系:
      image
      BaseTarget
      它是一个抽象类,值定义了一个Request成员变量,用于保存与之关联的图片加载请求Request,方便在ImageView绑定到window时去加载图片或者从window卸载时取消图片加载:
    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的核心:

    • 通过ViewTreeObserver实现View的大小测量,测量到大小之后回到监听者的onSizeReady告知view的大小已经测量ok
    • 通过监听View与Window的绑定关系发起加载图片的请求或者取消加载图片
    • 设置加载中的显示图片
    • 设置加载失败时显示的图片
    • 设置加载成功时的图片、动效

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

    相关文章

      网友评论

          本文标题:Glide解析四:为什么用Target而不直接用ImageVie

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