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

 


Glide

 

前言

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

关于作者

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


 

前情回顾

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

 

剧情(Glide into 运筹帷幄)

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

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

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

@NonNull
public <X> ViewTarget<ImageView, X> buildImageViewTarget(
      @NonNull ImageView imageView, @NonNull Class<X> 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 <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)");
    }
  }
}

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


BitmapTarget&DrawableTarget

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

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

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

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<TranscodeType> target,
      @Nullable RequestListener<TranscodeType> targetListener,
      RequestOptions requestOptions) {
    return buildRequestRecursive(
        target,
        targetListener,
        /*parentCoordinator=*/ null,
        transitionOptions,
        requestOptions.getPriority(),
        requestOptions.getOverrideWidth(),
        requestOptions.getOverrideHeight(),
        requestOptions);
}

private Request buildRequestRecursive(
      Target<TranscodeType> target,
      @Nullable RequestListener<TranscodeType> targetListener,
      @Nullable RequestCoordinator parentCoordinator,
      TransitionOptions<?, ? super TranscodeType> 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<TranscodeType> target,
      RequestListener<TranscodeType> targetListener,
      @Nullable RequestCoordinator parentCoordinator,
      TransitionOptions<?, ? super TranscodeType> 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<TranscodeType> target,
      RequestListener<TranscodeType> targetListener,
      RequestOptions requestOptions,
      RequestCoordinator requestCoordinator,
      TransitionOptions<?, ? super TranscodeType> 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 <R> SingleRequest<R> obtain(
      Context context,
      GlideContext glideContext,
      Object model,
      Class<R> transcodeClass,
      RequestOptions requestOptions,
      int overrideWidth,
      int overrideHeight,
      Priority priority,
      Target<R> target,
      RequestListener<R> targetListener,
      RequestListener<R> requestListener,
      RequestCoordinator requestCoordinator,
      Engine engine,
      TransitionFactory<? super R> animationFactory) {
    @SuppressWarnings("unchecked") SingleRequest<R> request =
        (SingleRequest<R>) 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那里:


我们刚才分析了这里面的几个点,建立了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<? super Z> 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:


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 大展宏图)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容