Android Glide4.0 源码遨游记(第四集)

 


Glide

 

前言

Android中加载图片的形式有很多种,网上也有很多的知名图片加载库,例如Glide、Picasso、Fresco等等,它们为我们带来的方便就不需再多言了,无论是从加载到缓存还是占位图等等都提供了简易的Api,且实现强大的功能。本系列只针对Glide4.0版本源码进行分析,提高自身阅读源码的能力,同时也是为了了解其中加载的流程以及缓存的原理,本文尽可能地截图说明结合源码解析,如有疏忽之处,还请指教。

关于作者

一个在奋斗路上的Android小生,欢迎关注,互相交流
GitHubGitHub-ZJYWidget
CSDN博客IT_ZJYANG
简 书Android小Y


 

前情回顾

上一集已经分析了Glide中into对于图片状态的展示处理和准备,已经了解到它的失败图和占位图等机制是怎样来的,ImageViewTarget进行最终的展示,还留下了个疑问,加载图片资源的过程是怎样的?

 

剧情(Glide into 大展宏图)

接上集结尾:

status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
  onSizeReady(overrideWidth, overrideHeight);
} else {
  target.getSize(this);
}

可以看到这段是处于WAITING_FOR_SIZE的状态,所以可以大概猜到是跟图片宽高有关,首先检验Util.isValidDimensions(overrideWidth, overrideHeight)

/**
 * Returns true if width and height are both > 0 and/or equal to {@link Target#SIZE_ORIGINAL}.
 */
public static boolean isValidDimensions(int width, int height) {
    return isValidDimension(width) && isValidDimension(height);
}
private static boolean isValidDimension(int dimen) {
    return dimen > 0 || dimen == Target.SIZE_ORIGINAL;
}

其实就是判断我们是否有调用requestOptions.override(int width, int height)来指定宽高,有的话就走onSizeReady路线,没有指定的话就走target.getSize(this)。我们分别跟进下:

1.onSizeReady

@Override
public void onSizeReady(int width, int height) {
    stateVerifier.throwIfRecycled();
    if (IS_VERBOSE_LOGGABLE) {
      logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
    if (status != Status.WAITING_FOR_SIZE) {
      return;
    }
    status = Status.RUNNING;

    float sizeMultiplier = requestOptions.getSizeMultiplier();
    this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
    this.height = maybeApplySizeMultiplier(height, sizeMultiplier);

    if (IS_VERBOSE_LOGGABLE) {
      logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
    }
    loadStatus = engine.load(
        glideContext,
        model,
        requestOptions.getSignature(),
        this.width,
        this.height,
        requestOptions.getResourceClass(),
        transcodeClass,
        priority,
        requestOptions.getDiskCacheStrategy(),
        requestOptions.getTransformations(),
        requestOptions.isTransformationRequired(),
        requestOptions.isScaleOnlyOrNoTransform(),
        requestOptions.getOptions(),
        requestOptions.isMemoryCacheable(),
        requestOptions.getUseUnlimitedSourceGeneratorsPool(),
        requestOptions.getUseAnimationPool(),
        requestOptions.getOnlyRetrieveFromCache(),
        this);

    // This is a hack that's only useful for testing right now where loads complete synchronously
    // even though under any executor running on any thread but the main thread, the load would
    // have completed asynchronously.
    if (status != Status.RUNNING) {
      loadStatus = null;
    }
    if (IS_VERBOSE_LOGGABLE) {
      logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
}

首先根据我们通过RequestOptions#sizeMultiplier设置的sizeMultiplier,它相当于图片的一个缩放系数,来计算出相应的宽高。然后接着调用了Engine的load方法:

public <R> LoadStatus load(
      GlideContext glideContext,
      Object model,
      Key signature,
      int width,
      int height,
      Class<?> resourceClass,
      Class<R> transcodeClass,
      Priority priority,
      DiskCacheStrategy diskCacheStrategy,
      Map<Class<?>, Transformation<?>> transformations,
      boolean isTransformationRequired,
      boolean isScaleOnlyOrNoTransform,
      Options options,
      boolean isMemoryCacheable,
      boolean useUnlimitedSourceExecutorPool,
      boolean useAnimationPool,
      boolean onlyRetrieveFromCache,
      ResourceCallback cb) {
    Util.assertMainThread();
    long startTime = LogTime.getLogTime();

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

    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return null;
    }

    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return null;
    }

    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
      current.addCallback(cb);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }

    EngineJob<R> engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);

    DecodeJob<R> decodeJob =
        decodeJobFactory.build(
            glideContext,
            model,
            key,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            onlyRetrieveFromCache,
            options,
            engineJob);

    jobs.put(key, engineJob);

    engineJob.addCallback(cb);
    engineJob.start(decodeJob);

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
}

代码有点长,首先它建立了一个EngineKey,而EngineKey其实就是一个简单的bean类(这里就不贴它代码出来了),里面重写了equals方法:

@Override
public boolean equals(Object o) {
    if (o instanceof EngineKey) {
      EngineKey other = (EngineKey) o;
      return model.equals(other.model)
          && signature.equals(other.signature)
          && height == other.height
          && width == other.width
          && transformations.equals(other.transformations)
          && resourceClass.equals(other.resourceClass)
          && transcodeClass.equals(other.transcodeClass)
          && options.equals(other.options);
    }
    return false;
}

所以大概能猜到它是用来代表我们此次请求的一个类似Key的东西(到时候缓存机制会用到,后文再讲)。
接着往下看,可以看到有几组判断,这些都是Glide的缓存机制的核心,由于缓存机制也是Glide的一个精华,所以单独抽取出来分析,这里就先继续往下走(假设现在没拿到缓存,发起一个新的请求),可以看到新建了一个EngineJob对象和DecodeJob对象。DecodeJob实现了Runnable,EngineJob相当于一个管理者,管理线程的调度,而DecodeJob就是真正加载图片的线程,可以看到最后调用了engineJob.start(decodeJob);,因此我们去看下DecodeJob的run方法里做了什么:

@Override
public void run() {
    TraceCompat.beginSection("DecodeJob#run");
    DataFetcher<?> localFetcher = currentFetcher;
    try {
      if (isCancelled) {
        notifyFailed();
        return;
      }
      runWrapped();
    } catch (Throwable t) {
      if (stage != Stage.ENCODE) {
        throwables.add(t);
        notifyFailed();
      }
      if (!isCancelled) {
        throw t;
      }
    } finally {
      if (localFetcher != null) {
        localFetcher.cleanup();
      }
      TraceCompat.endSection();
    }
}

可以看到,在try{}代码块里,首先判断是否中途被Cancel,如果被取消了就直接展示加载失败,否则就走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);
    }
}

首先看第一个分支,先是调用了getNextStage方法,这个方法只是记录和控制当前加载的一个过程,如果你设置了onlyRetrieveFromCache为true的话,就会停止此次请求。接着通过currentGenerator = getNextGenerator();生成一个DataFetcherGenerator对象:

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);
    }
}

可以看到,这里有三个不同的数据解析生成器,我们直接以全新数据源的情况开始看,即SOURCE类型,创建了一个SourceGenerator,然后调用runGenerators,我们看下runGenerators:

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;
      }
    }
    // We've run out of stages and generators, give up.
    if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
      notifyFailed();
    }

    // Otherwise a generator started a new load and we expect to be called back in
    // onDataFetcherReady.
}

刚才生成的Generator正是这里要用到,可以看到while里面调用到了currentGenerator.startNext(),跟进去看看:

SourceGenerator#startNext

可以看到这里调用了DataFetcher的loadData方法,DataFetcher是一个接口,它主要定义了各种资源取数据的接口,可以看到它有很多个实现类:FileFetcher、FilePathFetcher、AssetPathFetcher、HttpUrlFetcher等等,一看名字就可以猜到是对应各种类型的资源取数据的工具,这里以HttpUrlFetcher为例,看它的loadData方法的实现:

@Override
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.onDataReady(result);
    } catch (IOException e) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Failed to load data for url", e);
      }
      callback.onLoadFailed(e);
    } finally {
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
      }
    }
}

可以看到里面核心是loadDataWithRedirects,我们接着跟进去看:

private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
      Map<String, String> headers) throws IOException {
    if (redirects >= MAXIMUM_REDIRECTS) {
      throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
    } else {
      // Comparing the URLs using .equals performs additional network I/O and is generally broken.
      // See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
      try {
        if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
          throw new HttpException("In re-direct loop");

        }
      } catch (URISyntaxException e) {
        // Do nothing, this is best effort.
      }
    }

    urlConnection = connectionFactory.build(url);
    for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
      urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
    }
    urlConnection.setConnectTimeout(timeout);
    urlConnection.setReadTimeout(timeout);
    urlConnection.setUseCaches(false);
    urlConnection.setDoInput(true);

    // Stop the urlConnection instance of HttpUrlConnection from following redirects so that
    // redirects will be handled by recursive calls to this method, loadDataWithRedirects.
    urlConnection.setInstanceFollowRedirects(false);

    // Connect explicitly to avoid errors in decoders if connection fails.
    urlConnection.connect();
    // Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352.
    stream = urlConnection.getInputStream();
    if (isCancelled) {
      return null;
    }
    final int statusCode = urlConnection.getResponseCode();
    if (isHttpOk(statusCode)) {
      return getStreamForSuccessfulRequest(urlConnection);
    } else if (isHttpRedirect(statusCode)) {
      String redirectUrlString = urlConnection.getHeaderField("Location");
      if (TextUtils.isEmpty(redirectUrlString)) {
        throw new HttpException("Received empty or null redirect url");
      }
      URL redirectUrl = new URL(url, redirectUrlString);
      // Closing the stream specifically is required to avoid leaking ResponseBodys in addition
      // to disconnecting the url connection below. See #2352.
      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);
    }
}

看到了熟悉的HttpUrlConnection有木有!!这里面的代码就不那么陌生啦,主要就是给HttpUrlConnection设置请求参数并且发起请求。对于请求结果,做了如下操作:

HttpUrlFetcher请求成功

拿到了请求到的InputStream并且return回去,那么我们看回去刚才的loadData

回调请求结果

这个callback监听是loadData传进来的,那我们看回刚才的SourceGenerator

Generator监听.png

可以看到传进去的是SourceGenerator本身的实例,那么SourceGenerator肯定也实现了Callback的onDataReady接口,我们看下:

@Override
public void onDataReady(Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
      dataToCache = data;
      // We might be being called back on someone else's thread. Before doing anything, we should
      // reschedule to get back onto Glide's thread.
      cb.reschedule();
    } else {
      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
          loadData.fetcher.getDataSource(), originalKey);
    }
}

由于刚才我们一直假设的是没有缓存的情况下,所以这里走else分支,即onDataFetcherReady方法:

@Override
  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;
    if (Thread.currentThread() != currentThread) {
      runReason = RunReason.DECODE_DATA;
      callback.reschedule(this);
    } else {
      TraceCompat.beginSection("DecodeJob.decodeFromRetrievedData");
      try {
        decodeFromRetrievedData();
      } finally {
        TraceCompat.endSection();
      }
    }
  }

如果跟runGenerator不在同个线程下,就执行callback.reschedule(this);,进而执行了Engine的reschedule方法(且携带状态为DECODE_DATA):

@Override
public void reschedule(DecodeJob<?> job) {
    // Even if the job is cancelled here, it still needs to be scheduled so that it can clean itself
    // up.
    getActiveSourceExecutor().execute(job);
}

可以看到又是执行了DecodeJob,又会回到DecodeJob的run方法,又回到我们刚才DecodeJob里面判断状态的地方:

DecodeJob#runWrapped

所以说判断线程只是为了切换线程,最终其实都是调用的decodeFromRetrievedData,那我们看下decodeFromRetrievedData(Ps:前方会有很多层递进,先大概浏览一遍,我们目标是看到最后解析的地方):

private void decodeFromRetrievedData() {
    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);
    } else {
      runGenerators();
    }
}

可以看到先通过decodeFromData获取到了资源,然后再notify通知解码和释放资源。我们跟进去decodeFromData看看:

private <Data> Resource<R> decodeFromData(DataFetcher<?> fetcher, Data data,
      DataSource dataSource) throws GlideException {
    try {
      if (data == null) {
        return null;
      }
      long startTime = LogTime.getLogTime();
      Resource<R> result = decodeFromFetcher(data, dataSource);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Decoded result " + result, startTime);
      }
      return result;
    } finally {
      fetcher.cleanup();
    }
}

进一步跟进到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);
}

进一步跟进到runLoadPath

private <Data, ResourceType> Resource<R> runLoadPath(Data data, DataSource dataSource,
      LoadPath<Data, ResourceType, R> path) throws GlideException {
    Options options = getOptionsWithHardwareConfig(dataSource);
    DataRewinder<Data> rewinder = glideContext.getRegistry().getRewinder(data);
    try {
      // ResourceType in DecodeCallback below is required for compilation to work with gradle.
      return path.load(
          rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));
    } finally {
      rewinder.cleanup();
    }
}

进一步跟进到path.load(rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));

public Resource<Transcode> load(DataRewinder<Data> rewinder, @NonNull Options options, int width,
      int height, DecodePath.DecodeCallback<ResourceType> decodeCallback) throws GlideException {
    List<Throwable> throwables = Preconditions.checkNotNull(listPool.acquire());
    try {
      return loadWithExceptionList(rewinder, options, width, height, decodeCallback, throwables);
    } finally {
      listPool.release(throwables);
    }
 }

还不能停= =。,进一步跟进到loadWithExceptionList

private Resource<Transcode> loadWithExceptionList(DataRewinder<Data> rewinder,
      @NonNull Options options,
      int width, int height, DecodePath.DecodeCallback<ResourceType> decodeCallback,
      List<Throwable> exceptions) throws GlideException {
    Resource<Transcode> result = null;
    //noinspection ForLoopReplaceableByForEach to improve perf
    for (int i = 0, size = decodePaths.size(); i < size; i++) {
      DecodePath<Data, ResourceType, Transcode> path = decodePaths.get(i);
      try {
        result = path.decode(rewinder, width, height, options, decodeCallback);
      } catch (GlideException e) {
        exceptions.add(e);
      }
      if (result != null) {
        break;
      }
    }

    if (result == null) {
      throw new GlideException(failureMessage, new ArrayList<>(exceptions));
    }

    return result;
}

继续,跟进到path.decode(rewinder, width, height, options, decodeCallback);

public Resource<Transcode> decode(DataRewinder<DataType> rewinder, int width, int height,
      @NonNull Options options, DecodeCallback<ResourceType> callback) throws GlideException {
    Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
    Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
    return transcoder.transcode(transformed, options);
}

这里暂且忽略transformed,先看Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);

@NonNull
private Resource<ResourceType> decodeResource(DataRewinder<DataType> rewinder, int width,
      int height, @NonNull Options options) throws GlideException {
    List<Throwable> exceptions = Preconditions.checkNotNull(listPool.acquire());
    try {
      return decodeResourceWithList(rewinder, width, height, options, exceptions);
    } finally {
      listPool.release(exceptions);
    }
}

跟进decodeResourceWithList(rewinder, width, height, options, exceptions);

ResourceDecoder#decode

跟进了这么多层,可以看到最终调用了ResourceDecoder的decode接口,而ResourceDecoder有很多具体实现类,例如FileDecoder、ResourceDrawableDecoder、StreamBitmapDecoder等等,同样是对应各种资源类型数据的解析,我们之前是举了HttpUrl的例子并返回InputStream,那么此处应该调用的是StreamBitmapDecoder,将InputStream转化为了Bitmap对象。
到这里终于将请求结果转换为了ImageView可以展示的对象了,那么接下来就是如何将这个结果传递给ImageView展示。还记得我们刚才那一连串跟进的入口decodeFromRetrievedData吗,我们将结果一层一层返回出去,最终会回到那里:

解析完之后通知

这个时候resource已经返回来了,不等于null了,就会走notifyEncodeAndRelease ,那我们看下 notifyEncodeAndRelease方法 :

notifyEncodeAndRelease

可以看到这里面调用了notifyComplete,再看看notifyComplete:

private void notifyComplete(Resource<R> resource, DataSource dataSource) {
    setNotifiedOrThrow();
    callback.onResourceReady(resource, dataSource);
}

这里面调用了callback.onResourceReady(resource, dataSource);,这个callback是什么呢?在DecodeJob中追朔一下可以看到是init的时候传进来的,那么init又是谁调用的呢?我们查一下:

Engine调用DecodeJob的init

可以看到,是Engine调用的init,我们追过去Engine看一下,可以看到是DecodeJobFactory的build生成DecodeJob时调用的,这就回到我们刚才Engine生成EngineJob和DecodeJob的地方了:

DecodeJob生成时传进去的callback

可以看到传的正是EngineJob,那么EngineJob里面肯定有实现Callback接口:

EngineJob实现DecodeJob接口

所以就看下它的onResourceReady方法做了什么:

@Override
  public void onResourceReady(Resource<R> resource, DataSource dataSource) {
    this.resource = resource;
    this.dataSource = dataSource;
    MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
}

可以看到用Handler发出了一个MSG_COMPLETE消息,我们看下它接受MSG_COMPLETE的地方:

@Override
public boolean handleMessage(Message message) {
      EngineJob<?> job = (EngineJob<?>) message.obj;
      switch (message.what) {
        case MSG_COMPLETE:
          job.handleResultOnMainThread();
          break;
        case MSG_EXCEPTION:
          job.handleExceptionOnMainThread();
          break;
        case MSG_CANCELLED:
          job.handleCancelledOnMainThread();
          break;
        default:
          throw new IllegalStateException("Unrecognized message: " + message.what);
      }
      return true;
}

调用了handleResultOnMainThread,(看这名字,是不是感觉离展示越来越近了),继续跟进handlerResultOnMainThread:

handleResultOnMainThread

可以看到调用了ResourceCallbackonResourceReady,ResourceCallback是个接口,那么我们找它的实现,看到了一个熟悉的名字——SingleRequest,我们看SingleRequest的onResourceReady实现做了什么:

public void onResourceReady(Resource<?> resource, DataSource dataSource) {
    stateVerifier.throwIfRecycled();
    loadStatus = null;
    if (resource == null) {
      GlideException exception = new GlideException("Expected to receive a Resource<R> with an "
          + "object of " + transcodeClass + " inside, but instead got null.");
      onLoadFailed(exception);
      return;
    }

    Object received = resource.get();
    if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
      releaseResource(resource);
      GlideException exception = new GlideException("Expected to receive an object of "
          + transcodeClass + " but instead" + " got "
          + (received != null ? received.getClass() : "") + "{" + received + "} inside" + " "
          + "Resource{" + resource + "}."
          + (received != null ? "" : " " + "To indicate failure return a null Resource "
          + "object, rather than a Resource object containing null data."));
      onLoadFailed(exception);
      return;
    }

    if (!canSetResource()) {
      releaseResource(resource);
      // We can't put the status to complete before asking canSetResource().
      status = Status.COMPLETE;
      return;
    }

    onResourceReady((Resource<R>) resource, (R) received, dataSource);
}

可以看到方法最后又调用了另一个onResourceReady,跟进去看看:

onResourceReady传递数据给Target

看到了更加亲切的Target有木有~ 之前我们讲过的Target是用来展示图片的,到这里终于开始将数据传会给我们的Target了,我们返回去看看ImageViewTarget的onResourceReady:

@Override
public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
    if (transition == null || !transition.transition(resource, this)) {
      setResourceInternal(resource);
    } else {
      maybeUpdateAnimatable(resource);
    }
}

调用了setResourceInternal(resource)

private void setResourceInternal(@Nullable Z resource) {
    // Order matters here. Set the resource first to make sure that the Drawable has a valid and
    // non-null Callback before starting it.
    setResource(resource);
    maybeUpdateAnimatable(resource);
}

看到了setResource,这不正是我们之前说过的用来展示图片的setResource嘛!它有好几个子类(其中就有我们之前提到的DrawableImageViewTarget和BitmapImageViewTarget),我们拿其中一个看看它的setReource:

DrawableImageViewTarget#setResource

我的天!老泪纵横,看到了我们最熟悉的setImageDrawable,到这里就将图片展示出来了~

 

片尾

Glide的into里面做的事情远远超乎我们想象,表面只是传了个ImageView进去,里面实则帮我们做足了准备,各种资源的解析(比如网络请求、加载本地文件、读取Asset文件等多种解析方向),线程的切换,最终的展示。再来理一遍流程:

into里面分为三个步骤:1.获取数据 2.解析数据 3.结果展示。

获取数据:首先Glide会根据当前用户是否有通过override来重新指定加载的宽高,有的话就使用指定的宽高,没有的话就会读取ImageView的宽高,然后调用了Engine的load方法,建立了两个关键的对象:EngineJob和DecodeJob,EngineJob负责调度线程,DecodeJob负责请求解析数据,DecodeJob会生成一个DataFetcherGenerator,它负责生成对应的DataFetcher(这里的对应是Glide初始化的时候就已经匹配好了,什么资源需要对应什么解析器),找到解析器之后会调用其具体的loadData方法,获取图片数据。
解析数据:拿到数据之后就先通过EngineJob切换线程,保证在同一线程下,然后通过ResourceDecoder的decode接口,将我们的数据转换为ImageView可以展示的类型(比如Bitmap、Drawable)。
结果展示:将其回调回去,并通过EngineJob将线程切换为主线程,为展示做准备,结果会一直回调到Request,然后由Request通知Target可以准备展示了,最后Target收到通知拿到数据将其展示出来。

 


至此,我们将Glide对图片的请求和展示流程已经分析完毕,这只是最基本的主流程,Glide还在其中的缓存上做了大手脚,我们下回分解。

 

下集预告(Glide 缓存 有备无患)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容