Glide加载图片基本流程分析

2,253 阅读7分钟

前言

本文的源码分析基于Glide版本4.9.0

Glide是一个非常强大的图片加载框架,可以从网络、硬盘等等中加载显示图片,还具有非常多可以配置的接口。而且实现起来也非常的简单,通常如果我们需要从网络加载一张图片显示到ImageView中,只需要一行代码,

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

但殊不知Glide框架在背后为我们处理了成吨的工作,今天我们就进入上面一行代码中,分析一下Glide加载图片的主要流程。

源码

with

with是Glide类中的一组静态方法,它拥有很多个重载方法,

就拿我们最常见的,在Activity中使用Glide会调用这个重载方法,

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

阅读源码发现,with的这些重载方法都是调用Glide单例获取到RequestManagerRetriever对象,

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

RequestManagerRetriever对象是在Glide单例初始化的时候通过GlideBuilder实例化的,这一段就不贴源码了。

RequestManagerRetriever对象是一个为了创建RequestManager们,或者是获取已经存在的Activities和Fragment实例的一个静态方法集合。

这里需要注意的地方,如果是在子线程调用Glide和主线程是有区别的,主要在于生命周期的不同,

  1. 如果是子线程调用Glide,返回的RequestManager的生命周期是整个应用的生命周期,而且这个RequestManager只会存在一个。
  2. 如果是主线程,会创建一个空UI的Fragment来与其生命周期绑定,其目的也很明确,为了避免Activity关闭时Glide还在执行。
public RequestManager get(@NonNull Activity activity) {
    if (Util.isOnBackgroundThread()) {
        return get(activity.getApplicationContext());
    } else {
        ...
        android.app.FragmentManager fm = activity.getFragmentManager();
        return fragmentGet(activity, fm, null, isActivityVisible(activity));
    }
}

private RequestManager fragmentGet(@NonNull Context context,
  @NonNull android.app.FragmentManager fm,
  @Nullable android.app.Fragment parentHint,
  boolean isParentVisible) {
    //RequestManagerFragment是一个空UI的Fragment
    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;
}

综上,with方法最终返回了一个RequestManager对象,正常情况下该对象拥有和当前Activity同样的生命周期

load

根据上面的流程,with方法最终返回了一个RequestManager对象,所以load实际上调用的是RequestManager对象中的load方法。其实我们根据平常的使用可以推测出来,load也应该有很多个重载方法,如下,

因为我们的例子是通过网络加载图片,这里就拿load(Uri)这个方法分析一下,

public RequestBuilder<Drawable> load(@Nullable Uri uri) {
    //asDrawable()创建了一个RequestBuilder对象
    //其内部的transcodeClass为Drawable.class后面会提到
    return asDrawable().load(uri);
}

into

@NonNull
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
    ...
    return into(
    	//构建了一个ViewTarget, ViewTarget是Glide中很重要的一个概念
    	//刚才提到的transcodeClass,本例子中传入的是Drawable.class
    	glideContext.buildImageViewTarget(view, transcodeClass),
    	/*targetListener=*/ null,
    	//一些可配置的参数
    	requestOptions,
    	Executors.mainThreadExecutor());
}

@NonNull
public <X> ViewTarget<ImageView, X> buildImageViewTarget(
	@NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
	//实际调用的是底下的方法
	return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}

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)) {
    	//本例子中传入的是Drawable.class, 可见最终返回的是一个DrawableImageViewTarget
    	//源码中封装了ImageView.setImageDrawable()方法
    	return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
    } else {
    	throw new IllegalArgumentException(
    			"Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
    }
}

into(ImageView)方法中的参数target是Glide框架中一个很重要的概念,它代表一个可被 Glide 加载并且具有生命周期的资源。从上面的源码可以看出,当我们调用RequestBuilder.into 方法时会根据传入的transcodeClass创建对应类型的target实现类。

Target中有两点值得一提,

  1. DrawableImageViewTarget的父类ImageViewTarget中onLoadStartedonLoadFailed方法,实现了我们在Glide使用时配置的「加载图片前展示展位图」、「加载失败展示加载错误图」的逻辑。
  2. ImageViewTarget中的父类ViewTarget中的监听类SizeDeterminerLayoutListener,要将图片填充ImageView时可能会使用到。这是因为Glide需要获取到对应控件的大小来填充ImageView,但有时刚进入Activity,Glide已经获取到图片资源了,此时布局树还没有渲染完成,当布局渲染完成时会回调SizeDeterminerLayoutListener中的onPreDraw方法,之后Glide才会根据获取到的控件大小来执行加载任务。

上述源码into返回了一个内部方法into,我们继续往下看,


//简化后的代码
private <Y extends Target<TranscodeType>> Y into(
    @NonNull Y target,
    @Nullable RequestListener<TranscodeType> targetListener,
    BaseRequestOptions<?> options,
    Executor callbackExecutor) {

    //构建了一个Request
    //本例子最终会返回一个SingleRequest对象
    Request request = buildRequest(target, targetListener, options, callbackExecutor);
    Request previous = target.getRequest();
    
    if (...) {
        //这里获取之前的Request是一种优化,省略代码
        previous.begin();
        return target;
    }
    
    target.setRequest(request);
    
    return target;
}

Request

Request是一个接口,可见Request也是Glide中一种高度抽象的概念,简单看一下接口的定义,

定义了对请求的一些操作,包括开始、结束、回收、获取状态等。

Request 主要的实现类有三个:

  1. SingleRequest 通常使用的Request实现
  2. ThumbnailRequestCoordinator 该请求可以同时加载原图和缩略图
  3. ErrorRequestCoordinator 该请求用来在加载错误时加载某种图片资源

Request.begin方法会调用Engine.load,其中构建两个重要的对象EngineJob, DecodeJob,

  1. EngineJob, 用来执行 DecodeJob 以及管理加载完成的回调, 可以添加很多监听器。
  2. DecodeJob, 负责从缓存或者数据源中加载资源,并通过解码器转换为指定的资源类型。其中获取资源是通过DataFetcher抽象来完成的

DataFetcher

DataFetcher是Glide对获取数据源的一种接口抽象,核心的接口方法是loadData,它拥有很多个实现类实现了从不同的源获取数据,比如从文件、网络、资源AssetManager等等。

阅读源码可见,我们从网络上获取资源的实现类是HttpUrlFetcher,其内部实际是通过HttpURLConnection来完成的。

ResourceDecoder

当我们通过DataFetcher获取到对应的流之后, 会回调onDataFetcherReady,在其中调用decode方法对流进行解码,

//简化后的代码
@Override
public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
    DataSource dataSource, Key attemptedKey) {
    ...
    decodeFromRetrievedData();
}


//简化后的代码
private <Data> Resource<R> decodeFromData(DataFetcher<?> fetcher, Data data,
    DataSource dataSource) throws GlideException {
    ...
    //其中data是从回调方法里来的,这里例子中是InputStream流数据
    //dataSource是一个枚举类,这里是REMOTE,指数据来自于外部网络
    Resource<R> result = decodeFromFetcher(data, dataSource);
    return result;
}

//简化后的代码
@NonNull
private Resource<ResourceType> decodeResourceWithList(DataRewinder<DataType> rewinder, int width, 
    int height, @NonNull Options options, List<Throwable> exceptions) throws GlideException {
    for (int i = 0, size = decoders.size(); i < size; i++) {
        ResourceDecoder<DataType, ResourceType> decoder = decoders.get(i);
        DataType data = rewinder.rewindAndGet();
        if (decoder.handles(data, options)) {
            data = rewinder.rewindAndGet();
            //核心解码方法 ResourceDecoder接口
            //遍历List,根据DataType找到对应ResourceDecoder接口的实现
            result = decoder.decode(data, width, height, options);
        }
    }
    return result;
}

查看源码发现ResourceDecoder接口有很多实现, 包括解码Bitmap、VideoBitmap、Gif等等,我们这里是ByteBufferBitmapDecoder

public Resource<Bitmap> decode(@NonNull ByteBuffer source, int width, int height,
    @NonNull Options options) throws IOException {
    InputStream is = ByteBufferUtil.toStream(source);
    //最终是在DownSampler的decodeStream()方法中使用了BitmapFactory.decodeStream()来得到Bitmap对象
    return downsampler.decode(is, width, height, options);
}

解码之后,会调用到ImageViewTarget.onResourceReady中(看上面分析的DrawableImageViewTarget类中的方法),展示出图片来。

总结

Glide图片加载框架源码非常的庞大,如果不能够抽丝剥茧,一味的钻入代码中可能会晕头转向。很多人开始读源码时都会感觉读不懂,不是因为代码写的质量差,而是因为它的实现非常复杂。正是因为Glide这种实现架构,将加载图片的各个阶段都高度抽象,使得我们做需求,实现自定义化变得非常灵活。Glide的强大同样表现于它的缓存机制,下一节讲。这里用一张图对Glide架构做一个总结(图片来自于知乎用户艽野尘梦),