IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Glide源码分析——资源加载和显示 -> 正文阅读

[移动开发]Glide源码分析——资源加载和显示

上一篇文章中讲到Glide是如何通过RequestManager来管理资源加载请求,这篇文章就来看下资源加载请求执行和资源加载完成后显示的具体过程

《Glide源码分析——Request管理》

《Glide源码分析——开篇》

资源获取

资源获取的整体流程如下:
在这里插入图片描述

整个过程可概括为:

  • 创建RequestBuilder,然后根据要显示在的目标和配置创建获取资源的请求(Request)
  • 运行资源获取请求

创建获取资源请求

以RequestBuilder.into(ImageView imageView)为例,看下Reqeust创建的过程

@NonNull
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
  Util.assertMainThread();
  Preconditions.checkNotNull(view);

  BaseRequestOptions<?> requestOptions = this;
  if (!requestOptions.isTransformationSet()
      && requestOptions.isTransformationAllowed()
      && view.getScaleType() != null) {
    // Clone in this method so that if we use this RequestBuilder to load into a View and then
    // into a different target, we don't retain the transformation applied based on the previous
    // View's scale type.
    //
    switch (view.getScaleType()) {
      case CENTER_CROP:
        requestOptions = requestOptions.clone().optionalCenterCrop();
        break;
      case CENTER_INSIDE:
        requestOptions = requestOptions.clone().optionalCenterInside();
        break;
      case FIT_CENTER:
      case FIT_START:
      case FIT_END:
        requestOptions = requestOptions.clone().optionalFitCenter();
        break;
      case FIT_XY:
        requestOptions = requestOptions.clone().optionalCenterInside();
        break;
      case CENTER:
      case MATRIX:
      default:
        // Do nothing.
    }
  }

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


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()");
  }
  //创建Request接口对象
  Request request = buildRequest(target, targetListener, options, callbackExecutor);

  Request previous = target.getRequest();
  //判断新的请求和Target的当前请求是否相等,如果相等并且设置了内存缓存配置或者当前请求未完成,
  //则判断当前请求是否在运行,如果没有运行则开始运行。
  if (request.isEquivalentTo(previous)
      && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
    if (!Preconditions.checkNotNull(previous).isRunning()) {
      previous.begin();
    }
    return target;
  }

  //如果上述if语句条件判断不成立,则通过RequestManager来运行请求。
  requestManager.clear(target);
  target.setRequest(request);
  requestManager.track(target, request);

  return target;
}

Request对象的创建是在buildRequest(target, targetListener, options, callbackExecutor)完成的,这里不去追究具体的实现细节,主要理一下这个过程的逻辑,在将具体逻辑之前,我们先看下Request相关的类图
在这里插入图片描述
有关资源加载的几个类说明如下

  • Target:Glide加载资源后所显示位置的接口,最常见实现类是ImageViewTarget(持有ImageView对象引用)。Target的生命周期方法包括:onLoadStarted()、onResourceReady()、onLoadCleared()、onLoadFailed()。通常调用过程是: onLoadStarted -> onResourceReady/onLoadFailed -> onLoadCleared,但也不一定每次都如此,比如当资源在内存或者资源加载失败时onLoadStarted()就不会被调用。

  • Request :一个定义了资源请求的执行、停止、取消以及请求状态等方法的接口。它的实现类有SingleRequest、ErrorRequestCoordinator和ThumbnailRequestCoordinator。

  • RequestCoordinator:管理具有相同Target的多个Request的接口。

  • ThumbnailRequestCoordinator:同时实现了Request和RequestCoordinator接口,它用于管理两个独立的Request,其中一个是加载缩略图的请求,另一个是加载全尺寸的图片的请求,而这两个请求都是SingleRequest对象。

  • ErrorRequestCoordinator:同样会管理两个Request,其中一个Request是图片加载请求(可能是SingleRequest对象也可能是ThumbnailRequestCoordinator对象),我们把它称为"主请求";另一个是当主请求加载失败时触发的请求,我们把它称为"容错请求"

创建Reuest逻辑如下:

  1. 判断是否有配置容错请求,如果有则创建ErrorRequestCorrdinatord对象errorRequestCoordinator,并把该对象赋值给变量parentCoordinator
  2. 创建主请求,如果有配置缩略图请求,则创建ThumbnailRequestCoordinator对象,并创建全尺寸和缩略图加载请求(都是SingleRequest对象),然后返回ThumbnailRequestCoordinator对象;如果没有配置缩略图请求,则创建加载全尺寸图片请求(SingleRequest对象),并返回该请求对象。
  3. 如果没有配置容错请求,则返回第二步创建的主请求。如果有,则创建容错请求对象,并将容错请求和主请求分别赋值给ErrorRequestCoordinator.errorErrorRequestCoordinator.primary

配置容错请求的式:

Glide.with(this)
        .load(main_url)
        //通过RequestBuilder.error()方法配置容错请求
        .error(Glide.with(this).
                load(backup_url))
        .placeholder(R.drawable.placeholder)
        .fitCenter()
        .into(mImvTest);

配置缩略图请求的方式:

Glide.with(this)
        .load(main_url)
        //配置缩略图请求
        .thumbnail(Glide.with(this).
                load(thumb_url))
        .placeholder(R.drawable.placeholder)
        .fitCenter()
        .into(mImvTest);

Request创建情况

Request创建分为以下几种情况,其中顶层的节点为起始节点,也就是说Request执行的时候会以顶层的节点开始执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2vir2WOj-1641989986527)(./create_request_cases.png)]

a 没有配置缩略图和容错请求,对应的代码如下

Glide.with(this)
        .load(main_url)
        .placeholder(R.drawable.placeholder)
        .fitCenter()
        .into(mImvTest);

b 配置了缩略图请求,没有配置容错请求,对应代码如下

Glide.with(this)
        .load(main_url)
        //配置缩略图请求
        .thumbnail(Glide.with(this).
                load(thumb_url))
        .placeholder(R.drawable.placeholder)
        .fitCenter()
        .into(mImvTest);

c 配置了容错请求,没有配置缩略图请求,对应代码如下

Glide.with(this)
        .load(main_url)
        //通过RequestBuilder.error()方法配置容错请求
        .error(Glide.with(this).
                load(backup_url))
        .placeholder(R.drawable.placeholder)
        .fitCenter()
        .into(mImvTest);

d 配置了容错请求和缩略图请求,对应代码如下

Glide.with(this)
        .load(main_url)
        //通过RequestBuilder.error()方法设置主请求失败时要加载的内容
        .error(Glide.with(this).
                load(backup_url))
        //配置缩略图加载请求
        .thumbnail(Glide.with(this).
                load(thumb_url))
        .placeholder(R.drawable.placeholder)
        .fitCenter()
        .into(mImvTest);

运行资源获取请求

?在了解图片获取请求创建和类型之后,我们再看下执行图片加载请求的过程。对于不同类型的Request,执行的过程略有差异,比如ErrorRequestCorrdinatord会执行主请求,如果主请求执行失败则执行容错请求。因为实际的请求最终对应的都是SingleRequest对象,所以我们以最简单的情况(Request创建中的a情况)为例,看下执行图片加载请求的过程。从SingleRequest.begin()方法开始执行资源加载请求,然后执行到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,
    Executor callbackExecutor) {
  long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;

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

  EngineResource<?> memoryResource;
  synchronized (this) {
    memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);

    if (memoryResource == null) {
      //从磁盘或者原始地址加载资源
      return waitForExistingOrStartNewJob(
          glideContext,
          model,
          signature,
          width,
          height,
          resourceClass,
          transcodeClass,
          priority,
          diskCacheStrategy,
          transformations,
          isTransformationRequired,
          isScaleOnlyOrNoTransform,
          options,
          isMemoryCacheable,
          useUnlimitedSourceExecutorPool,
          useAnimationPool,
          onlyRetrieveFromCache,
          cb,
          callbackExecutor,
          key,
          startTime);
    }
  }

  //如果memoryResource不为null,则通过回调方法通知资源加载完成
  cb.onResourceReady(
      memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
  return null;
}

资源加载的逻辑如下:

  • 首先生成EngineKey,然后用该key在内存缓存中查找图片资源,而这个过程又分为两个步骤

    • 先从ActiveResource(活动资源,表示资源在被另一个 View 使用)中查找,如果找到则使用找到的资源;

    • 如果没找到,则从LRU内存缓存(曾经被加载,暂时没被使用到)中查找,如果有找到则使用找到的资源;

  • 如果从内存中没有找到,则从磁盘缓存中查找;

  • 如果在磁盘缓存中没有找到,则根据资源文件对应的地址加载资源。

从内存缓存中获取图片资源

@Nullable
private EngineResource<?> loadFromMemory(
    EngineKey key, boolean isMemoryCacheable, long startTime) {
  //判断是否有配置不使用内存缓存,如果不使用则返回null。
  if (!isMemoryCacheable) {
    return null;
  }
  //从活动缓存中查找,如果有找到则返回资源
  EngineResource<?> active = loadFromActiveResources(key);
  if (active != null) {
    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Loaded resource from active resources", startTime, key);
    }
    return active;
  }
  //从缓存中查找,如果有找到则将资源存入活动资源中然后返回资源,否则返回null
  EngineResource<?> cached = loadFromCache(key);
  if (cached != null) {
    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Loaded resource from cache", startTime, key);
    }
    return cached;
  }

  return null;
}

从内存中查找资源分为两种情况:一种是活动资源中查找;另一种是内存缓存中查找。先说第一种,活动资源实际上通过一个Map保存资源的弱引用,如果发生GC,所保存的资源会被回收。第二种内存缓存是一种采用LRU算法的缓存,通过LinkedHaspMap来保存资源。关于LRU算法,建议做下LeetCode上的LRU算法题,自己动手实现一遍。

从磁盘或者原始地址获取资源

如果在内存没有加载得到资源的话,会先从磁盘中获取,如果磁盘中没有加载到的话就则从资源的原始地址获取(网络、Assets、文件等)。在讲具体获取过程前,我们先来看下加载过程中涉及到的类,他们的关系图如下:
在这里插入图片描述

Engine.waitForExistingOrStartNewJob()方法会执行一个DecodeJob任务,在该任务中实现从磁盘或者原始地址加载资源。看下几个关键的类和接口定义:

  • DecodeJob: 负责从缓存数据或原始地址获取资源然后解码。可以把它理解为一个加载资源的任务。

  • DecodeJob.RunReason: 定义了DeocdeJob被执行的原因,有初始状态(INITIALIZE)、从磁盘切换到原始资源(SWITCH_TO_SOURCE_SERVICE)、从其他线程获取到资源后回到原线程处理资源(DECODE_DATA)。

  • DecodeJob.Stage: 定义资源获取和转换的几阶段,包括:初始阶段(INITIALIZE)、从转码缓存获取资源阶段(RESOURCE_CACHE)、从未修改的数据缓存获取资源阶段(DATA_CACHE)、从原始路径获取资源阶段(SOURCE)、获取到资后源转码阶段(ENCODE)、结束阶段(FINISHED)

  • DataFetcher: 泛型接口,定义了获取和取消资源获取的接口,有对应通过不同方式获取资源的实现类,比如网络(HttpUrlFetcher)、Assets(AssetPathFetcher)等。

  • ModelLoader: 用于将任意复杂的数据模型转换为具体数据类型的工厂接口, DataFetcher可以使用该数据类型来获取模型所表示的资源的数据。

  • DataFetcherGenerator: 作用是使用ModeLoader和数据模型生成一系列DataFetcher接口。DataFetcherGenerator实现类有:ResourceCacheGenerator(生成从转码后的缓存获取资源的DataFetcher)、DataCacheGenerator(生成从未修改的缓存获取资源的DataFetcher)、SourceGenerator(生成从原始地址获取资源的DataFetcher)

他们之间的关系可总结为:DecodeJob根据任务执行的原因(RunReason)和来源(Stage)创建DataFetcherGenerator接口实现类对象。DataFetcherGenerator接口实现类通过LoadData中的DataFetcher对象实现获取资源的功能。

class DecodeJob<R>
    implements DataFetcherGenerator.FetcherReadyCallback,
        Runnable,
        Comparable<DecodeJob<?>>,
        Poolable {
  //省略部分代码

  // We need to rethrow only CallbackException, but not other types of Throwables.
  @SuppressWarnings("PMD.AvoidRethrowingException")
  @Override
  public void run() {
    // This should be much more fine grained, but since Java's thread pool implementation silently
    // swallows all otherwise fatal exceptions, this will at least make it obvious to developers
    // that something is failing.
    GlideTrace.beginSectionFormat("DecodeJob#run(model=%s)", model);
    // Methods in the try statement can invalidate currentFetcher, so set a local variable here to
    // ensure that the fetcher is cleaned up either way.
    DataFetcher<?> localFetcher = currentFetcher;
    try {
      if (isCancelled) {
        notifyFailed();
        return;
      }
      runWrapped();
    } catch (CallbackException e) {
      // If a callback not controlled by Glide throws an exception, we should avoid the Glide
      // specific debug logic below.
      throw e;
    } catch (Throwable t) {
      // Catch Throwable and not Exception to handle OOMs. Throwables are swallowed by our
      // usage of .submit() in GlideExecutor so we're not silently hiding crashes by doing this. We
      // are however ensuring that our callbacks are always notified when a load fails. Without this
      // notification, uncaught throwables never notify the corresponding callbacks, which can cause
      // loads to silently hang forever, a case that's especially bad for users using Futures on
      // background threads.
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(
            TAG,
            "DecodeJob threw unexpectedly" + ", isCancelled: " + isCancelled + ", stage: " + stage,
            t);
      }
      // When we're encoding we've already notified our callback and it isn't safe to do so again.
      if (stage != Stage.ENCODE) {
        throwables.add(t);
        notifyFailed();
      }
      if (!isCancelled) {
        throw t;
      }
      throw t;
    } finally {
      // Keeping track of the fetcher here and calling cleanup is excessively paranoid, we call
      // close in all cases anyway.
      if (localFetcher != null) {
        localFetcher.cleanup();
      }
      GlideTrace.endSection();
    }
  }

  private void runWrapped() {
    //DecodeJob对象在初始化时,runReason被设置为INITIALIZE
    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);
    }
  }
            
  //根据当前stage获取下一状态下的stage值,diskCacheStrategy为磁盘缓存策略,默认为DiskCacheStrategy.AUTOMATIC
 //该策略下即会从转码后的缓存获取资源也会从未转码的缓存获取资源(decodeCachedResource()和decodeCachedDat()都会返回true)。
  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:
        // Skip loading from source if the user opted to only retrieve the resource from cache.
        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        return Stage.FINISHED;
      default:
        throw new IllegalArgumentException("Unrecognized stage: " + current);
    }
  }            
  //根据当前状态,创建不同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);
    }
  }
  // 获取资源的主要逻辑就在该方法中,
  private void runGenerators() {
    currentThread = Thread.currentThread();
    startFetchTime = LogTime.getLogTime();
    boolean isStarted = false;
    //执行当前DataFetcherGenerator.startNext()方法,在该方法中如果执行了获取资源的动作,则返回true,否则返回false.
    while (!isCancelled
        && currentGenerator != null
        && !(isStarted = currentGenerator.startNext())) {
      //如果没有执行,则获取下一个状态,创建对应的DataFetcherGenerator对象。
      stage = getNextStage(stage);
      currentGenerator = getNextGenerator();
      //如果当前状态是Stage.SOURCE(从原始地址获取资源),则重新执行DecodeJob任务
      if (stage == Stage.SOURCE) {
        reschedule();
        return;
      }
    }
    // We've run out of stages and generators, give up.
    if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
      notifyFailed();
    }
  }
  
  @Override
  public void reschedule() {
    //设置runReason,然后重新执行DecodeJob任务。
    runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
    callback.reschedule(this);
  }            

  //省略部分代码

}

具体加载过程在此不展开,感兴趣的可以去跟下源码。这里讲下从磁盘或者原始地址(网络、文件等)获取资源的逻辑,大致如下:

  • 先从转码的磁盘缓存获取资源(通过ResourceCacheGenerator),如果有对应的资源则返回;如果没有,则从未修改的磁盘缓存获取资源(通过DataCacheGenerator)。
  • 如果从未修改的磁盘缓存有获取到资源,则返回,否则从原始地址获取。
  • 从原始地址获取到资源后,再执行DecodeJob任务,将从原始地址获取到的资源写入磁盘缓存(未经修改的资源缓存)。然后创建DataCacheGenerator对象,并执行从未修改的磁盘缓存中读取资源。

总结下资源加载的逻辑,入下图所示
在这里插入图片描述

最后附上资源加载的时序图仅供参考
在这里插入图片描述

资源显示

?资源显示的时序整体如下

在这里插入图片描述

从磁盘或者原始地址获取资源完成分为两种情况:一种是资源获取成功;另外一种是资源获取失败。对于这两种情况,显示的过程也不一样。

资源获取成功

?资源获取成功后DataFetcher接口实现类会最终会调用DecodeJob实现的DataFetcherGenerator.FetcherReadyCallback.onDataFetcherReady()方法,在该方法中会实现从磁盘读取缓存的文件并解码成Bitmap或者BimapDrawable,如果解码成功则显示解码后的内容;如果失败则显示占位符内容(优先级是:Fallback>Error>Placeholder)。

资源获取失败

? 获取资源失败的逻辑比较简单,如果获取资源失败则显示占位符内容(优先级是:Fallback>Error>Placeholder)。

对于从内存缓存中获取到资源的逻辑,它和从磁盘解码后显示的逻辑是一样的。

关于Request、Resource和Target的关系,可以参考下面的类图。Reuqest相当于连接Resource和Target的桥梁,当Resource加载完成后,Request负责将加载到的内容传给Target显示。
在这里插入图片描述

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-01-14 02:05:57  更:2022-01-14 02:06:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 10:39:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码