Glide原理解析(一):加载流程分析详解手机开发


Glide 是目前非常流行的图片加载第三方开源库,而且功能极其强大,内部代码复杂程度也极其大。经过一番心理斗争决定还是要好好学习Glide的部分源码设计。

因为Glide非常的强大且复杂,所以我挑选了自己感兴趣的几个点学习研究:

  1. Glide 加载图片的整体流程
  2. Glide 的缓存池
  3. Glide 怎么判断图片是有效
  4. Glide 怎么计算ImageView宽高?
  5. Glide 在listview中如何加载图片的

Glide加载图片的整体流程

Glide 使用很简单:

Glide.with(Context).load(url).into(View)

所以,分成三个流程进行分析:

1. with

  // Glide.java 
  public static RequestManager with(@NonNull Activity activity) {
    
    return getRetriever(activity).get(activity); 
  } 
 

with()方法有很多参数,这里只看Activity作为参数的情况。

  private static RequestManagerRetriever getRetriever(@Nullable Context context) {
    
    return Glide.get(context).getRequestManagerRetriever(); 
  } 

RequestManagerRetriever 是检索者的意思,负责创建RequestManager,或者从Activity或Fragment找出RequestManager

Glide是一个单例,在创建的时候生成很多配置对象,比如RequestManagerRetriever 、缓存对象等。

RequestManager 顾名思义,就是负责管理Request 请求,而且会跟着Fragment或Activity的生命周期

看一下 RequestManager 是怎么生成的

  // RequestManagerRetriever 
  @NonNull 
  public RequestManager get(@NonNull Context context) {
    
    if (context == null) {
    
      throw new IllegalArgumentException("You cannot start a load on a null Context"); 
    } else if (Util.isOnMainThread() && !(context instanceof Application)) {
    
      if (context instanceof FragmentActivity) {
    
        return get((FragmentActivity) context); 
      } else if (context instanceof Activity) {
    
        return get((Activity) context); 
      } else if (context instanceof ContextWrapper) {
    
        return get(((ContextWrapper) context).getBaseContext()); 
      } 
    } 
    return getApplicationManager(context); 
  } 

get()方法根据context是不同的实例,根据Context的类型是Application和非Application来获取不同的RequestManager;

这里我们只看context是Activity的情况

// RequestManagerRetriever 
  public RequestManager get(@NonNull Activity activity) {
    
    if (Util.isOnBackgroundThread()) {
    // 如果非UI线程,走Application的ReqeustManager 
      return get(activity.getApplicationContext()); 
    } else {
    
      android.app.FragmentManager fm = activity.getFragmentManager(); 
      // 获取FragmentManager 后续可以创建存储Fragment 
      return fragmentGet( 
          activity, fm, /*parentHint=*/ null, isActivityVisible(activity)); 
    } 
  } 
   
  private RequestManager fragmentGet(@NonNull Context context, 
      @NonNull android.app.FragmentManager fm, 
      @Nullable android.app.Fragment parentHint, 
      boolean isParentVisible) {
    
	 
	// 内部创建一个Fragment,构造RequestManager 
    RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible); 
    RequestManager requestManager = current.getRequestManager(); 
    if (requestManager == null) {
    
      Glide glide = Glide.get(context); 
      requestManager = 
          factory.build( 
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context); 
      current.setRequestManager(requestManager); 
    } 
    return requestManager; 
  } 

简述一下上面的RequestManager流程,主要目的是获取对应的FragmentManager,创建一个透明的Fragment;

getRequestManagerFragment()负责获取或者创建 SupportRequestManagerFragment(其实就是Fragment),不继续深究了,以流程为主;

这样 RequestManager自然就有了生命周期了的通知。

小结:

with() 目的是创建一个RequestManager

2.load()

RequestManager的 load() 方法返回的是一个ReqeustBuilder,分明是一个建造者模式,ReqeustBuilder负责创建Request;

RequestManager的大部分方法就是负责链式配置ReqeustBuilder的各种属性。

我们看一个最常用的load(),参数是一个String类型的URL

// RequestManager 
  public RequestBuilder<Drawable> load(@Nullable String string) {
    
    return asDrawable().load(string); 
  } 
 
  public RequestBuilder<Drawable> asDrawable() {
    
    return as(Drawable.class); 
  } 
 
 public <ResourceType> RequestBuilder<ResourceType> as( 
      @NonNull Class<ResourceType> resourceClass) {
    
    return new RequestBuilder<>(glide, this, resourceClass, context); 
  } 

看起来as()方法就是构造一个RequestBuilder,其中resourceClass表示资源类型,可以是Drawable、Bitmap、还有GifDrawable;

看完了asDrawable()构建好了RequestBuilder,最后看下ReqeustBuilder的load():

public RequestBuilder<TranscodeType> load(@Nullable String string) {
    
    return loadGeneric(string); 
} 
 
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
    
    this.model = model; 
    isModelSet = true; 
    return this; 
} 

load(String )方法也是很简单的,大致上就是配置一下model 模型参数,表示我要加载的是什么model 模型参数,可以是uri、bitmap等等。

isModelSet是标记位,表示已设定好模型参数。

小结:

load() 目的是创建一个RequestBuilder,并配置各类参数

3. into()

以上构建好了RequestBuilder,来到最后一步RequestBuilder的into()方法,这是最重要也是最复杂的一步。

忍着头皮继续往下看吧。

这里只挑选into(ImageView)的方法

public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
    
    // 判断是否主线程 
    Util.assertMainThread(); 
    Preconditions.checkNotNull(view); 
	// 使用默认的 RequestOptions 配置 
    RequestOptions requestOptions = this.requestOptions; 
    // 判断是否需要自定义 transform 转换裁剪 
    if (!requestOptions.isTransformationSet() 
        && requestOptions.isTransformationAllowed() 
        && view.getScaleType() != null) {
    
      // 根据scale type设置裁剪策略 
      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); 
} 

上面出现了一个RequestOptions,它保存着所有requestManager的配置,包括缓存策略、裁剪大小、动画效果、优先级等等。在用于创建reqeust的时候,配置reqeust的属性。

into(ImageView view) 根据scale type设定了一个对应的裁剪策略。

接着调用了glideContext.buildImageViewTarget(view, transcodeClass)创建一个ViewTarget。

最后重载的方法into()

我们先说下ViewTarget,它集成自BaseTarget,相当于是Glide处理完结果的回调,负责处理最后的结果,并且带有生命周期。

Glide提供了各种各样的Target:
在这里插入图片描述
ImageViewTargetFactory 负责创建ViewTarget:

public class ImageViewTargetFactory {
    
  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)"); 
    } 
  } 
} 

假定我们传的是Bitmap,返回的是一个BitmapImageViewTarget,我们在最后拿到结果的时候再回来分析BitmapImageViewTarget是如何处理的。

创建完了BitmapImageViewTarget,往下看重载的方法into(),基本是此次分析的重点流程:

  // RequestBuilder.java 
  private <Y extends Target<TranscodeType>> Y into( 
      @NonNull Y target, 
      @Nullable RequestListener<TranscodeType> targetListener, 
      @NonNull RequestOptions options) {
    
    Util.assertMainThread(); 
    Preconditions.checkNotNull(target); 
    if (!isModelSet) {
    // model在RequestBuilder的load()设置过了 
      throw new IllegalArgumentException("You must call #load() before calling #into()"); 
    } 
 
    options = options.autoClone(); 
    // 1. 创建一个Request 
    Request request = buildRequest(target, targetListener, options); 
    // 2. 获取target之前的request 
    Request previous = target.getRequest(); 
     
    // 3. 判断是否要开始这一次的请求 
    if (request.isEquivalentTo(previous) 
        && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
    
      // 取消请求 
      request.recycle(); 
      if (!Preconditions.checkNotNull(previous).isRunning()) {
    
        // 开始之前的请求 
        previous.begin(); 
      } 
      return target; 
    } 
    // 4.先清除本次的target,再track开始此次请求 
    requestManager.clear(target); 
    target.setRequest(request); 
    // 启动、追踪 
    requestManager.track(target, request); 
 
    return target; 
  } 

1. buildRequest() 会创建一个Request请求,内部会根据情况创建 三种请求:

  1. ErrorRequestCoordinator 错误图片+目标图片联合请求(请求出错时,如有配置,则启动该请求)

  2. ThumbnailRequestCoordinator 缩略图+目标图片联合请求(小图请求较快显示。如有配置,在请求开始时启动)

  3. SingleRequest 目标图片请求(目标图片请求,在请求开始启动)

    我们假定返回的SingleRequest请求

2. target.getRequest() 获取Target之前的请求

比如一个ImageView之前可能有一个Request正在请求或者执行完成。

这里我们看ViewTarget.getRequest()方法,个人觉得很有意思。

  // ViewTarget.java 
  public Request getRequest() {
    
    Object tag = getTag(); 
    Request request = null; 
    if (tag != null) {
    
      if (tag instanceof Request) {
    
        request = (Request) tag; 
      } else {
    
        throw new IllegalArgumentException( 
            "You must not call setTag() on a view Glide is targeting"); 
      } 
    } 
    return request; 
  } 
   
  public void setRequest(@Nullable Request request) {
    
    setTag(request); 
  } 

哈哈,原来Glide每次将Request都放进了View.setTag(Request)中,这样完成结果或者需要Request就可以从Tag中取出,好机智。

3. 判断此次请求是否还要继续

request.isEquivalentTo(previous)
用来判断是否是同样的请求

isSkipMemoryCacheWithCompletePreviousRequest(options, previous))
表示之前的请求已经完成并且这次的请求不缓存

4. requestManager.track(target, request) 开始此次真正的请求

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

TargetTracker内部收录着所有的target,负责在收取到生命周期的时候通知所有的target,这里相当于target注册了成为观察者。

RequestTracker管理控制所有的request,负责启动、停止、管理所有的request。

// RequestTracker 
  public void runRequest(@NonNull Request request) {
    
    requests.add(request); 
    if (!isPaused) {
    
      request.begin(); 
    } else {
    
      request.clear(); 
      pendingRequests.add(request); 
    } 
  } 

上面假定了Request 是SingleRequest,直接看SingleRequest的begin()启动方法

  public void begin() {
    
    assertNotCallingCallbacks(); 
    stateVerifier.throwIfRecycled(); 
    startTime = LogTime.getLogTime(); 
    if (model == null) {
    // model是在load()方法设定的,前面假定是String url 
      // overrideWidth, overrideHeight是override()外部设定的裁剪的大小 
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
    
        width = overrideWidth; 
        height = overrideHeight; 
      } 
      int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG; 
      // sh 
      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)) {
    
      // 已经拿到了size,可以开始异步请求 
      onSizeReady(overrideWidth, overrideHeight); 
    } else {
    
      // 从Target中获取目标的size,至于怎么保证拿到看后续问题,最后会走到onSizeReady() 
      target.getSize(this); 
    } 
 
    if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE) 
        && canNotifyStatusChanged()) {
    
        // onSizeReady是异步的,所以会调用target.onLoadedStarted(),显示占位符 
      target.onLoadStarted(getPlaceholderDrawable()); 
    } 
  } 

上面的注释已经把流程标注得很清晰,最后看一个onSizeReady()方法

//SingleReqeust 
public void onSizeReady(int width, int height) {
    
    stateVerifier.throwIfRecycled(); // 判断是否已经被释放 
    if (status != Status.WAITING_FOR_SIZE) {
    
        return; 
    } 
    status = Status.RUNNING; 
 
    float sizeMultiplier = requestOptions.getSizeMultiplier(); // 图像的尺寸系数(0 - 1)之间,用在缩略图 
    this.width = maybeApplySizeMultiplier(width, sizeMultiplier); // 根据尺寸系数算高度和宽度 
    this.height = maybeApplySizeMultiplier(height, sizeMultiplier); 
 
    loadStatus = engine.load( // 调用Engine加载 
        glideContext, 
        model, 
        requestOptions.getSignature(), 
        this.width, 
        this.height, 
        requestOptions.getResourceClass(), 
        transcodeClass, 
        priority, 
        requestOptions.getDiskCacheStrategy(), 
        requestOptions.getTransformations(), 
        requestOptions.isTransformationRequired(), 
        requestOptions.isScaleOnlyOrNoTransform(), 
        requestOptions.getOptions(), 
        requestOptions.isMemoryCacheable(), 
        requestOptions.getUseUnlimitedSourceGeneratorsPool(), 
        requestOptions.getUseAnimationPool(), 
        requestOptions.getOnlyRetrieveFromCache(), 
        this); 
 
    if (status != Status.RUNNING) {
    
        loadStatus = null; 
    } 
} 

Engine内部包含了内存缓存和磁盘缓存,和怎么加载资源,这个在后续的缓存池模块继续分析;

最后会到Target的onResourceReady()成功回调

上面我们假定了Traget是 BitmapImageViewTarget,它继承自ImageViewTraget

//ImageViewTraget 
  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) {
    
    setResource(resource); // setResource是抽象方法 
    maybeUpdateAnimatable(resource); 
  } 
 
// BitmapImageViewTarget 
  protected void setResource(Bitmap resource) {
    
    // View 就是最初的into中的ImageView 
    view.setImageBitmap(resource); 
  } 

小结

into() 方法复杂无比,包括生命周期管理、缓存策略、裁剪、动画效果等等。

Glide.with(Conext).load(url).into(Image)

以上,基本分析完成了Glide最简单的链式调用的流程,这次尽量的省略大片大片的源码贴出,仅对整体流程加以把控。

后续补上流程图,并遗留下几个问题还没解答。

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/app/6285.html

(0)
上一篇 2021年7月17日 00:45
下一篇 2021年7月17日 00:45

相关推荐

发表回复

登录后才能评论