Glide 加载图片流程和缓存机制(下)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


前言

上篇可能因为字数限制不能继续编辑了所以分为了两篇这是下篇


提示:以下是本篇文章正文内容

上篇说到 com.bumptech.glide.load.engine.DataCacheGenerator#startNext 这里会拿到多个
ModelLoader 第一个是 ByteBufferFileLoader 它的 DataFetcher 是 ByteBufferFetcher 看下它的 loadData

    public void loadData(@NonNull Priority priority,
        @NonNull DataCallback<? super ByteBuffer> callback) {
      ByteBuffer result;
      try {
        result = ByteBufferUtil.fromFile(file);
      } catch (IOException e) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
          Log.d(TAG, "Failed to obtain ByteBuffer for file", e);
        }
        callback.onLoadFailed(e);
        return;
      }

	  // 这里把文件读进来以后一步步回调出去	
      callback.onDataReady(result);
    }

上面会把文件读进来一步步回调出去路径是:com.bumptech.glide.load.engine.DataCacheGenerator#onDataReady -> com.bumptech.glide.load.engine.SourceGenerator#onDataFetcherReady -> com.bumptech.glide.load.engine.DecodeJob#onDataFetcherReady 看一下 onDataFetcherReady

  // DecodeJob
  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 {
      GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
      try {
        // 最后都会走到这里另一分支忽略
        decodeFromRetrievedData();
      } finally {
        GlideTrace.endSection();
      }
    }
  }

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

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

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

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

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

  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;
    // DecodePath 是对解码器、转换器的封装,这里有 3 个 DecodePath
    // DecodePath{ dataClass=class java.nio.DirectByteBuffer, decoders=[com.bumptech.glide.load.resource.gif.ByteBufferGifDecoder@5bcc337], transcoder=com.bumptech.glide.load.resource.transcode.UnitTranscoder@86410a4}
    // DecodePath{ dataClass=class java.nio.DirectByteBuffer, decoders=[com.bumptech.glide.load.resource.bitmap.ByteBufferBitmapDecoder@2853e0d, com.bumptech.glide.load.resource.bitmap.VideoDecoder@6b191c2], transcoder=com.bumptech.glide.load.resource.transcode.BitmapDrawableTranscoder@2105bd3}
    // DecodePath{ dataClass=class java.nio.DirectByteBuffer, decoders=[com.bumptech.glide.load.resource.bitmap.BitmapDrawableDecoder@5239310, com.bumptech.glide.load.resource.bitmap.BitmapDrawableDecoder@554ae09], transcoder=com.bumptech.glide.load.resource.transcode.UnitTranscoder@86410a4}
    // 其中 dataClass 处理的资源类型 decoders 是所有可用的解码器 transcoder 是转换器
    for (int i = 0, size = decodePaths.size(); i < size; i++) {
      DecodePath<Data, ResourceType, Transcode> path = decodePaths.get(i);
      try {
      	// 这里会使用第二个 DecodePath 之后会使用其中第一个解码器 ByteBufferBitmapDecoder 解码得到 Bitmap 最后再使用 BitmapDrawableTranscoder 将 Bitmap 转换为 Drawable 返回出去,因为默认需要拿到的是 Drawable.class
        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;
  }

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

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

  private Resource<ResourceType> decodeResourceWithList(
      DataRewinder<DataType> rewinder,
      int width,
      int height,
      @NonNull Options options,
      List<Throwable> exceptions)
      throws GlideException {
    Resource<ResourceType> result = null;
    //noinspection ForLoopReplaceableByForEach to improve perf
    for (int i = 0, size = decoders.size(); i < size; i++) {
      ResourceDecoder<DataType, ResourceType> decoder = decoders.get(i);
      try {
        DataType data = rewinder.rewindAndGet();
        // 这里的 decoder 就是上面说的 ByteBufferBitmapDecoder 先调用 handles 判断是否可以处理,如果返回 true 表示可以处理再调用解码方法去解码
        if (decoder.handles(data, options)) {
          data = rewinder.rewindAndGet();
          result = decoder.decode(data, width, height, options);
        }
        // Some decoders throw unexpectedly. If they do, we shouldn't fail the entire load path, but
        // instead log and continue. See #2406 for an example.
      } catch (IOException | RuntimeException | OutOfMemoryError e) {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
          Log.v(TAG, "Failed to decode data for " + decoder, e);
        }
        exceptions.add(e);
      }

      if (result != null) {
        break;
      }
    }

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

  // ByteBufferBitmapDecoder
  public boolean handles(@NonNull ByteBuffer source, @NonNull Options options) {
    return downsampler.handles(source);
  }

  // Downsampler 的 handles 直接返回 true 所以会调用 decode 去解码
  public boolean handles(@SuppressWarnings("unused") ByteBuffer byteBuffer) {
    // We expect downsampler to handle any available type Android supports.
    return true;
  }

  // ByteBufferBitmapDecoder
  public Resource<Bitmap> decode(@NonNull ByteBuffer source, int width, int height,
      @NonNull Options options)
      throws IOException {
    // 这里同样只是调用了 downsampler 的 decode
    // downsampler 类的注释翻译过来的意思是 '使用{@link BitmapFactory}根据 exif 方向向下采样、解码和旋转图像。'
    InputStream is = ByteBufferUtil.toStream(source);
    return downsampler.decode(is, width, height, options);
  }

  // Downsampler
  public Resource<Bitmap> decode(InputStream is, int outWidth, int outHeight,
      Options options) throws IOException {
    return decode(is, outWidth, outHeight, options, EMPTY_CALLBACKS);
  }

  public Resource<Bitmap> decode(InputStream is, int requestedWidth, int requestedHeight,
      Options options, DecodeCallbacks callbacks) throws IOException {
    Preconditions.checkArgument(is.markSupported(), "You must provide an InputStream that supports"
        + " mark()");

    byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
    BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
    bitmapFactoryOptions.inTempStorage = bytesForOptions;

    DecodeFormat decodeFormat = options.get(DECODE_FORMAT);
    DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);
    boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);
    boolean isHardwareConfigAllowed =
      options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);

    try {
      Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
          downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
          requestedHeight, fixBitmapToRequestedDimensions, callbacks);
      return BitmapResource.obtain(result, bitmapPool);
    } finally {
      releaseOptions(bitmapFactoryOptions);
      byteArrayPool.put(bytesForOptions);
    }
  }

下面就不再往下跟了,反正就是创建 BitmapFactory.Options 对象,对这个对象的各种属性赋值,比如采样率、Bitmap.Config(比如 JPEG 图片没有透明通道使用 Bitmap.Config.RGB_565)等还有根据 exif 方向旋转,最后依然是通过 BitmapFactory.decodeStream 来得到一个 Bitmap 对象

另外有一个可以说的是 inBitmap

在 Android 3.0(API 级别 11)开始,系统引入了 BitmapFactory.Options.inBitmap 字段。如果设置了此选项,那么采用 Options 对象的解码方法会在生成目标 Bitmap 时尝试复用 inBitmap,这意味着 inBitmap 的内存得到了重复使用,从而提高了性能,同时移除了内存分配和取消分配。不过 inBitmap 的使用方式存在某些限制,在 Android 4.4(API 级别 19)之前系统仅支持复用大小相同的位图,4.4 之后只要 inBitmap 的大小比目标 Bitmap 大即可

跟 inBitmap 相关的是 BitmapPool

通过上文我们知道了可以通过inBitmap复用内存,但是还需要一个地方存储可复用的Bitmap,这就是BitmapPool
JDK 中的 ThreadPoolExecutor 相信大多数开发者都很熟悉,我们一般将之称为“线程池”。池化是一个很常见的概念,其目的都是为了实现对象复用,例如 ThreadPoolExecutor 就实现了线程的复用机制
BitmapPool即实现了Bitmap的池化

以上两处引用内容来自参考&感谢二

Glide 中大量使用了缓存和对象池,只不过 Bitmap 可能是占用内存最多的对象,使用对象池的目的是为了避免频繁创建和回收内存触发 GC 造成卡顿

然后再回到 com.bumptech.glide.load.engine.DecodePath#decode (第二步) -> com.bumptech.glide.load.engine.DecodeJob.DecodeCallback#onResourceDecoded -> com.bumptech.glide.load.engine.DecodeJob#onResourceDecoded 这个方法里主要是判断是否需要缓存转换后的资源如果需要则初始化一些必要的对象,这里因为是加载网络资源默认不缓存资源文件所以不会初始化(下面的缓存相关逻辑会判断这里是否初始化了相关对象)

然后再回到 com.bumptech.glide.load.engine.DecodePath#decode (第三步)上面说了这里的是 BitmapDrawableTranscoder

  // BitmapDrawableTranscoder
  public Resource<BitmapDrawable> transcode(
      @NonNull Resource<Bitmap> toTranscode, @NonNull Options options) {
    return LazyBitmapDrawableResource.obtain(resources, toTranscode);
  }

然后返回一个懒加载的资源在 get 时才创建 BitmapDrawable 对象,然后再一步步返回直到
com.bumptech.glide.load.engine.DecodeJob#decodeFromRetrievedData

// com.bumptech.glide.load.engine.DecodeJob#notifyEncodeAndRelease
  private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
    if (resource instanceof Initializable) {
      ((Initializable) resource).initialize();
    }

    Resource<R> result = resource;
    LockedResource<R> lockedResource = null;
    // 不会执行
    if (deferredEncodeManager.hasResourceToEncode()) {
      lockedResource = LockedResource.obtain(resource);
      result = lockedResource;
    }

    notifyComplete(result, dataSource);

    stage = Stage.ENCODE;
    try {
      // 不执行
      if (deferredEncodeManager.hasResourceToEncode()) {
        deferredEncodeManager.encode(diskCacheProvider, options);
      }
    } finally {
      if (lockedResource != null) {
        lockedResource.unlock();
      }
    }
    // Call onEncodeComplete outside the finally block so that it's not called if the encode process
    // throws.
    onEncodeComplete();
  }

// com.bumptech.glide.load.engine.DecodeJob.DeferredEncodeManager#hasResourceToEncode
// 这里 toEncode == null 所以会返回 false
    boolean hasResourceToEncode() {
      return toEncode != null;
    }

看一下 notifyComplete

  // com.bumptech.glide.load.engine.DecodeJob#notifyComplete
  private void notifyComplete(Resource<R> resource, DataSource dataSource) {
    setNotifiedOrThrow();
    callback.onResourceReady(resource, dataSource);
  }
  
  // com.bumptech.glide.load.engine.EngineJob#onResourceReady
  // 这一块不同的版本源码不太一样
  public void onResourceReady(Resource<R> resource, DataSource dataSource) {
    this.resource = resource;
    this.dataSource = dataSource;
    MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
  }

  private static class MainThreadCallback implements Handler.Callback {

    @Synthetic
    @SuppressWarnings("WeakerAccess")
    MainThreadCallback() { }

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

  // com.bumptech.glide.load.engine.EngineJob#handleResultOnMainThread 
  void handleResultOnMainThread() {
    stateVerifier.throwIfRecycled();
    if (isCancelled) {
      resource.recycle();
      release(false /*isRemovedFromQueue*/);
      return;
    } else if (cbs.isEmpty()) {
      throw new IllegalStateException("Received a resource without any callbacks to notify");
    } else if (hasResource) {
      throw new IllegalStateException("Already have resource");
    }
    engineResource = engineResourceFactory.build(resource, isCacheable);
    hasResource = true;

    // Hold on to resource for duration of request so we don't recycle it in the middle of
    // notifying if it synchronously released by one of the callbacks.
    engineResource.acquire();
    // 缓存
    listener.onEngineJobComplete(this, key, engineResource);

    // 上文提到过如果同样请求正在执行则加入到回调列表中
    for (int i = 0, size = cbs.size(); i < size; i++) {
      ResourceCallback cb = cbs.get(i);
      if (!isInIgnoredCallbacks(cb)) {
        // 每回调一个则引用计数加一
        engineResource.acquire();
        // 这里的 cb 就是 SingleRequest
        cb.onResourceReady(engineResource, dataSource);
      }
    }
    // Our request is complete, so we can release the resource.
    engineResource.release();

    release(false /*isRemovedFromQueue*/);
  }

  // com.bumptech.glide.load.engine.Engine#onEngineJobComplete
  public void onEngineJobComplete(EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
    Util.assertMainThread();
    // A null resource indicates that the load failed, usually due to an exception.
    if (resource != null) {
      resource.setResourceListener(key, this);

      if (resource.isCacheable()) {
        // 加入到内存中(活动缓存)
        activeResources.activate(key, resource);
      }
    }

    jobs.removeIfCurrent(key, engineJob);
  }

  // com.bumptech.glide.request.SingleRequest#onResourceReady(com.bumptech.glide.load.engine.Resource<?>, com.bumptech.glide.load.DataSource)
  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);
  }

  // com.bumptech.glide.request.SingleRequest#onResourceReady(com.bumptech.glide.load.engine.Resource<R>, R, com.bumptech.glide.load.DataSource)
  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 {
      boolean anyListenerHandledUpdatingTarget = false;
      if (requestListeners != null) {
        for (RequestListener<R> listener : requestListeners) {
          anyListenerHandledUpdatingTarget |=
              listener.onResourceReady(result, model, target, dataSource, isFirstResource);
        }
      }
      anyListenerHandledUpdatingTarget |=
          targetListener != null
              && targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);

      if (!anyListenerHandledUpdatingTarget) {
        Transition<? super R> animation =
            animationFactory.build(dataSource, isFirstResource);
        // 这里的 target 是上文提到过的 com.bumptech.glide.request.target.DrawableImageViewTarget 
        target.onResourceReady(result, animation);
      }
    } finally {
      isCallingCallbacks = false;
    }

    notifyLoadSuccess();
  }

  // com.bumptech.glide.request.target.ImageViewTarget#onResourceReady
  public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
    if (transition == null || !transition.transition(resource, this)) {
      setResourceInternal(resource);
    } else {
      maybeUpdateAnimatable(resource);
    }
  }

  // com.bumptech.glide.request.target.ImageViewTarget#setResourceInternal
  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);
  }

  // com.bumptech.glide.request.target.DrawableImageViewTarget#setResource
  // 最终通过 setImageDrawable 设置到 ImageView 上整个流程就结束了
  protected void setResource(@Nullable Drawable resource) {
    view.setImageDrawable(resource);
  }

当在其他位置再次请求这个资源时在 com.bumptech.glide.load.engine.Engine#load 中内存缓存中就会命中这个资源直接返回不会再执行网络请求了。如果杀死 App 再次进入请求这个资源就会命中磁盘缓存再次执行上面说的 DataCacheGenerator 同样不会再次执行网络请求

Glide 之所以使用内存缓存的原因是:防止应用重复将图片读入到内存,造成内存资源浪费。
之所以使用磁盘缓存的原因是:防止应用重复的从网络或者其他地方下载和读取数据。
正式因为有着这两种缓存的结合,才构成了Glide极佳的缓存效果。

以上两处引用内容来自参考&感谢二

参考&感谢

Glide 中文文档
【带着问题学】Glide做了哪些优化?
Android 图片加载框架 Glide 4.9.0 (一) 从源码的角度分析 Glide 执行流程
面试官:简历上最好不要写Glide,不是问源码那么简单
Glide源码分析之数据拉取
Glide最全解析

学习 Glide 源码的过程持续了好几个月期间看过几十篇相关文章不再一一列举

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值