以常见的使用为例,详细分析Glide v4 源码

前言

这次的源码分析所对应的Glide版本为4.11.0,这是我为数不多的仔细看比较复杂的源码框架,上一次看还是大二的时候看ARouter,不过那时水平有限(也不知道现在比那时候有没有变强)。这次的源码分析写了4-5天左右,自己实践了Glide的一些功能+阅读文档+看源码,然后再总结成了这篇阅读笔记,也确实填充了我的一些知识空白。这篇阅读笔记内容比较详细,而且配合了注释+图片,看的朋友们应该也比较好懂。

本文分成以下几个部分,针对我们常用的Glide.with().load().into()的写法,以网络请求为例子进行了分析。我写完了这篇之后,也看了一些关于Glide的面经,稍加整理和思索基本上可以回答出来问题。如果读者觉得不错的话,就点个赞吧,有问题我们可以在留言区探讨探讨~(希望我能回答出来hh

Part I 请求铺垫

常规使用方式

日常开发中使用Glide一般用于给ImageView或者其子类加载图片资源,或者是使用Glide下载图片。
本文以首次加载网络中的一张图片到ImageView中为例分析Glide的源码。之所以强调首次是因为Glide中存在图片复用的策略。

// 一个比较简单的例子
Glide.with(this) // 返回RequestManager对象
    .load(imagePath)    // 返回RequestBuilder对象
    .into(imageView)

// 一个稍微复杂的配置的例子
Glide.with(this)
    .load(imagePath)
    .placeholder(R.color.black)     // 返回BaseRequestOptions
    .error(R.drawable.ic_launcher_background) // 返回BaseRequestOptions
    .transform(MultiTransformation(FitCenter(), CenterCrop()))
    .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
    .into(imageView) // 这里还可以指定Target
    
val resultBitmap = Glide.with(this)
    .asBitmap()
    .load(imagePath)
    .submit()
    .get()

复制代码

思考如下问题:

  1. Glide使用过程中需要注意线程切换问题吗?
  2. 作为一个流式调用,很难不联想到如RxJava Kotlin中的中间操作和终止操作。Glide的流式调用是否可以划分成“中间操作”和“终止操作”呢?
  3. Glide的缓存机制的实现如何?Glide如何重用资源的?

Glide构造方法

Glide在使用时通常都是一个以Glide单例开始,Glide.with(context)之后开始进行请求加载过程。
所以我们先看看Glide中的构造函数。

// Glide#constructor(()
Glide(
    @NonNull Context context,
    @NonNull Engine engine,
    @NonNull MemoryCache memoryCache,
    @NonNull BitmapPool bitmapPool,
    @NonNull ArrayPool arrayPool,
    @NonNull RequestManagerRetriever requestManagerRetriever,
    @NonNull ConnectivityMonitorFactory connectivityMonitorFactory,
    int logLevel,
    @NonNull RequestOptionsFactory defaultRequestOptionsFactory,
    @NonNull Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions,
    @NonNull List<RequestListener<Object>> defaultRequestListeners,
    boolean isLoggingRequestOriginsEnabled,
    boolean isImageDecoderEnabledForBitmaps) {
  // 初始化全局变量
  // 如 this.engine = engine 
  .......

  final Resources resources = context.getResources();
 // Registry 类负责管理图像的编码解码等
  registry = new Registry();
  registry.register(new DefaultImageHeaderParser());
  // Right now we're only using this parser for HEIF images, which are only supported on OMR1+.
  // If we need this for other file types, we should consider removing this restriction.
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
    registry.register(new ExifInterfaceImageHeaderParser());
  }

  List<ImageHeaderParser> imageHeaderParsers = registry.getImageHeaderParsers();

 // 初始化和图像采样解码相关的类 
 .....

 // 不同类型的数据交给不同类型的工厂类初始化和处理
 // 展示一下几个例子,其他的省去了
  ResourceDrawableDecoder resourceDrawableDecoder = new ResourceDrawableDecoder(context);
  ResourceLoader.StreamFactory resourceLoaderStreamFactory =
      new ResourceLoader.StreamFactory(resources);
......

 // 不同类型的图像(Bitmap File)有不同的Decoder,这些Decoder都在Registry中注册
......
 
 // 对于不同的Target类型产生不同的Target对象
  ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory();
  
 //Glide单例中的属性全部都托付给GlideContext管理
  glideContext =
      new GlideContext(
          context,
          arrayPool,
          registry,
          imageViewTargetFactory,
          defaultRequestOptionsFactory,
          defaultTransitionOptions,
          defaultRequestListeners,
          engine,
          isLoggingRequestOriginsEnabled,
          logLevel);
}
复制代码

Glide构造函数签名、构造方法中涉及到的重要的类。

类型 作用
Engine 管理、加载活动资源和缓存资源
MemoryCache 使用LRU缓存算法缓存资源实现
BitmapPool 回收重用Bitmap资源
ArrayPool 回收和重用资源
RequestManagerRetriever 获取RequestManager对象
RequestOptionsFactory RequestOptions的工厂类
RequestRegistry decode encoder transcoder等等注册管理
GlideContext 作为一个全局的Glide上下文对象 向外提供加载资源需要的参数

Glide单例的获取

// Glide#get
public static Glide get(@NonNull Context context) {
  if (glide == null) {
  //和GlideModule有关
    .....
    synchronized (Glide.class) {
      if (glide == null) {
        checkAndInitializeGlide(context, annotationGeneratedModule);
      }
    }
  }

  return glide;
}
复制代码

Bitmap 重用和回收的实现

Bitmap重用和回收主要依赖的是BitmapPool对象。在Glide构造方法中,BitmapPool主要和encoder,decoder,transcoder,DownSampler这些图像处理相关的类打交道。由于我本身对图像处理这块暂时不太感兴趣,于是就看看BitmapPool管理bitmap的逻辑。

LruBitmapPool

BitmapPool主要实现类是LruBitmapPool,一个使用了Lru算法管理Bitmap对象的类。按照Lru算法的思想,一个新的Bitmap对象被添加到链表中,并且链表的大小超过了maxSize,最不常用的Bitmap会被移除:

// LruBitmapPool#put(Bitmap)

public synchronized void put(Bitmap bitmap) {
  if (bitmap == null) {
    throw new NullPointerException("Bitmap must not be null");
  }
  // 调用Bitmap.recycle()时会使用native方法清理Bitmap引用指向的像素。
  // recycle方法主要是作为一个标记使用,方便GC回收。
  // 如果Bitmap已经回收了 isRecycled返回true
  if (bitmap.isRecycled()) {
    throw new IllegalStateException("Cannot pool recycled bitmap");
  }
  // isMutable()表示Bitmap是可以被复用的
  if (!bitmap.isMutable()
      || strategy.getSize(bitmap) > maxSize
      || !allowedConfigs.contains(bitmap.getConfig())) {
      
      //....
      
        // 如果:
        // 1. 当前的Bitmap 不能被复用
        // 2. 当前的Bitmap的大小过大(bytes超过了maxSize)
        // 3. 不满足相关Bitmap设置
        // 直接标记bitmap回收,不放入BitmapPool中
        bitmap.recycle();
    return;
  }

  final int size = strategy.getSize(bitmap);
  strategy.put(bitmap);
  tracker.add(bitmap);

  puts++;
  currentSize += size;

  // ....

  dump();

  evict();
}
复制代码

另外还有一个相关类是ArrayPool,思想和这个类似。

Glide.with()

RequestManager和RequestManagerRetriever

Glide.with()返回的是一个RequestManager对象,这个对象的取用是通过RequestManagerRetriever实现的。在看具体的代码之前,我已经归纳了这两个类的职责:

  • RequestManager职责:
    1. 监听Activity声明周期回调
    2. 管理和启动Request
  • RequestManagerRetriever职责:
    1. 通过不同的重载函数返回RequestManager对象

通过RequestManagerRetriever返回RequestManager对象

通过RequestManagerFragment监听生命周期

主要是通过get()一系列的重载函数,如图所示:
下面选取RequestManager#get(Activity)和RequestManager#get(Context)进行展示

RequestManager#get(Activity)
public RequestManager get(@NonNull Activity activity) {
  // 如果在UI线程:
  // 这里的是否在UI线程是通过looper来判断,如果传入的context是ApplicationContext
  // 自然不在"UI线程"
  if (Util.isOnBackgroundThread()) {
  // --- 备注2: RequestManager#get(Context) ---
    return get(activity.getApplicationContext());
  } else {
    assertNotDestroyed(activity);
    android.app.FragmentManager fm = activity.getFragmentManager();
    // --- 备注1:构建RMFragment来监听Activity的生命周期回调 ---
    return fragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
  }
}
复制代码

一般来说,我们使用Glide的时候并不需要在Activity的生命周期回调函数中注册Glide.clear(),这是因为Glide自身通过RequestManagerRetriever返回的RequestManager需要具备能够监听生命周期回调的能力。这样实现的目的是为了Glide能够方便的回收资源。当Activity被销毁的时候,自然不需要额外的加载,更不需要加载好的Bitmap在内存中占用资源。

//RequestManagerFragment 相关的生命周期方法
@Override
public void onStart() {
  super.onStart();
  // lifeCycle是ActivityFragmentLifecycle的实例,用于提示RMFragment绑定的Activity生命周期回调
  lifecycle.onStart();
}
复制代码

监听方式的实现是通过将RequestManager和一个没有界面的(view-less)的Fragment:RequestManagerFragment(后文简写为RMFragment)绑定。

// --- 备注1:构建RMFragment来监听Activity的生命周期回调 ---
private RequestManager fragmentGet(
    @NonNull Context context,
    @NonNull android.app.FragmentManager fm,
    @Nullable android.app.Fragment parentHint,
    boolean isParentVisible) {
    // view-less RMFragment
  RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
  RequestManager requestManager = current.getRequestManager();
  if (requestManager == null) {
    // TODO(b/27524013): Factor out this Glide.get() call.
    Glide glide = Glide.get(context);
    // 绑定RMFragment
    requestManager =
        factory.build(
            glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
    current.setRequestManager(requestManager);
  }
  return requestManager;
}
复制代码

RMFragment内部实现了对于它所以来的Activity生命周期的监听。通过Factory构建的RequestManager这样一来就有了生命周期感知力。

如果传入的是一个context,就转入备注2的get(Context):

// --- 备注2: RequestManager#get(Context) ---
// RequestManagerRetriver#get(context)

public RequestManager get(@NonNull Context context) {
  if (context == null) {
    throw new IllegalArgumentException("You cannot start a load on a null Context");
    // Util.isOnMainThread() 是否是在主线程上被调用
    // 根据不同的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
        // Only unwrap a ContextWrapper if the baseContext has a non-null application context.
        // Context#createPackageContext may return a Context without an Application instance,
        // in which case a ContextWrapper may be used to attach one.
        && ((ContextWrapper) context).getBaseContext().getApplicationContext() != null) {
      return get(((ContextWrapper) context).getBaseContext());
    }
  }

// 使用单例模式返回一个RequestManager对象
  return getApplicationManager(context);
}
复制代码

RequestManagerRetriver#get(context)主要思想是对所有的Context实例进行类型上的匹配。主要的情况分成有UI的context和没有UI的context,后者指的是application context。对于这样的context,通过RequestManagerRetriever返回一个applicationManager单例:

// RequestManagerRetriever#getApplicationManager
private RequestManager getApplicationManager(@NonNull Context context) {
  // Either an application context or we're on a background thread.
  if (applicationManager == null) {
    synchronized (this) {
      if (applicationManager == null) {
      //.....
        Glide glide = Glide.get(context.getApplicationContext());
        applicationManager =
            factory.build(
                glide,
                new ApplicationLifecycle(),
                new EmptyRequestManagerTreeNode(),
                context.getApplicationContext());
      }
    }
  }
复制代码

如果是UI相关的context,每次都会构造一个Glide实例,这也并不难理解,毕竟UI相关的context是有生命周期的。

// RequestManagerRetriever#supportFragmentGet
private RequestManager supportFragmentGet(....) {
  //....
  if (requestManager == null) {
  //...
  // 构造一个实例对象
    Glide glide = Glide.get(context);
    requestManager =
        factory.build(
            glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
    current.setRequestManager(requestManager);
  }
  return requestManager;
}
复制代码

Glide.with().asBitmap().load()

需要十分注意的是Glide.with()返回的是一个RequestManager对象,之后的asBitmap(), load(), transform()都返回的是RequestBuilder对象。

RequestBuilder

RequestBuilder本身是BaseRequestOptions的子类

RequestBuilder<TranscodeType> extends BaseRequestOptions<RequestBuilder<TranscodeType>>
复制代码

transform(), override(),placeholder()是RequestBuilder从父类继承而来的方法。
构建这个父子类的考虑,我想也是因为需要让RequestBuilder聚焦于自己的单一职责。
RequestBuilder的主要职责:

  1. 对于资源类型进行转化,通过各类asXXX:asBitmap(), asFile()
  2. 加载资源:load(), preload(), submit()
  3. 展示资源:into()

还有可以使用transistion进行过渡。

load相关方法

RequestBuilder#load方法有很多的重载函数,都是大同小异。实际上load方法并没有做实质性的工作,只是对于load(model)中接受的参数model,都会上转型成Object类存储起来。
举一个简单的例子:

// RequestBuilder#load(File file)
public RequestBuilder<TranscodeType> load(@Nullable File file) {
  return loadGeneric(file);
}

// RequestBuilder#loadGeneric
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
  this.model = model;
  isModelSet = true;
  return this;
}
复制代码

RequestOptions

BaseRequestOptions

RequestBuilder 继承了BaseRequestOptions中调用图像的裁减变换方法,具体实现需要看看BaseRequestOptions中:

//BaseRequestOptions#centerCrop()

public T centerCrop() {
//这里通过策略模式实现图片的变化
  return transform(DownsampleStrategy.CENTER_OUTSIDE, new CenterCrop());
}
复制代码

Glide.with().load().into()

真正的开始加载的方法被放在了into中。into方法一般来说使我们常用的操作的重点,在RequestBuilder中有很多into方法的重载。我们选择两种常用的具体看一下:

// 1.  RequestBuilder#into(ImageView) 加载对象到ImageView上
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
// 一定需要是在主线程上
  Util.assertMainThread();
  Preconditions.checkNotNull(view);

  BaseRequestOptions<?> requestOptions = this;

 // .....
  
  return into(
      glideContext.buildImageViewTarget(view, transcodeClass),
      /*targetListener=*/ null,
      requestOptions,
      Executors.mainThreadExecutor());
}

// 2.  RequestBuilder#into(Target) 加载对象到Target上
// (可能是我们自定的Target,也可能使用一些Target API
public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {
  return into(target, /*targetListener=*/ null, 
  // 指定回调的线程为主线程
  Executors.mainThreadExecutor());
}

复制代码

还有其他的重载方法,但是大体的操作和思路都是类似的。这些重载方法都指定了Executor为主线程,并且这些方法条条大路通罗马,都会最终调用这个重载:

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()");
  }
  // 1. 构建Request,注意callbackExecutor为主线程的Executor
  Request request = buildRequest(target, targetListener, options, callbackExecutor);

  // 2. 检查对应的Target的Request能否复用
  Request previous = target.getRequest();
  if (request.isEquivalentTo(previous)
      && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
    if (!Preconditions.checkNotNull(previous).isRunning()) {
      // 3. 可以复用的话,begin
      previous.begin();
    }
    return target;
  }

  // 4. 如果不能复用,绑定Tracker,构建请求
  // TargetTracker和生命周期回调相关。
  requestManager.clear(target);
  target.setRequest(request);
  // 这里最终还是会调用begin
  requestManager.track(target, request);

  return target;
}
复制代码

从into的代码中可以得出以下的结论

  1. Request会被TargetTrack追踪。TargetTracker实现了LifecycleListener,会监听生命周期回调。
  2. Request 对象和Target对象绑定,相同的Target的Request是相同的。
  3. Request绑定的callbackExecutor,也就是执行完成后的回调线程池是Executors.mainThreadExecutor()。

Target

返回的Target对象也非常重要,Glide通过回调可以操作加载好的资源设置到ImageView中。举Target的一个实现类ImageViewTarget为例:

// ImageViewTarget#onResourceReady
@Override
public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
  if (transition == null || !transition.transition(resource, this)) {
  // 设置资源到ImageView
    setResourceInternal(resource);
  } else {
    maybeUpdateAnimatable(resource);
  }
}
复制代码

当回调onResourceReady被触发时,最终会转到BitmapImageViewTarget(ImageViewTarget)的子类中设置Bitmap:

// BitmapImageViewTarget#setResource
@Override
protected void setResource(Bitmap resource) {
  view.setImageBitmap(resource);
}
复制代码

SingleRequest#begin

从上文into方法中会转到SingleRequest的begin:

   // 3. 可以复用的话,begin
      previous.begin();
复制代码

作为一个接口,Request的一个实现类是SingleRequest。SingleRequest的职责就是加载资源(Resource)到目标(Target)中。SingleRequest定义了一系列的枚举方法标识当前资源的加载状态:

// Request已经创建,但是还没有执行
PENDING,
// 获取资源的过程中
RUNNING,
// 等待Target中定义的callback被回调,用于确定图片的大小
WAITING_FOR_SIZE,
// 图片执行完成
COMPLETE,
// 失败
FAILED,
// 用于使用placeholder占位符清除了Request,在这种情况下可能需要重启Request
CLEARED,
复制代码

理解了这些枚举的意思,可以方便我们更好的阅读begin的源代码:

// SingleRequest#begin

public void begin() {
  synchronized (requestLock) {
    assertNotCallingCallbacks();
    stateVerifier.throwIfRecycled();
    startTime = LogTime.getLogTime();
   
    // ......
    // 1. 不能begin正在运行的Request
    if (status == Status.RUNNING) {
      throw new IllegalArgumentException("Cannot restart a running request");
    }
    
    // 2. Resource获取完成
    if (status == Status.COMPLETE) {
      // onResourceReady会对资源resource进行检查
      onResourceReady(resource, DataSource.MEMORY_CACHE);
      return;
    }
    
    
    status = Status.WAITING_FOR_SIZE;
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
      // 3. 图片大小确定会调用Engine#load方法加载资源
      onSizeReady(overrideWidth, overrideHeight);
    } else {
      target.getSize(this);
    }

    // .......
  }
}
复制代码

使用Glide加载一张网络上的图片使我们最常用场景之一,这个过程会被Engine#load方法处理。需要我们注意的是如下几个参数:

engine.load(
    // GlideContext类型的变量,管理了很多和加载配置相关的信息
    glideContext,
    // model Object 类型
    model, 
    //.....
    // ResourceCallback资源加载后的回调,SingleRequest实现了它,这意味着资源加载好了之后会将控制权交还给SingleRequest中的方法
    this, 
    // Executors.mainThreadExecutor()
    callbackExecutor);
复制代码

整体的调用链非常长,我归纳在了这张图片中,我们详细看看。

截屏2021-08-23 15.01.06.png

Part II 加载开始

Engine#load

从Request#begin之后的下一站我们就回来到Engine#load

Glide最为经典的部分就是将缓存模型分成了三个部分:

  1. 活动资源 (Active Resources) – 现在是否有另一个 View 正在展示这张图片?
  2. LRU内存缓存 (Memory cache) – 该图片是否最近被加载过并仍存在于内存中?
  3. 资源类型(Resource) – 该图片是否之前曾被解码、转换并写入过磁盘缓存?
  4. 数据来源 (Data) – 构建这个图片的资源是否之前曾被写入过文件缓存

Glide中的缓存-官方文档

后面两个部分是硬盘缓存我们可以看成一个部分;第一第二都是在内存中的,我们统称为内存缓存。

Glide会首先看内存缓存(活动缓存和Lru缓存)中是否存在这样的资源,如果没有再去网络上请求。Glide对于内存缓存的处理后面的章节会详细讨论,这里先注重网络请求的过程。

具体对应的函数如表所示:

Engine#load包含了从内存中寻找(loadFromMemory)和从网络中寻找(waitForExistingOrStartNewJob)。先看看Engine#load

public <R> LoadStatus load(......) {
// ......
// 构造key
  EngineKey key =
      keyFactory.buildKey(........);


// 查找内存中是否有这样的资源
  EngineResource<?> memoryResource;
  synchronized (this) {
    memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);

    if (memoryResource == null) {
    // 如果没有,再去网络上查找
      return waitForExistingOrStartNewJob( ...... ;
    }
  }
复制代码

Engine#waitForExistingOrStartNewJob

我们假设我们现在是第一次加载,内存缓存、硬盘缓存中不存在我们需要的资源,我们需要去网络上加载资源。

// Engine#waitForExistingOrStartNewJob
private <R> LoadStatus waitForExistingOrStartNewJob(....) {

// 如果EngineJob存在
  EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
  if (current != null) {
    current.addCallback(cb, callbackExecutor);
    // .... 
    return new LoadStatus(cb, current);
  }

  EngineJob<R> engineJob =
      engineJobFactory.build(....);

  DecodeJob<R> decodeJob =
      decodeJobFactory.build(.....);

  jobs.put(key, engineJob);

  engineJob.addCallback(cb, callbackExecutor);
  // 执行decodeJob
  engineJob.start(decodeJob);

///....
  return new LoadStatus(cb, engineJob);
}
复制代码

Engine#waitForExistingOrStartNewJob首先会看看是否存在EngineJob。存在的依据是看看是否在Map中存在相同的key。EngineKey的参数很多,稍微修改一些都会被认为是不同的Key,导致重新请求

EngineKey key =
    keyFactory.buildKey(
        model,
        signature,
        width,
        height,
        transformations,
        resourceClass,
        transcodeClass,
        options);
复制代码

这里应该是考虑相同的EngineJob是否可以复用。如果不能复用,使用线程池执行DecodeJob:

EngineJob#start

public synchronized void start(DecodeJob<R> decodeJob) {
  this.decodeJob = decodeJob;
  // 选择从磁盘还是从活动资源中解码
  // 如果是后续请求,且在磁盘中有缓存,就会从磁盘中拿取
  GlideExecutor executor =
      decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();
      // 最终会调用run方法
  executor.execute(decodeJob);
}
复制代码

DecodeJob#run

线程池最终会调用DecodeJob中的run方法。在分析具体的代码之前,需要理解DecodeJob的职责:

DecodeJob的职责

A class responsible for decoding resources either from cached data or from the original source and applying transformations and transcodes.

根据文档中的描述,DecodeJob的具体职责是解码缓存(cache)和从原始来源处的资源,并且实现过渡(transformation)和transcode(转码)

class DecodeJob<R>
    implements DataFetcherGenerator.FetcherReadyCallback,
        Runnable,
        Comparable<DecodeJob<?>>,
        Poolable
复制代码

DecodeJob需要解码的资源应该也在run方法中的调用链上:

// DecodeJob#run
public void run() {
 // 获取数据的类
  DataFetcher<?> localFetcher = currentFetcher;
  try {
    if (isCancelled) {
      notifyFailed();
      return;
    }
    runWrapped();
  } catch (CallbackException e) {
    // 异常及其处理
  }
}
复制代码

沿着调用链往下,我们最终可以得到SourceGenerator#startNext方法,我们先简单看看源码:

SourceGenerator#startNext

// SourceGenerator#startNext

// startNext的目标是成功使用DataFetcher进行请求数据
// 成功的话返回true
public boolean startNext() {
  
  //. ....
  sourceCacheGenerator = null;

  loadData = null;
  boolean started = false;
  while (!started && hasNextModelLoader()) {
    // 1. 通过ModelLoader构建LoadData
    // --- 备注1 ---- Glide如何选择具体的ModelLoader对象?
    loadData = helper.getLoadData().get(loadDataListIndex++);
    if (loadData != null
        && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
            || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
      started = true;
      // 2. 进行真正的请求过程
      startNextLoad(loadData);
    }
  }
  return started;
}
复制代码

startNext的核心思想是使用一个while寻找找到合适的ModelLoader来建立LoadData,所以它通过引入以下角色完成了一次请求的过程:

  1. DataFetcher
  2. ModelLoader

DataFecther顾名思义,是完成网络请求的重要角色,后面会详细讨论。先看看ModelLoader:

ModelLoader

Glide设计ModelLoader的作用就是为了将复杂的数据类型转换成DataFetcher处理的LoadData。ModelLoader本质上是创建LoadData的一个工厂。按照上面例子中的假设,我们还是通过一个http url(String)进行网络请求,HttpGlideUrlLoader作为ModelLoader的实现类回去实现相关的方法:

public class HttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream>
复制代码

目的非常纯粹,就是将GlideUrl包装的String url对象转换成Decoder能够处理的InputStream。

// HttpGlideUrlLoader#buildLoadData
public LoadData<InputStream> buildLoadData(
    @NonNull GlideUrl model, int width, int height, @NonNull Options options) {
  // GlideUrls memoize parsed URLs so caching them saves a few object instantiations and time
  // spent parsing urls.
  GlideUrl url = model;
  if (modelCache != null) {
    url = modelCache.get(model, 0, 0);
    if (url == null) {
      modelCache.put(model, 0, 0, model);
      url = model;
    }
  }
  int timeout = options.get(TIMEOUT);
  // 返回构建的LoadData对象,并且配置了一个DataFetcher的实现类
  return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
}
复制代码

ModelLoader对象的匹配

我们返回上面的备注1,看看Glide如何匹配对应的ModelLoader对象:

// --- 备注1 ---- Glide如何选择具体的ModelLoader对象?
loadData = helper.getLoadData().get(loadDataListIndex++);
复制代码
List<LoadData<?>> getLoadData() {
    // .....
    
    //1. 从glideContext中寻找可能的ModelLoader
    // glideContext是所有相关对象的管理上下文,是在Glide构造器中创建的,前面已经提及过
   
    List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model);
    for (int i = 0, size = modelLoaders.size(); i < size; i++) {
      ModelLoader<Object, ?> modelLoader = modelLoaders.get(i);
      // 2. 面所说的buildLoadData方法构建LoadData对象
      LoadData<?> current = modelLoader.buildLoadData(model, width, height, options);
   // ...
    }
  }
  return loadData;
}
复制代码

ModelLoader的匹配沿着调用链继续寻找,可以在ModelLoaderRegistry中找到结果

public <A> List<ModelLoader<A, ?>> getModelLoaders(@NonNull A model) {
  List<ModelLoader<A, ?>> modelLoaders = getModelLoadersForClass(getClass(model));
 /// ......
  for (int i = 0; i < size; i++) {
   
   // ...
   // 如果ModelLoader能够handle对应的Model(这里会转换成GlideUrl包装的对象
    if (loader.handles(model))
  // ...
}
复制代码

SourceGenerator#startNextLoad

我们分析完成SourceGenerator#startNext之后,按着主线继续往下前行,就会到真正的启动load的方法:startNextLoad

// SourceGenerator#startNextLoad

private void startNextLoad(final LoadData<?> toStart) {
  loadData.fetcher.loadData(
      helper.getPriority(),
      new DataCallback<Object>() {
        @Override
        public void onDataReady(@Nullable Object data) {
          if (isCurrentRequest(toStart)) {
            onDataReadyInternal(toStart, data);
          }
        }

        @Override
        public void onLoadFailed(@NonNull Exception e) {
          if (isCurrentRequest(toStart)) {
            onLoadFailedInternal(toStart, e);
          }
        }
      });
}
复制代码

方法主要是使用LoadData绑定的Fetcher对象进行数据的抓取,针对成功和失败两种情况进行回调。
这样我们就来到的我们最后的环节,使用HttpUrlFetcher进行请求:

HttpDataFetcher#loadData

// HttpDataFetcher#loadData
public void loadData(
    @NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
  long startTime = LogTime.getLogTime();
  try {
    // 处理重定向的情况
    InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
    // 成功回调
    // 这里的callback是DataCallback的实现,这个方法由上文中的
    // SourceGenerator中的匿名内部类实现
    callback.onDataReady(result);
  } catch (IOException e) {
    // ....
    // 异常回调
    callback.onLoadFailed(e);
  } finally {
    // ...
    }
  }
}
复制代码

请求图片的过程中可能会出现重定向的问题,而且这里的LoadData方法其实也只是loadDataWithRedirects的包装,具体的请求过程分析如下:

HttpUrlFetcher#loadDataWithRedirects

// HttpUrlFetcher#loadDataWithRedirects

// 使用HttpUrlConnection加载并且输出InputStream
private InputStream loadDataWithRedirects(
    URL url, int redirects, URL lastUrl, Map<String, String> headers) throws IOException {
    // 不可有太多的重定向,出现重定向会递归调用,这个也是为了防止StackOverflow
  if (redirects >= MAXIMUM_REDIRECTS) {
    throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
  } else {
   // ......
  urlConnection = connectionFactory.build(url);
  // ..... 省略了一些关于HttpUrlConnection的配置代码
  
  // 连接
  urlConnection.connect();
 
 // 获取输入流
  stream = urlConnection.getInputStream();
  if (isCancelled) {
    return null;
  }
 //......
 
 // 如果返回了成功的状态代码
  if (isHttpOk(statusCode)) {
    // 获取InputStream
    return getStreamForSuccessfulRequest(urlConnection);
    //如果状态码的类型和重定向有关
  } else if (isHttpRedirect(statusCode)) {
  
    // 检查和获取重定向url有无问题
    String redirectUrlString = urlConnection.getHeaderField("Location");
    if (TextUtils.isEmpty(redirectUrlString)) {
      throw new HttpException("Received empty or null redirect url");
    }
    URL redirectUrl = new URL(url, redirectUrlString);
    // 清理资源
    // 关闭资源 inputStream.close() urlConnection.disconnect()
    cleanup();
    // 递归调用
    return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
  } else if (statusCode == INVALID_STATUS_CODE) {
    throw new HttpException(statusCode);
  } else {
    throw new HttpException(urlConnection.getResponseMessage(), statusCode);
  }
}
复制代码

当请求成功时,Glide就会回调到SourceGenerator的匿名内部类Callback中,将返回的InputStream进行解码。具体的内容这里就不分析了,

总结一下

上面的请求过程非常长,有点复杂,我使用一个图来总结它在过程中的主要调用逻辑:

请求的入口是RequestBuilder#into,通过Request#begin开始请求。在Request中会首先标记标记当前的请求状态为PENDING,也就是构建好了请求,但是还未开始。然后再进入Engine#load,在Engine#load中会判断是要加载内存中的(活动资源 Cache资源)资源,还是加载磁盘中,亦或是开启新任务。准备好了之后就会开启子线程处理这个任务,并且回到begin。当前请求状态是RUNNING。然后就等让Target等待结果,在这个过程中可以设置ImageView的;placeholder。

Glide缓存分析

内存两级缓存的配合策略

loadFromMemory()首先会去ActiveResource(活动资源)中寻找资源,如果Active中不存在,再会去Cache(缓存)中寻找。这两个缓存的配合关系如图所示:

Case#1: 如果活动资源中直接有需要的Key对应的资源,直接从活动资源里面拿
Case#2:如果活动资源中没有,从Cache中拿,并且将这个资源获取之后“激活”,放入活动资源。

活动资源和LRU资源都使用了引用计数的方式。当acquired计数器变成0,活动资源中的资源就会退到LRU资源中。

synchronized void acquire() {
 // ..... 
  ++acquired;
}
复制代码

之所以在内存缓存上设计这样的两级缓存,个人是这样理解的:
如果只有Lru缓存,最不经常使用的资源,如图中的1,会随着后面资源的加入被移除,如果需要使用,又需要重新请求。设计活动资源的目的是表示在这里的资源正在被使用中,当这个资源需要的时候,可以从LRU中移除,减轻LRU缓存的压力。

loadFromMemory

// Engine#loadFromActiveResources(key) 
private EngineResource<?> loadFromActiveResources(Key key) {
// activeEngineResource是为了方便Resource资源被回收
// Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>()
// 资源的取用直接通过键值对进行
  EngineResource<?> active = activeResources.get(key);
  if (active != null) {
    active.acquire();
  }

  return active;
}

// Engine#loadFromCache(key) 
private EngineResource<?> loadFromCache(Key key) {
  EngineResource<?> cached = getEngineResourceFromCache(key);
  if (cached != null) {
    cached.acquire();
    // 如果在缓存中存在,需要将这个资源“激活”到活动资源中
    activeResources.activate(key, cached);
  }
  return cached;
}
复制代码

Engine#loadFromActiveResources(key) 中的activeResources是ActiveResource的一个实例。ActiveResource使用了Map将Key和Resource进行绑定

@VisibleForTesting final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();

class ResourceWeakReference extends WeakReference<EngineResource<?>>
复制代码

ResourceWeakReference中的对象一般是Bitmap,这样的操作是为了当内存不足时方便回收被弱引用修饰的Bitmap。举个例子,当Lru中的资源被激活到活动资源之后,就会调用reset()方法,设置引用指向null,方便GC回收。

// ResourceWeakReference#reset
void reset() {
  resource = null;
  // native clear
  clear();
}
复制代码

getEngineResourceFromCache()在具体实现中引入了MemoryCache。MemoryCache通过LRU算法管理放入的对象,当对象的数量超过规定的大小时,会抛弃最不常用的对象。

public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache
复制代码

Glide的磁盘缓存策略

缓存策略 意义
ALL 对于远程的图片缓存Data和Resource,对于本地图片,只缓存Resource
AUTOMATIC 从磁盘上读取下载的原始数据
DATA 只缓存Data
NONE Data和Resource都不缓存
RESOURCE 只缓存Resource

在我的理解中,Data表示的是没有被处理的原始的数据,Resource相对的表示的是处理之后的数据。
Glide默认使用的策略是AUTOMATIC,这种策略会加载没有被裁切,变换的原始的图片。当加载完了一次从网络上请求的图片之后,如果在磁盘缓存中能够”找到“缓存,则会从缓存中显示。正如上面所说的,在Engine#load方法中会首先构造EngineKey,EngineKey就是文件的标识,它由以下的内容组成:

EngineKey key =
    keyFactory.buildKey(
        model,
        signature,
        width,
        height,
        transformations,
        resourceClass,
        transcodeClass,
        options);
复制代码

我这里Debug了来看看当前的例子中,这些参数具体的内容。
EngineKey的组成部分
key的要求是很严格的,上面的参数,比如长宽,model(网络url)配置改变了都会导致EngineKey不同,从而不能从磁盘缓存中加载图片。磁盘缓存也是通过Lru算法支持的DiskLruCache类实现。

如果在磁盘中有缓存,在EngineJob#start方法中就会使用diskExecutor从磁盘中加载:

public synchronized void start(DecodeJob<R> decodeJob) {
  this.decodeJob = decodeJob;
  GlideExecutor executor =
      decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();
  executor.execute(decodeJob);
}
复制代码

总结

  1. Glide.with().load().into()使我们常用的三段式的使用方法,但是每一段返回的类型有区别,需要注意一下
  2. Glide对于Application Context返回的是一个全局的单例,对于每一个UI context,会首先检查是否存在创建好的Glide单例,如果没有,则创建并返回一个UI绑定的单例。
  3. Glide使用子线程池加载缓存,磁盘中的资源,在这个过程中,传递的回调线程池为主线程的线程池。并没有做线程切换,通过回调Target中的设置图片的方法,将加载好的资源放置到ImageView上。

参考内容
# 聊一聊关于Glide在面试中的那些事

# Glide源码分析-缓存与复用机制

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享