Glide源码解析

Glide滑行的意思,可以看出这个库的主旨就在于让图片加载变的流畅。

这里我们从使用入手研究Glide源码:

Glide.with(this).load(url).into(imageView);

拆成三部曲之后:

                RequestManager requestManager = Glide.with(getContext());
                RequestBuilder<Drawable> requestBuilder = requestManager.load("");
                requestBuilder.into(imageView);

with方法

with方法的源码:

  @NonNull
  public static RequestManager with(@NonNull Context context) {
    return getRetriever(context).get(context);
  }

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

  @NonNull
  public static RequestManager with(@NonNull FragmentActivity activity) {
    return getRetriever(activity).get(activity);
  }

  @NonNull
  public static RequestManager with(@NonNull Fragment fragment) {
    return getRetriever(fragment.getContext()).get(fragment);
  }

  public static RequestManager with(@NonNull android.app.Fragment fragment){
    return getRetriever(fragment.getActivity()).get(fragment);
  }
传入参数类型不同,作用域不同

参数view:作用域Activity/Fragment
参数fragment: 作用域fragment
参数activity: 作用域Activity
参数context:作用域application

并且,如果在子线程中调用with,那么作用域是application,跟随App的灭亡而灭亡。

//根据传入的参数,获取不同的RequestManager
    public RequestManager get(Context context) {
        //context为null则抛出异常
        if (context == null) {
            throw new IllegalArgumentException("You cannot start a load on a null Context");
        } else if (Util.isOnMainThread() && !(context instanceof Application)) {
            //当前线程是主线程并且此context并不是Application的实例,根据context的类型做不同的处理
            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方法里面,再进入supportFragmentGet方法:

  @NonNull
  private RequestManager supportFragmentGet(
      @NonNull Context context,
      @NonNull FragmentManager fm,
      @Nullable Fragment parentHint,
      boolean isParentVisible) {
    SupportRequestManagerFragment current =
        getSupportRequestManagerFragment(fm, parentHint, isParentVisible);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
      // TODO(b/27524013): Factor out this Glide.get() call.
      Glide glide = Glide.get(context);
      requestManager =
          factory.build(
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
      current.setRequestManager(requestManager);
    }
    return requestManager;
  }
with方法过程总结:

通过retriever.get(context)获取RequestManager,在get(context)方法中通过对context类型的判断做不同的处理:

  • context是Application,通过getApplicationManager(Context context) 创建并返回一个RequestManager对象,这样不会生成一个空白fragment,不会进行监听页面的生命周期。

  • context是Activity,通过fragmentGet(activity, fm)在当前activity创建并添加一个没有界面的fragment,名为SupportRequestManagerFragment,当activity发生生命周期中onstart,onStop,onDestroy,空白fragment就会感知到,然后传递给requestManager,在requestManager各个生命周期事件中去执行各个图片加载类的对应事件,从而实现图片加载与activity的生命周期相绑定,之后创建并返回一个RequestManager对象,
    最终在RequestManager中完成对Glide对象的初始化Glide.get(context)。

load方法

load方法调用的是RequestManager中的load方法

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

进入RequestBuilder

  private Object model;
  private boolean isModelSet;

  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类型的URL存入了内部的model成员变量中,再将数据来源是否已经设置的标志位 isModelSet 设置为true,意味着我们在调用 Glide.with(context).load(url) 之后数据来源已经设置成功了。

into()

into方法调用的是RequestBuilder中的into方法

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

方法的核心是最后一行: into(glideContext.buildImageViewTarget(view, transcodeClass)) ,首先是通过 glideContext.buildImageViewTarget(view, transcodeClass) 创建出一个
ViewTarget 类型的对象,然后把这个target传入GenericRequestBuilder中的into方法中。

buildImageViewTarget方法

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

这个方法的目的是把我们传入的imageView包装成一个Target。

  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 (!Preconditions.checkNotNull(previous).isRunning()) {
        previous.begin();
      }
      return target;
    }

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

    return target;
  }

过程分析:获取当前target中的Request对象,如果存在,则清空并终止这个Request对象的执行;创建新的Request对象并与当前target绑定;执行新创建的图片处理请求Request。

然后在RequestTracker中调用runRequest方法,这个类主要负责Request的执行,暂停,取消等等关于图片请求的操作。

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

进入SingleRequest类的begin方法:

  @Override
  public void begin() {
    assertNotCallingCallbacks();
    stateVerifier.throwIfRecycled();
    startTime = LogTime.getLogTime();
    if (model == null) {
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        width = overrideWidth;
        height = overrideHeight;
      }
      // Only log at more verbose log levels if the user has set a fallback drawable, because
      // fallback Drawables indicate the user expects null models occasionally.
      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());
    }
    if (IS_VERBOSE_LOGGABLE) {
      logV("finished run method in " + LogTime.getElapsedMillis(startTime));
    }
  }

begin的实际实现在SingleRequest中,方法的逻辑大致是这样的:

获取图片的长宽尺寸,如果长宽已经确定,走 onSizeReady(overrideWidth, overrideHeight) 流程;如果未确定,先获取长宽,再走 onSizeReady(overrideWidth, overrideHeight)
图片开始加载,首先显示占位图。

  @Override
  public void onSizeReady(int width, int height) {
       ...
  }

核心代码最终调用了Engine类中的engine.load()方法。

  public <R> LoadStatus load(
       ...
  }

load方法内部会从内存、本地、网络三个来源获取图片数据,使用的LruCache原理。获取图片资源后,通过onResourceReady方法回调。

  private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
    // We must call isFirstReadyResource before setting status.
    boolean isFirstResource = isFirstReadyResource();
    status = Status.COMPLETE;
    this.resource = resource;

    if (glideContext.getLogLevel() <= Log.DEBUG) {
      Log.d(GLIDE_TAG, "Finished loading " + result.getClass().getSimpleName() + " from "
          + dataSource + " for " + model + " with size [" + width + "x" + height + "] in "
          + LogTime.getElapsedMillis(startTime) + " ms");
    }

    isCallingCallbacks = true;
    try {
      if ((requestListener == null
          || !requestListener.onResourceReady(result, model, target, dataSource, isFirstResource))
          && (targetListener == null
          || !targetListener.onResourceReady(result, model, target, dataSource, isFirstResource))) {
        Transition<? super R> animation =
            animationFactory.build(dataSource, isFirstResource);
        target.onResourceReady(result, animation);
      }
    } finally {
      isCallingCallbacks = false;
    }

    notifyLoadSuccess();
  }

核心代码: target.onResourceReady(result, animation),其实在这句代码的内部最终是通过:

public class DrawableImageViewTarget extends ImageViewTarget<Drawable> {
    public DrawableImageViewTarget(ImageView view) {
        super(view);
    }

    @Override
    protected void setResource(Drawable resource) {
       view.setImageDrawable(resource);
    }
}

本质是通过 setResource(Drawable resource) 来实现的,在这个方法的内部调用了Android内部最常用的加载图片的方法 view.setImageDrawable(resource) 。

into总结

1.将imageview包装成imageViewTarget。
2.清除这个imageViewTarget之前绑定的请求,绑定新的请求
执行新的请求。
3.获取图片数据之后,成功则会调用ImageViewTarget中的onResourceReady()方法,失败则会调用ImageViewTarget中的onLoadFailed();二者的本质都是通过调用Android中的imageView.setImageDrawable(drawable)来实现对imageView的图片加载。

glide流程简化过程总结:

参考文章:
https://segmentfault.com/a/1190000014853308
Glide缓存机制

glide面试题总结

  • Glide加载一个100x100的图片,是否会压缩后再加载?放到一个300x300的view上会怎样?

当我们调整ImageView大小事,Glide会为每个不同尺寸的ImageView缓存一张图片,也就是说不管你的这张图片有没有被加载过,只要ImageView的尺寸不一样,那么GLide就会重新加载一次,这时候,他会在加载ImageView之前从网络上重新下载,然后再缓存。
举个例子,如果一个页面的ImageView是300 * 300像素,而另一个页面中的ImageView是100 * 100像素,这时候想要让两个ImageView是同一张图片,那么Glide需要下载两次图片,并且缓存两张图片。

public <R> LoadStatus load() {
    EngineKey key = keyFactory.buildKey(model, signature, 
width, height, resourceClass, transcodeClass, options);
}

缓存key生成条件之一就是控件的宽高,宽高不同的的图片会成为不同的缓存。

  • 简单说一下内存泄漏的场景,如果在一个页面中使用Glide加载了一张图片,图片正在获取中,如果突然关闭页面,这个页面会造成内存泄漏吗?

Glide在加载资源的时候,如果是在Activity,Fragment这一类有生命周期的组件上进行的话,会创建一个透明的RequestManagerFragment加入到FragmentManager之中,感知生命周期,当Activity, Fragment等组件进入不可见,或者已经销毁的时候,Glide会停止加载资源。但是如果是在非生命周期的组件上进行时,会采用Application的生命周期贯穿整个应用,所以applicationManager只有在应用程序关闭时终止加载。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。