Android

Android Glide源码剖析系列(二)Glide如何管理图

2022-05-16  本文已影响0人  怡红快绿

Glide源码剖析系列


为什么选择Glide?

小结:支持图片格式多;Bitmap复用和主动回收;生命周期感应;优秀的缓存策略;加载速度快(Bitmap默认格式RGB565)

Glide简单使用

Glide.with(this)
        .load("https://t7.baidu.com/it/u=3779234486,1094031034&fm=193&f=GIF")
        .into(imageView);

源码分析

通过Android Glide源码解析系列(一)图片加载请求如何感知组件生命周期一文我们知道,with()方法返回的是一个RequestManager对象,说明load()方法是在RequestManager类当中的,所以我们首先要看的就是RequestManager这个类。

load()重载方法

load()方法支持多种形式的图片来源,本文以RequestManager类中的 load(String string) 方法为例进行深入分析。

  @NonNull
  @CheckResult
  @Override
  public RequestBuilder<Drawable> load(@Nullable String string) {
    return asDrawable().load(string);
  }

  @NonNull
  @CheckResult
  public RequestBuilder<Drawable> asDrawable() {
    return as(Drawable.class);
  }

  @NonNull
  @CheckResult
  public <ResourceType> RequestBuilder<ResourceType> as(
      @NonNull Class<ResourceType> resourceClass) {
    return new RequestBuilder<>(glide, this, resourceClass, context);
  }

为了使逻辑更清晰,把上面三个方法合并成一行代码来分析

RequestManager#load(String string)
//相当于
new RequestBuilder<>(glide, RequestManager.this, Drawable.class, context).load(string);

load(String string)最终被分为两步执行:

  1. Drawable.class为参数创建RequestBuilder实例
    RequestBuilder构造函数
  protected RequestBuilder(
      @NonNull Glide glide,
      RequestManager requestManager,
      Class<TranscodeType> transcodeClass,
      Context context) {
    this.glide = glide;
    this.requestManager = requestManager;
    this.transcodeClass = transcodeClass;  //重要变量赋值为Drawable.class,构建ImageViewTarget的时候使用
    this.context = context;
    this.transitionOptions = requestManager.getDefaultTransitionOptions(transcodeClass);
    this.glideContext = glide.getGlideContext();

    initRequestListeners(requestManager.getDefaultRequestListeners());    //初始化默认请求监听
    apply(requestManager.getDefaultRequestOptions());    //应用默认配置信息
  }
  1. 调用RequestBuilder#load(String string)
  public RequestBuilder<TranscodeType> load(@Nullable String string) {
    return loadGeneric(string);
  }

  @NonNull
  private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
    if (isAutoCloneEnabled()) {
      return clone().loadGeneric(model);
    }
    this.model = model;  //String变量赋值给model
    isModelSet = true;
    return selfOrThrowIfLocked();
  }

RequestBuilder重要成员变量:

RequestManager#load(String string)分析到现在,似乎都是一些准备工作,真正的图片加载还没有出现。既然“千呼万唤不出来”,那咱们就“打破砂锅查到底”,继续查看RequestBuilder#into(ImageView view)方法

#RequestBuilder.java
  public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
    Util.assertMainThread();  //判断是否在主线程执行
    Preconditions.checkNotNull(view);

    //把ImageView的scaleType写进requestOptions 
    //省略代码

    return into(
        //注释1:构建新的ImageViewTarget
        glideContext.buildImageViewTarget(view, transcodeClass),  
        /*targetListener=*/ null,
        requestOptions,  //请求选项
        Executors.mainThreadExecutor());  //主线程池,切回到主线程显示图片
  }

  private <Y extends Target<TranscodeType>> Y into(
      @NonNull Y target,
      @Nullable RequestListener<TranscodeType> targetListener,
      BaseRequestOptions<?> options,
      Executor callbackExecutor) {

    Preconditions.checkNotNull(target);
    if (!isModelSet) {
      throw new IllegalArgumentException("You must call #load() before calling #into()");
    }

    //注释2:构建新的加载请求Request 
    Request request = buildRequest(target, targetListener, options, callbackExecutor);

    Request previous = target.getRequest();  //获取目标View上已经存在的旧请求
    if (request.isEquivalentTo(previous)  //两次的请求相同 && !(跳过内存缓存 && 旧请求已完成)
        && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) { 
      if (!Preconditions.checkNotNull(previous).isRunning()) {  //如果旧请求未开始
        previous.begin();  //启动旧请求
      }
      return target;
    }
  
    //取消Glide为target准备的所有加载请求,并释放已经
    //加载的资源(例如Bitmap),以便它们可以被重用。
    requestManager.clear(target); 

    //利用View.setTag把request请求绑定到target指向的View
    target.setRequest(request); 

    //注释3
    requestManager.track(target, request);

    return target;
  }

  private boolean isSkipMemoryCacheWithCompletePreviousRequest(
      BaseRequestOptions<?> options, Request previous) {
    return !options.isMemoryCacheable() && previous.isComplete();
  }

小结:

  1. 调用方法glideContext.buildImageViewTarget(view, transcodeClass)创建新的ImageViewTarget
  2. 创建新的加载请求request,并且检查target上是否已有老请求previous:
    2.1 如果两次的请求相同 && !(跳过内存缓存 && 旧请求已完成),into()方法直接返回target
    2.2 如果满足2.1且 && 请求未开始,先启动旧请求再返回target
    2.3 如果2.1和2.2都不满足,依次执行:取消Glide为target准备的所有加载请求,并释放已经加载的资源(例如Bitmap),以便它们可以被重用;利用View.setTag把新请求绑定到target指向的View;requestManager重新管理target和request。

代码注释1分析:

  @NonNull
  public <X> ViewTarget<ImageView, X> buildImageViewTarget(
      @NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
    //transcodeClas = Drawable.class
    return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
  }

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)) {  //transcodeClas = Drawable.class
      return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
    } else {
      throw new IllegalArgumentException(
          "Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
    }
  }
}

根据transcodeClass类型创建对应的ImageViewTarget,即Drawable.class -> DrawableImageViewTarget

代码注释2分析:Request创建过程比较复杂,还要处理缩略图加载请求、加载错误图片加载请求等等,因此我们只分析主线buildRequest -> buildRequestRecursive -> buildThumbnailRequestRecursive -> obtainRequest -> SingleRequest.obtain -> new SingleRequest()。最终创建出一个SingleRequest实例

代码注释3分析:

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

主要做了两件事:

  1. target添加到targetTracker,管理target指向的View生命周期回调事件
  2. request添加到requestTracker,管理图片加载请求

targetTracker和requestTracker都是RequestManager的成员变量,分别负责管理target和request

TargetTracker源码:

/**
 * TargetTracker 职责是维护一个Target列表,统一管理所有Target的onStart()、
 * onStop()和onDestroy()方法回调事件
 */
public final class TargetTracker implements LifecycleListener {
  //维护target列表
  private final Set<Target<?>> targets =
      Collections.newSetFromMap(new WeakHashMap<Target<?>, Boolean>());

  public void track(@NonNull Target<?> target) {
    targets.add(target);
  }

  public void untrack(@NonNull Target<?> target) {
    targets.remove(target);
  }

  @Override
  public void onStart() {
    for (Target<?> target : Util.getSnapshot(targets)) {
      target.onStart();
    }
  }

  @Override
  public void onStop() {
    for (Target<?> target : Util.getSnapshot(targets)) {
      target.onStop();
    }
  }

  @Override
  public void onDestroy() {
    for (Target<?> target : Util.getSnapshot(targets)) {
      target.onDestroy();
    }
  }

  @NonNull
  public List<Target<?>> getAll() {
    return Util.getSnapshot(targets);
  }

  public void clear() {
    targets.clear();
  }
}

RequestTracker部分源码:

public class RequestTracker {
  private static final String TAG = "RequestTracker";
  // 如果Set直接持有request强引用,可能会发生内存泄漏,因此将request作为key存储在WeakHashMap里 
  //面,如果这些key不再使用,WeakHashMap会自动异常这些key,从而避免内存泄漏。
  private final Set<Request> requests =
      Collections.newSetFromMap(new WeakHashMap<Request, Boolean>());

  //直接持有未完成和等待开始的request强引用对象,防止在运行过程中被GC
  private final Set<Request> pendingRequests = new HashSet<>();

//所有请求是否已经被暂停
  private boolean isPaused;

  /** Starts tracking the given request. 把请求添加到requests和pendingRequests*/
  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);
    }
  }

  /** Stops any in progress requests. 停止正在运行的请求*/
  public void pauseRequests() {
    isPaused = true;
    for (Request request : Util.getSnapshot(requests)) {
      if (request.isRunning()) {
        // Avoid clearing parts of requests that may have completed (thumbnails) to avoid blinking
        // in the UI, while still making sure that any in progress parts of requests are immediately
        // stopped.
        request.pause();
        pendingRequests.add(request);
      }
    }
  }

  /** Starts any not yet completed or failed requests. 开启未完成或失败的请求*/
  public void resumeRequests() {
    isPaused = false;
    for (Request request : Util.getSnapshot(requests)) {
      // We don't need to check for cleared here. Any explicit clear by a user will remove the
      // Request from the tracker, so the only way we'd find a cleared request here is if we cleared
      // it. As a result it should be safe for us to resume cleared requests.
      if (!request.isComplete() && !request.isRunning()) {
        request.begin();
      }
    }
    pendingRequests.clear();
  }
  
  //省略其他方法
}

图片加载请求的管理流程END

结语

Glide管理图片加载请求的方式并不复杂:使用一个请求管理类RequestTracker 维护所有请求列表,并且提供统一操作所有请求的方法(即所有请求的开启、暂停和重启等等方法),RequestManager只要通过一个RequestTracker 对象就能轻轻松松控制图片加载请求。

下一篇文章我们将会学习Glide如何启动图片加载请求,以及图片资源是如何经历重重改造最终显示到界面上,敬请期待!

上一篇 下一篇

猜你喜欢

热点阅读