glide-源码解析-2

glide-源码解析-2

本篇接篇1代码,来分析RequestManager.into方法

一、RequestManager.into(imageView)

我们可以大体推测下,into里面最终肯定会发送一个加载资源请求,请求数据并终转换成一个Drawable对象,最后将这个对象设置我们传入的imageView当中去,这样就实现了图片的加载。接下来看下glide是如何实现的?

  /**
   * Sets the {@link ImageView} the resource will be loaded into, cancels any existing loads into
   * the view, and frees any resources Glide may have previously loaded into the view so they may be
   * reused.
   *
   * @see RequestManager#clear(Target)
   * @param view The view to cancel previous loads for and load the new resource into.
   * @return The {@link com.bumptech.glide.request.target.Target} used to wrap the given {@link
   *     ImageView}.
   */
  @NonNull
  public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
   
    // 1. 运行线程校验,因为涉及到view的渲染,必须保证在UI线程
    Util.assertMainThread();
    Preconditions.checkNotNull(view);

    BaseRequestOptions<?> requestOptions = this;
    if (!requestOptions.isTransformationSet()
        && requestOptions.isTransformationAllowed()
        && view.getScaleType() != null) {
   
      // Clone in this method so that if we use this RequestBuilder to load into a View and then
      // into a different target, we don't retain the transformation applied based on the previous
      // View's scale type.
      // 2. 获取imageview中的scaleType,并根据其值自动生成与其相匹配的requestOptions
      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.
      }
    }

    // 3. 构建imageViewTarget,requestOptions,UI线程执行器,传入into方法
    return into(
        glideContext.buildImageViewTarget(view, transcodeClass),
        /*targetListener=*/ null,
        requestOptions,
        Executors.mainThreadExecutor());
  }

这里面实现其实也是比较简单

  1. 校验工作
    • 如果是不是运行的UI线程会抛出异常,因为涉及到view的渲染操作,所以必须保证在UI线程执行
    • 校验传入的imageView是否空,view参数不能为空
  2. 生成一个与imageview.scaleType相匹配的请求配置信息
  3. 构建imageViewTarget、将2中的配置信息、UI线程调度器传入给into重载方法

1. 不得不说的Target

target翻译过来意思就是目标,在glide当中可以理解想把resource经过层层转换后,最终转换成Target需要消费的类型对象(resource),本例中是通过model(url)最终转换为Drawable对象

接下来看下buildImageViewTarget构建对象,最终调用ImageViewTargetFactory.buildTarget方法

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

参数clazz就是Target想要接受消费resouce类型,也就是Drawable.class;这里glide返回的target其实是DrawableImageViewTarget对象,它其实实现了Target接口


Target是可以接受resource加载事件以及组件生命周期事件的对象,通常调用流程如下

onLoadStarted
onResourceReady/onLoadFailed
onLoadCleared

我们有理由相信onResourceReady中方法提供了给我们想要的Drawable资源了,glide就是在这个方法中实现了将drawable设置到view当中去的;我们先看下DrawableImageViewTarget的父类ImageViewTarget,它实现了onResourceReady方法,

// ImageViewTarget.java
...
@Override
  public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
   
    if (transition == null || !transition.transition(resource, this)) {
   
      setResourceInternal(resource);
    } else {
   
      maybeUpdateAnimatable(resource);
    }
  }
private void setResourceInternal(@Nullable Z resource) {
   
    // Order matters here. Set the resource first to make sure that the Drawable has a valid and
    // non-null Callback before starting it.
    setResource(resource);
    maybeUpdateAnimatable(resource);
  }
protected abstract void setResource(@Nullable Z resource);
...

可以看到ImageViewTarget中最终调用了一个setResource抽象方法,子类DrawableImageViewTarget实现了它,

// DrawableImageViewTarget.java
@Override
  protected void setResource(@Nullable Drawable resource) {
   
    view.setImageDrawable(resource);
  }

可以清楚的看到DrawableImageViewTarget作为ImageViewTarget的子类承担了将target渲染到view上的职责,到了这里我们明白了resource(Drawable)是如何通过target渲染到mageview中去的。那问题来了,target的onLoadStarted、onResourceReady等方法到底有谁来触发的呢?

2. 谁触发了target的onXXX方法?

要回答这个问题, 我们需要回到之前的into方法中寻找答案,into方法调用了重载into方法

private <Y extends Target<TranscodeType>> Y into(
      @NonNull Y target,
      @Nullable RequestListener<TranscodeType> targetListener,
      BaseRequestOptions<?> options,
      Executor callbackExecutor) {
   
    
    // 1. 校验target, model是否设置
    Preconditions.checkNotNull(target);
    if (!isModelSet) {
   
      throw new IllegalArgumentException("You must call #load() before calling #into()");
    }

    // 2. 构建一个请求
    Request request = buildRequest(target, targetListener, options, callbackExecutor);

    // 3. 请求重复利用
    Request previous = target.getRequest();
    if (request.isEquivalentTo(previous)
        && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
   
      // If the request is completed, beginning again will ensure the result is re-delivered,
      // triggering RequestListeners and Targets. If the request is failed, beginning again will
      // restart the request, giving it another chance to complete. If the request is already
      // running, we can let it continue running without interruption.
      if (!Preconditions.checkNotNull(previous).isRunning()) {
   
        // Use the previous request rather than the new one to allow for optimizations like skipping
        // setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions
        // that are done in the individual Request.
        previous.begin();
      }
      return target;
    }
    
    // 4. 清理之前的请求
    requestManager.clear(target);
    
    // 5. 将新request和target绑定起来
    target.setRequest(request);
    
    // 6. 发起请求
    requestManager.track(target, request);

    return target;
  }

其实归来起来就三点

  1. 校验操作

    • 校验target不能为空
    • 校验model不能为空(此处为url)
  2. 旧请求的复用

    • 对于是相同请求的(请求的参数配置信息和大小是一样的视为相同请求)并且这个请求还没完成且可以内存复用
    • 如果旧请求正在运行中,直接触发旧请求的begin方法
  3. 清理不能复用的旧请求

      /**
       * Cancel any pending loads Glide may have for the target and free any resources (such as {@link
       * Bitmap}s) that may have been loaded for the target so they may be reused.
       *
       * @param target The Target to cancel loads for.
       */
      public void clear(@Nullable final Target<?> target) {
         
        if (target == null) {
         
          return;
        }
    
        untrackOrDelegate(target);
      }
      
      private void untrackOrDelegate(@NonNull Target<?> target) {
         
        // isOwnedByUs为true表示清理成功了
        boolean isOwnedByUs = untrack(target);
        
        // 如果清理不成功,需要glide继续做清理操作
        Request request = target.getRequest();
        if (!isOwnedByUs && !glide.removeFromManagers(target) && request != null) {
         
          target.setRequest(null);
          request.clear();
        }
        
        synchronized boolean untrack(@NonNull Target<?> target) {
         
            Request request = target.getRequest();
            // If the Target doesn't have a request, it's already been cleared.
            if (request == null) {
         
              return true;
            }
        
            // 此时拿到request不空,一定是旧的,需要自己动手清理
            if (requestTracker.clearAndRemove(request)) {
         
              targetTracker.untrack(target);
              target.setRequest(null);
              return true;
            } else {
         
              return false;
            }
        }
    }
    

    总结清理操作主要分如下几个步骤

    • 将旧请求从RequestTracker中请求池中移除
    • 中止旧请求,并释放所有相关资源
    • 从TargetTracker.targets列表中移除target
    • 解除target与request之间关联
  4. 发起新请求

    • target与新请求建立绑定关系

      // ViewTarget.java
        @Override
        public void setRequest(@Nullable Request request) {
             
          setTag(request);
        }
        private void setTag(@Nullable Object tag) {
             
          isTagUsedAtLeastOnce = true;
          // tagId==R.id.glide_custom_view_target_tag
          view.setTag(tagId, tag);
        }
      

      从ViewTarget类中可以看到是我们熟知的view.setTag方式来建立绑定关系,防止显示错位问题

    • 调用requestManager.track方法(这部是重点)

3. RequestManager.track

重点方式就是在track方法
下面我们来看下其实现

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

代码非常简洁
在这里插入图片描述
可以看到第一步只是将target添加到targetTracker中的一个集合中去,这样做是为了能够让所有的target感知生命周期,以方便target在不同生命周期执行不同操作

重点是第二行它是请求的真正发起者,我们先了解下RequestTracker结构

RequestTracker这个类顾名思义它是请求的追踪者,负责追踪,取消,重启进行中,完成,失败的请求,这个类管理RequestManager中所有请求,它是非线程安全,必须在主线程使用

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

可以看到RequestTracker发起请求前是将其存储到requests集合中,如果页面处在非paused情况下那就发起请求;否则停止请求,并将其存放到待请求列表中,一旦页面处以可以请求状态直接恢复请求。

接下来我们了解下Request,它的职责是用来给target加载一个resouce
Glide中的Request是个接口,它有三个子类


我们以常用的SingleRequest为例来看下begin方法,里面就有我们期待的target.onLoadStarted、target.onResourceReady等方法

@Override
  public void begin
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值