Android Glide4.0 源码遨游记(第三集)

 


Android Glide4.0 源码遨游记(第三集)_第1张图片
Glide

 

前言

Android中加载图片的形式有很多种,网上也有很多的知名图片加载库,例如Glide、Picasso、Fresco等等,它们为我们带来的方便就不需再多言了,无论是从加载到缓存还是占位图等等都提供了简易的Api,且实现强大的功能。本系列只针对Glide4.0版本源码进行分析,提高自身阅读源码的能力,同时也是为了了解其中加载的流程以及缓存的原理,本文尽可能地截图说明结合源码解析,如有疏忽之处,还请指教。

关于作者

一个在奋斗路上的Android小生,欢迎关注,互相交流
GitHub:GitHub-ZJYWidget
CSDN博客:IT_ZJYANG
简 书:Android小Y


 

前情回顾

上一集已经分析了Glide中load的作用,主要涉及到RequestManager和RequestBuilder,获取了我们的资源参数和想要加载的结果类型,并已经持有了这些参数,接下来就是Glide的核心部分——加载图片,这一集将开始分析Glide的into方法,这一部分有点多,稳住!!!

 

剧情(Glide into 运筹帷幄)

Glide中into的使用,我们一般都是传递了一个ImageView这样子的图片加载控件,然后Glide就会帮我们把之前传进去的资源给绘制到ImageView上,表面轻描淡写,底下实则深似水。代码如下:

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

先是老规矩,检查下传进来的ImageView是否为空,然后可以看到获取了一个requestOptions参数,是不是很眼熟,这就是我们在使用Glide时调用apply传进来的RequestOptions,然后会获取ImageView是否有设置了ScaleType,如果有,就优先使用其ScaleType来作为最终展示的ScaleType,然后接着调用了另一个into(Target,RequestListener,RequestOptions),这里涉及到一个新的概念——Target,那么它有什么作用呢?跟进去buildImageViewTarget

@NonNull
public  ViewTarget buildImageViewTarget(
      @NonNull ImageView imageView, @NonNull Class transcodeClass) {
    return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}

可以看到调用了ImageViewTargetFactory来生成对应的ViewTarget:

/**
 * A factory responsible for producing the correct type of
 * {@link com.bumptech.glide.request.target.Target} for a given {@link android.view.View} subclass.
 */
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)");
    }
  }
}

为何Glide要区分两种Bitmap和Drawable呢,因为ImageView加载这两个的api是不一样的,点进去可以看到它们的最明显区别:


Android Glide4.0 源码遨游记(第三集)_第2张图片
BitmapTarget&DrawableTarget

所以,我们刚才的buildImageViewTarget方法的作用就是根据传进来的transcodeClass(上一集讲Glide的load的时候有提到,这个参数代表着我们最终要以什么类型来展示),生成对应的ImageViewTarget,且可以很明显看出,这个ImageViewTarget是用来最终展示图片的,这正好对应了我们transcodeClass的作用。即通俗的讲,我们在外面调用的asBitmap、asDrawable,最终Glide会在这里根据不同的类型调用ImageView的setImageBitmap或者setImageDrawable进行展示。

解释完Target的作用,我们再回到刚才的into(Target,RequestListener,RequestOptions),跟进去看到:

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

    requestManager.clear(target);
    target.setRequest(request);
    requestManager.track(target, request);

    return target;
}

首先看到先是判断是否为主线程,毕竟Android不允许在子线程展示UI,然后判断Target是否为空,因为刚说了,Target是用来展示图片的,必须要有。接着判断isModelSet(是不是又很眼熟,这也是Glide的load方法时赋值的),只有先调用了Glide.with().load(),这个标志位才会为true,否则就说明没有告诉Glide你要展示的是什么资源,从而就抛出异常"You must call #load() before calling #into()"

Android Glide4.0 源码遨游记(第三集)_第3张图片
into#前置条件

接着往下看:

Request request = buildRequest(target, targetListener, options);

Request previous = target.getRequest();
if (request.isEquivalentTo(previous)
        && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
      request.recycle();
            if (!Preconditions.checkNotNull(previous).isRunning()) {
        previous.begin();
      }
      return target;
}

requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);

这里先是生成了一个Request对象,然后又根据Target对象拿到一个Request,接着调用了isEquivalentToisSkipMemoryCacheWithCompletePreviousRequest进行判断,我们分别看俩家伙是干嘛的:

1.isEquivalentTo
isEquivalentTo有三个具体实现,我们挑其中一个——SingleRequest进行分析:

@Override
public boolean isEquivalentTo(Request o) {
    if (o instanceof SingleRequest) {
      SingleRequest that = (SingleRequest) o;
      return overrideWidth == that.overrideWidth
          && overrideHeight == that.overrideHeight
          && Util.bothModelsNullEquivalentOrEquals(model, that.model)
          && transcodeClass.equals(that.transcodeClass)
          && requestOptions.equals(that.requestOptions)
          && priority == that.priority
          // We do not want to require that RequestListeners implement equals/hashcode, so we don't
          // compare them using equals(). We can however, at least assert that the request listener
          // is either present or not present in both requests.
          && (requestListener != null
          ? that.requestListener != null : that.requestListener == null);
    }
    return false;
}

可以看到是在对比两个Request对象的参数是否完全一致,而这些参数也都很眼熟,顾名思义,这里就不具体阐述。

2.isSkipMemoryCacheWithCompletePreviousRequest

private boolean isSkipMemoryCacheWithCompletePreviousRequest(
      RequestOptions options, Request previous) {
    return !options.isMemoryCacheable() && previous.isComplete();
}

这里的isMemoryCacheable其实就是判断当前Glide的缓存功能是否打开,也就是我们平时设置的RequestOption#skipMemoryCache 方法传进来的boolean值。

回到刚才生成Request的过程,上述两个条件一方面判断了是否相等,另一方面判断缓存开关是否打开,最终决定我们要使用缓存还是重新请求,那么我们从buiildRequest跟进去看,看下Request里面做了啥:

private Request buildRequest(
      Target target,
      @Nullable RequestListener targetListener,
      RequestOptions requestOptions) {
    return buildRequestRecursive(
        target,
        targetListener,
        /*parentCoordinator=*/ null,
        transitionOptions,
        requestOptions.getPriority(),
        requestOptions.getOverrideWidth(),
        requestOptions.getOverrideHeight(),
        requestOptions);
}

private Request buildRequestRecursive(
      Target target,
      @Nullable RequestListener targetListener,
      @Nullable RequestCoordinator parentCoordinator,
      TransitionOptions transitionOptions,
      Priority priority,
      int overrideWidth,
      int overrideHeight,
      RequestOptions requestOptions) {
    //....忽略部分代码

    Request mainRequest =
        buildThumbnailRequestRecursive(
            target,
            targetListener,
            parentCoordinator,
            transitionOptions,
            priority,
            overrideWidth,
            overrideHeight,
            requestOptions);

    if (errorRequestCoordinator == null) {
      return mainRequest;
    }
    //....忽略部分代码
}

private Request buildThumbnailRequestRecursive(
      Target target,
      RequestListener targetListener,
      @Nullable RequestCoordinator parentCoordinator,
      TransitionOptions transitionOptions,
      Priority priority,
      int overrideWidth,
      int overrideHeight,
      RequestOptions requestOptions) {
      //忽略部分代码
     
      // Recursively generate thumbnail requests.
    if(thumbnailBuilder != null){
        
    } else if (thumbSizeMultiplier != null) {
      // Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse.
    } else {
      // Base case: no thumbnail.
      return obtainRequest(
          target,
          targetListener,
          requestOptions,
          parentCoordinator,
          transitionOptions,
          priority,
          overrideWidth,
          overrideHeight);
    }
}

中间省略了一些代码,可以看到,buildRequest其实调用了buildRequestRecursive,进而调用了buildThumbnailRequestRecursive,最终会调用到obtainRequest,并且传递了很多眼熟的参数,比如说加载的宽高、监听、requestOption等等,那么传到obtainRequest里面又做了什么呢?继续往下看:

private Request obtainRequest(
      Target target,
      RequestListener targetListener,
      RequestOptions requestOptions,
      RequestCoordinator requestCoordinator,
      TransitionOptions transitionOptions,
      Priority priority,
      int overrideWidth,
      int overrideHeight) {
    return SingleRequest.obtain(
        context,
        glideContext,
        model,
        transcodeClass,
        requestOptions,
        overrideWidth,
        overrideHeight,
        priority,
        target,
        targetListener,
        requestListener,
        requestCoordinator,
        glideContext.getEngine(),
        transitionOptions.getTransitionFactory());
}

可以看到,这里又调用了SingleRequestobtain方法:

public static  SingleRequest obtain(
      Context context,
      GlideContext glideContext,
      Object model,
      Class transcodeClass,
      RequestOptions requestOptions,
      int overrideWidth,
      int overrideHeight,
      Priority priority,
      Target target,
      RequestListener targetListener,
      RequestListener requestListener,
      RequestCoordinator requestCoordinator,
      Engine engine,
      TransitionFactory animationFactory) {
    @SuppressWarnings("unchecked") SingleRequest request =
        (SingleRequest) POOL.acquire();
    if (request == null) {
      request = new SingleRequest<>();
    }
    request.init(
        context,
        glideContext,
        model,
        transcodeClass,
        requestOptions,
        overrideWidth,
        overrideHeight,
        priority,
        target,
        targetListener,
        requestListener,
        requestCoordinator,
        engine,
        animationFactory);
    return request;
}

生成了一个SingleRequest实例,并且调用SingleRequest的init方法,而init方法里面其实就是将刚才传进来的参数赋值给SingleRequest的成员变量,所以我们刚才从buildRequest走下来这么一条线路,其实最终就是生成一个带有各种加载参数的Request实例,回到一开始的buildRequest那里:

Android Glide4.0 源码遨游记(第三集)_第4张图片

我们刚才分析了这里面的几个点,建立了Request对象,还剩两个地方未分析,一个是Request的begin方法,一个是RequestManager的track,这两个才是重点,我们分别来看下里面的内容:

Request.begin()
我们同样只挑选SingleRequest来分析,看下SingleRequest里面的begin方法:

@Override
  public void begin() {
    if (model == null) {
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        width = overrideWidth;
        height = overrideHeight;
      }
      int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
      onLoadFailed(new GlideException("Received null model"), logLevel);
      return;
    }

    if (status == Status.RUNNING) {
      throw new IllegalArgumentException("Cannot restart a running request");
    }

    if (status == Status.COMPLETE) {
      onResourceReady(resource, DataSource.MEMORY_CACHE);
      return;
    }

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

可以看到,其中基本都是根据状态的判断来做一些操作,首先可以看到判断model是否为空,这里的model正是我们上一步buildRequest传进来的参数,即代表着我们要加载的资源,因此model等于空肯定是加载失败的,然后判断是否正在执行中,不重复执行,接着判断是否已经是加载完成状态,是的话调用onResourceReady,上面这个过程有Target的几个关键的函数:onResourceReady、onLoadFailed、onLoadStarted,我们追朔到 ImageViewTarget(是不是有点惊喜,之前提到ImageViewTarget是用来展示图片的)里面看它们的实现:

1.onLoadFailed

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

调用了setDrawable(errorDrawable),而setDrawable里面果然看到了熟悉的setImageDrawable,因此onLoadFailed作用其实就是在加载失败时展示我们的失败图。

2.onResourceReady
onResourceReady里面层层递进最终也是调用的ImageViewTarget的onResrouceReady:

@Override
public void onResourceReady(@NonNull Z resource, @Nullable Transition transition) {
    if (transition == null || !transition.transition(resource, this)) {
      setResourceInternal(resource);
    } else {
      maybeUpdateAnimatable(resource);
    }
}

里面其实也是加载图片资源。

3.onLoadStarted
onLoadStarted里面最终也是调用的ImageViewTarget的onLoadStarted:

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

来展示我们的占位图。
到这里虽然找到了展示图片的真正地方,但是很多小伙伴会疑惑,这是最终的展示,那那请求资源的过程去哪啦?我们回到刚才的begin:


Android Glide4.0 源码遨游记(第三集)_第5张图片
begin

刚才分析的都是图片正在加载中或者加载完成时的状态,那些状态下都是可以直接展示的,所以剩下的就是它的加载过程了:

status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
      onSizeReady(overrideWidth, overrideHeight);
    } else {
      target.getSize(this);
}

由于篇幅较长,将真正请求资源的部分抽取了出来,等下回分解。

 

片尾

本文只讲了Glide中into方法的一半,into帮我们建立好了展示图片所用的ImageViewTarget,然后通过我们设立的参数(包括RequestOptions)建立了一个Request对象,在Request里面控制了不同加载状态下所对应的结果展示。比如加载失败就显示失败图,加载中就显示正在加载的占位图。而Request就是一个调度请求与展示的中间人,真正的请求从这里面的Engine才刚刚开始。

 

下集预告(Glide into 大展宏图)

 


Android Glide4.0 源码遨游记(第三集)_第6张图片
关注Android开发小栈,更多原创精选

你可能感兴趣的:(Android Glide4.0 源码遨游记(第三集))