Glide源码解析(四)

101 阅读10分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第10天,点击查看活动详情

Glide源码解析(一)

Glide源码解析(二)

Glide源码解析(三)

基本用法

Glide最基本的用法如下:

Glide.with(context)
     .load(url)
     .into(imageView)

本文就从这三个方法去学习Glide的源码。

PS:本文基于4.13.2版本的Glide源码。

废话不多说直接开始!

继续into

前文再续,书接上一回。本来打算一个方法出一篇,三篇就完结了,没料到这个into如此多弯弯绕绕,只得再出一篇。

上回说到EngineJob.start,我们就继续看看这个方法做了什么吧!

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

可以看到就是获取了一个Executor然后执行了decodeJob。既然DecodeJob是可以被execute的,那它必然是实现了Runnable接口的。那就看看DecodeJob的run方法里面是什么东东。

public void run() {
    DataFetcher<?> localFetcher = currentFetcher;
    try {
        if (isCancelled) {
            notifyFailed();
            return;
        }
        runWrapped();
    } catch (CallbackException e) {
        throw e;
    } catch (Throwable t) {
        //一些出错的处理,省略。
    } finally {
        if (localFetcher != null) {localFetcher.cleanup();}
    }
}

声明了一个DataFetcher,执行runWrapped,最后cleanup这个DataFetcher。

顾名思义这个DataFetcher是用来获取数据的,而具体的操作就在runWrapped里面,继续往下看。

private void runWrapped() {
    switch (runReason) {
      case INITIALIZE:
        stage = getNextStage(Stage.INITIALIZE);
        currentGenerator = getNextGenerator();
        runGenerators();
        break;
      case SWITCH_TO_SOURCE_SERVICE:
        runGenerators();
        break;
      case DECODE_DATA:
        decodeFromRetrievedData();
        break;
      default:
        throw new IllegalStateException("Unrecognized run reason: " + runReason);
    }
  }

runWrappered方法根据当前RunReason而执行不同的行为。RunReason则是一个枚举类,表明了当前执行的这次DecodeJob需要干什么。有三个值:

  • INITIALIZE:表明是第一次执行。
  • SWITCH_TO_SOURCE_SERVICE:表明需要从源获取数据。
  • DECODE_DATA:表明Glide从一个不属于Glide的线程中获取了一些数据,需要切换回Glide的线程去处理这些数据。

而RunReason的状态流转以及DecodeJob的工作还与另一个枚举类息息相关:Stage。该枚举类的官方注释为:

Where we're trying to decode data from.

与其说Stage表明Glide将从什么地方decode数据,我更愿意将Stage理解成任务执行的阶段。Stage有六个值:

  • INITIALIZE:初始化阶段。
  • RESOURCE_CACHE:从缓存的Resource中decode。
  • DATA_CACHE:从缓存的源数据decode。
  • SOURCE:从获取的源中decode。
  • ENCODE:成功加载后对转换后的资源进行编码。
  • FINISHED:结束阶段。

只有中间的RESOURCE_CACHE、DATA_CACHE、SOURCE这三个值是表明从什么地方decode数据的。而其他的值却是在描述任务执行的阶段。而Stage的状态流转基本上是在getNextStage方法中的,我们来看看这个方法。

private Stage getNextStage(Stage current) {
    switch (current) {
      case INITIALIZE:
        return diskCacheStrategy.decodeCachedResource()
            ? Stage.RESOURCE_CACHE
            : getNextStage(Stage.RESOURCE_CACHE);
      case RESOURCE_CACHE:
        return diskCacheStrategy.decodeCachedData()
            ? Stage.DATA_CACHE
            : getNextStage(Stage.DATA_CACHE);
      case DATA_CACHE:
        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        return Stage.FINISHED;
      default:
        throw new IllegalArgumentException("Unrecognized stage: " + current);
    }
  }

可以看到,Stage会根据缓存策略的不同而被指定为RESOURCE_CACHE、DATA_CACHE、SOURCE的其中一个。同时也让Stage只能从上往下流转,而不能反向流转。而事实上,从RESOURCE_CACHE到DATA_CACHE再到SOURCE,获取数据的源头是越来越远的。

这里解释一下Glide中,Resource、Data、Source的区别:

  • Source:即是源头,获取图片数据的源头,可以理解为文件(本地)或者url(网络)。
  • Data:也称Source Data,即是源数据,是指从Source获取到的图片原数据(未经编解码)。
  • Resource:资源,即是已经转码完成的图片,基本上可以直接进行展示(已编码)。

其中Data和Resource都会被缓存下来(前提是开启了对应的缓存选项)。

回到getNextStage中,我们假设是第一次执行getNextStage(也即对应runWrappered方法中RunReason为INITIALIZE的情况)会有以下流程:

graph TD
A[input] --> |INITIALIZE| B[getNextStage]
B --> C{开启Resource缓存?}
C --> |true| D[RESOURCE_CACHE]
D --> E[return]
C --> |false| F{开启Data缓存?}
F --> |true| G[DATA_CACHE]
G --> E
F --> |false| H[SOURCE]
H --> E

好了,由于考虑开启缓存的流程过于长,本例只考虑禁用缓存的情况,也即Stage为SOURCE的情况。

回过头来看runWrappered,这里再贴一遍:

private void runWrapped() {
    switch (runReason) {
      case INITIALIZE:
        stage = getNextStage(Stage.INITIALIZE);
        currentGenerator = getNextGenerator();
        runGenerators();
        break;
      //先只看INITIALIZE情况,其他先忽略。
    }
  }

stage是SOURCE,接着获取Generator,说明一下,这个Generator是DataFetcher的生成器。看方法名也能猜出个大概,获取了Generator就执行这个Generator。既然是DataFetcher的生成器,那运行内容自然就是生成DataFetcher了。并且没有执行DataFetcher的代码,大胆猜测一下DataFetcher是自动运行的。先来看看getNextGenerator。

private DataFetcherGenerator getNextGenerator() {
    switch (stage) {
      case RESOURCE_CACHE:
        return new ResourceCacheGenerator(decodeHelper, this);
      case DATA_CACHE:
        return new DataCacheGenerator(decodeHelper, this);
      case SOURCE:
        return new SourceGenerator(decodeHelper, this);
      case FINISHED:
        return null;
      default:
        throw new IllegalStateException("Unrecognized stage: " + stage);
    }
  }

很简单,根据不同的Stage生成不同的Generator。本例的Stage为SOURCE,自然返回的是SourceGenerator。

接下来就是运行这个SourceGenerator了。

private void runGenerators() {
    currentThread = Thread.currentThread();
    startFetchTime = LogTime.getLogTime();
    boolean isStarted = false;
    while (!isCancelled
        && currentGenerator != null
        && !(isStarted = currentGenerator.startNext())) {
      stage = getNextStage(stage);
      currentGenerator = getNextGenerator();
​
      if (stage == Stage.SOURCE) {
        reschedule();
        return;
      }
    }
    
    if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
      notifyFailed();
    }
​
  }

首先while循环的判断条件里面有一个startNext,我们就来看看SourceGenerator的startNext。

public boolean startNext() {
    //①
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      boolean isDataInCache = cacheData(data);
      if (!isDataInCache) {
        return true;
      }
    }
​
    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
      return true;
    }
    sourceCacheGenerator = null;
​
    //②
    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      loadData = helper.getLoadData().get(loadDataListIndex++);
      if (loadData != null
          && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
              || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
        started = true;
        startNextLoad(loadData);
      }
    }
    return started;
  }
  • ①处代码。与缓存逻辑有关,这里暂时忽略。
  • ②处代码。循环获取与执行LoadData。如果有符合条件的LoadData则开始执行,并且started的值将会设为true。相反,如果没有找到符合条件的LoadData,started则将会是false。started的结果会一直传递到DecodeJob的runGenerator中循环的判断条件里。如果返回的结果为false(证明该Generator并不能获取到资源),这时候DecodeJob就会寻找下一个Stage(也即上文讲到的“从近到远”)和与之对应的Generator,并再次尝试获取。

LoadData是什么东西呢?它是ModelLoader的内部类。那ModelLoader又是什么东西呢?

我们回想一下,之前我们传入的网址,Glide保存起来的时候的变量名是什么?不正是model吗?此model正是ModelLoader中的model。ModelLoader本身是一个接口,里面有两个泛型Model和Data,Model是传入的类型,Data是期望获取到的源数据类型,每一个实现负责将某个特定的Model类型加载出某个特定Data类型的数据。

为什么变量model的类型是Object?,就是因为要支持传入各种类型的Model,比如URL、String、ResourceId等等。而ModelLoader的功能则是从Model中加载出资源。举个例子,假如传入的model是ResourceId,也即Int类型,则我们可以假设存在一个IntModelLoader(当然这是假的,实际上人家叫ResourceLoader),它就负责从ResourceId加载资源。

事实上,Glide实例初始化的时候,就已经生成了好多张注册表,有ResourceDecoder、ResourceEncoder等的注册表,自然也有ModelLoader的注册表。Model注册表的类名为ModelLoaderRegistry,代码就不贴了,大概说一下吧。ModelLoaderRegistry内部实际上是一个List,Entry是一个包含一个Model类型、一个Data类型以及与Model、Data相匹配的ModelLoader的Factory的类。需要说明的是,即使是Model以及Data的类型都相同,也不代表就指定了某个特定的factory。比如Model为String,Data为InputStream可以是StringLoader(负责处理ContentResolver类的获取),也可以是DataUriLoader(负责处理Base64的图片)。

看回上面startNext的代码,循环地从DecodeHelper中获取LoadData。而LoadData的生成依赖于ModelLoader,所以要获取到LoadData就先要获取到ModelLoader。DecodeHelper中存放着GlideContext,GlideContext则存放着所有注册表。而从注册表检索ModelLoader只需要传入model,注册表会根据传入的model类型取出所有Model为该类型的ModelLoader(可能细心的同学会发现,刚刚上面说了,注册表里面存放的是factory,怎么检索出来的是ModelLoader呢?其实就是注册表在检索的过程中,一并将ModelLoader从factory中build出来了)。这时候还没能直接返回给SourceGenerator,还需要进行一个筛选的过程。比如说同样Model是String,既可能是ContentResolver的格式,也可能是Base64的内容。这时候就需要“问一问”各个ModelLoader是否有能力处理当前传入的这个model。ModelLoader有一个handles的方法,传入model,能处理的返回true,不能处理返回false。通过handles的筛选,剩下的ModelLoader都能处理传入的model。获取到了ModelLoader,就可以使用buildLoadData来构建对应的LoadData了。至此我们就得到了LoadData了。

检索LoadData的过程还是有点复杂的,以图的形式来总结一下。

sequenceDiagram
DecodeHelper ->> Registry:getLoadData()
Registry ->> ModelLoaderRegistry:getModelLoaders()
ModelLoaderRegistry ->> Factory:getModelLoadersForClass()
Factory ->> Factory:build()
Factory ->> ModelLoaderRegistry:List<ModelLoader>
ModelLoaderRegistry ->> Registry:Filtered List<ModelLoader>
Registry ->> DecodeHelper:Filtered List<ModelLoader>
DecodeHelper ->> ModelLoader:buildLoadData
ModelLoader ->> DecodeHelper:List<LoadData>

既然已经获取到了符合条件的LoadData,那就开始执行它吧!

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内部有一个对应的DataFetcher,loadData方法即是实际获取数据的方法了。上面我们猜测,DataFetch是自动运行的,看来也是对的。回调的DataCallback很简单,一个获取成功一个获取失败。我们看看获取成功,检查了一下是否为当前的请求(重复请求忽略旧请求),然后调用onDataReadyInternal并将获取到的数据传入。

void onDataReadyInternal(LoadData<?> loadData, Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
      dataToCache = data;
      cb.reschedule();
    } else {
      //①
      cb.onDataFetcherReady(
          loadData.sourceKey,
          data,
          loadData.fetcher,
          loadData.fetcher.getDataSource(),
          originalKey);
    }
  }

重点关注①处代码,把获取到的数据回调给DecodeJob。顺便提一嘴,网络请求的DataFetcher为HttpUrlFetcher,请求的底层是用HttpURLConnection。有兴趣的读者自己去了解吧!

public void onDataFetcherReady(
    Key sourceKey, Object data, DataFetcher<?> fetcher, DataSource dataSource, Key attemptedKey) {
  this.currentSourceKey = sourceKey;
  this.currentData = data;
  this.currentFetcher = fetcher;
  this.currentDataSource = dataSource;
  this.currentAttemptingKey = attemptedKey;
  this.isLoadingFromAlternateCacheKey = sourceKey != decodeHelper.getCacheKeys().get(0);
​
  if (Thread.currentThread() != currentThread) {
    runReason = RunReason.DECODE_DATA;
    callback.reschedule(this);
  } else {
    GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
    try {
      //①
      decodeFromRetrievedData();
    } finally {
      GlideTrace.endSection();
    }
  }
}

将回调过来的数据都赋一下值,然后关注①处代码。从这里到实际解码数据的调用链如下:

graph LR
decodeFromRetrievedData --> decodeFromData --> decodeFromFetcher

接下来我们看看decodeFromFetcher。

private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)
    throws GlideException {
  LoadPath<Data, ?, R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());
  return runLoadPath(data, dataSource, path);
}

这个方法做了两件事:获取LoadPath;执行这个LoadPath。

LoadPath这个东西其实跟LoadData很相似。LoadData负责从Source中读取Data,而LoadPath则负责将Data解码成所需的Resource。和LoadData一样,LoadPath也是通过注册表检索的。检索的过程如下:

graph LR
获取Decoder以及Transcoder--> 构建DecodePath列表 --> 构建LoadPath

LoadPath包含一个DecodePath的列表;DecodePath则包含一个Decoder以及一个Transcoder。注册表会首先找出所有符合条件的Decoder,然后再根据Decoder找出所有符合条件的Transcoder,接着将每一个Decoder和每一个Transcode都一一配队组合起来构建出一个DecodePath的列表,最后封装成LoadPath返回给DecodeJob。

本例中,Decoder负责将获取到的Data数据Decode成Bitmap,Transcoder负责将Bitmap转换成BitmapDrawable。

runLoadPath接下来的调用链如下:

graph LR
DecodeJob.runLoadPath --> LoadPath.load --> LoadPath.loadWithExceptionList --> DecodePath.decode

DecodePath.decode分成三步:

  • decode:将data解码成Resource。本例中将InputStream解码成Bitmap。
  • transform:对解码后的Resource进行变换(例如切圆角等),回调给DecodeJob执行。
  • transcode:对变换后的Resource进行转码。本例中将Bitmap转码成BitmapDrawable。

至此我们已经得到了最终的Resource了。回过头看回decodeFromRetrieveData方法。

private void decodeFromRetrievedData() {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey(
          "Retrieved data",
          startFetchTime,
          "data: "
              + currentData
              + ", cache key: "
              + currentSourceKey
              + ", fetcher: "
              + currentFetcher);
    }
    Resource<R> resource = null;
    try {
      //①
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);
    } catch (GlideException e) {
      e.setLoggingDetails(currentAttemptingKey, currentDataSource);
      throwables.add(e);
    }
    if (resource != null) {
      //②
      notifyEncodeAndRelease(resource, currentDataSource, isLoadingFromAlternateCacheKey);
    } else {
      runGenerators();
    }
  }
  • ①处代码即是上述解码过程。
  • ②处代码则是获取到最终的Resource,开始回调结果。

回调的顺序如下:

graph LR
DecodeJob.notifyEncodeAndRelease --> DecodeJob.notifyComplete --> EngineJob.onResourceReady --> Engine.onEngineJobComplete

EngineJob.onResourceReady --> SingleRequest.onResourceReady

Engine.onEngineJobComplete回调是通知Engine移除该Job的。

SingleRequest.onResourceReady是通知Resource获取成功以及传递Resource的。

继续传递:

graph LR
SingleRequest.onResourceReady --> ImageViewTarget.onResourceReady --> DrawableImageViewTarget.setResource

最后把资源回调到了DrawableImageViewTarget的setResource中。

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

view自然就是into传入的ImageView。

终于完成了!

结言

终于完成了对Glide的流程源码分析了。不得不说Glide真的是非常非常复杂。现在只是进行最简单的流程,也这么长。甚至还不惜跳过了缓存相关的东西,个人认为Glide的缓存实现是非常典型且具有教育意义的,但只能是以后有机会再继续这个Glide系列了。说实话深入研究Glide的话,势必还有很多东西需要探究,比如占位图实现、错误图实现、各种图片变换处理、不同的Data类型和不同的Resource类型的处理……任重道远啊!