首页 > 编程语言 >Glide源码阅读之状态模式[SingleRequest<R>.Status]

Glide源码阅读之状态模式[SingleRequest<R>.Status]

时间:2023-09-19 11:38:47浏览次数:47  
标签:Status status resource Glide 状态 模式 requestLock 源码


前言

前面写完策略模式,接着写状态模式;在开始接触这两个模式的时候我也很疑惑,这两个设计模式很相似,用法也很类似。好一段时间我都没有区分这两者的区别。在使用的时候也不知道怎么选择,后来慢慢的加深理解也就总结出规律了。先看看状态模式的经典结构

状态模式介绍

《Android源码设计模式解析与实践》

定义

当一个对象的内在状态改变时容许改变其行为,这个对象开起来像是改变了其类

使用场景
1)一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为
2)代码包含大量与对象状态有关的条件语句,
状态模式将每个条件分支放入一个独立的类中,这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化,这样通过多态来去除过多的、重复的if-else等分支语句

UML类图

Glide源码阅读之状态模式[SingleRequest<R>.Status]_设计模式

《大话设计模式》

定义
当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类(和《Android源码设计模式解析与实践》定义相同)

状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化

Glide源码阅读之状态模式[SingleRequest<R>.Status]_设计模式_02

策略模式VS状态模式

状态模式和策略模式的结构几乎完全一样,但它们的目的、本质却完全不一样。状态模式的行为是平行的、不可替换的,策略模式的行为是彼此独立、可相互替换的。用一句话来表述,状态模式把对象的行为包装在不同的状态对象里,每个状态对象都有一个共同的抽象状态基类。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变–《Android源码设计模式解析与实践》

Glide状态模式应用SingleRequest.Status

包路径:com.bumptech.glide.request.SingleRequest.Status
将资源加载到给定目标中的请求。

/**
 * A {@link Request} that loads a {@link com.bumptech.glide.load.engine.Resource} into a given
 * {@link Target}.
 *
 * @param <R> The type of the resource that will be transcoded from the loaded resource.
 */
 将资源加载到给定目标中的请求。
public final class SingleRequest<R> implements Request, SizeReadyCallback, ResourceCallback {
  /** Tag for logging internal events, not generally suitable for public use. */
  private static final String TAG = "GlideRequest";
  /** Tag for logging externally useful events (request completion, timing etc). */
  private static final String GLIDE_TAG = "Glide";

  private static final boolean IS_VERBOSE_LOGGABLE = Log.isLoggable(TAG, Log.VERBOSE);
  private int cookie;

  private enum Status {
    /** Created but not yet running. */
    已创建但尚未运行。
    PENDING,
    /** In the process of fetching media. */
    在获取媒体的过程中。
    RUNNING,
    /** Waiting for a callback given to the Target to be called to determine target dimensions. */
    等待给定给Target的回调被调用以确定目标维度。
    WAITING_FOR_SIZE,
    /** Finished loading media successfully. */
    成功加载媒体。
    COMPLETE,
    /** Failed to load media, may be restarted. */
    加载介质失败,可能被重新启动。
    FAILED,
    /** Cleared by the user with a placeholder set, may be restarted. */
    使用占位符设置的用户清除,可能会重新启动。
    CLEARED,
  }

Status.PENDING

// We are in fact locking on the same lock that will be used for all subsequent method calls.
  @SuppressWarnings("GuardedBy")
  private SingleRequest(
      Context context,
      GlideContext glideContext,
      @NonNull Object requestLock,
      @Nullable Object model,
      Class<R> transcodeClass,
      BaseRequestOptions<?> requestOptions,
      int overrideWidth,
      int overrideHeight,
      Priority priority,
      Target<R> target,
      @Nullable RequestListener<R> targetListener,
      @Nullable List<RequestListener<R>> requestListeners,
      RequestCoordinator requestCoordinator,
      Engine engine,
      TransitionFactory<? super R> animationFactory,
      Executor callbackExecutor) {
    this.requestLock = requestLock;
    this.context = context;
    this.glideContext = glideContext;
    this.model = model;
    this.transcodeClass = transcodeClass;
    this.requestOptions = requestOptions;
 //。。。
    status = Status.PENDING;

   。。。
    }
  }

Status.RUNNING

@Override
  public void begin() {
    synchronized (requestLock) {
      assertNotCallingCallbacks();
      stateVerifier.throwIfRecycled();
      startTime = LogTime.getLogTime();
     ....

      if (status == Status.RUNNING) {
        throw new IllegalArgumentException("Cannot restart a running request");
      }
      // If we're restarted after we're complete (usually via something like a notifyDataSetChanged
      // that starts an identical request into the same Target or View), we can simply use the
      // resource and size we retrieved the last time around and skip obtaining a new size, starting
      // a new load etc. This does mean that users who want to restart a load because they expect
      // that the view size has changed will need to explicitly clear the View or Target before
      // starting the new load.
      if (status == Status.COMPLETE) {
        onResourceReady(
            resource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
        return;
      }

      // Restarts for requests that are neither complete nor running can be treated as new requests
      // and can run again from the beginning.

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

      if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
          && canNotifyStatusChanged()) {
        target.onLoadStarted(getPlaceholderDrawable());
      }
      if (IS_VERBOSE_LOGGABLE) {
        logV("finished run method in " + LogTime.getElapsedMillis(startTime));
      }
    }
  }
@Override
  public boolean isRunning() {
    synchronized (requestLock) {
      return status == Status.RUNNING || status == Status.WAITING_FOR_SIZE;
    }
  }
/** A callback method that should never be invoked directly. */
  @Override
  public void onSizeReady(int width, int height) {
    stateVerifier.throwIfRecycled();
    synchronized (requestLock) {
      if (IS_VERBOSE_LOGGABLE) {
        logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
      }
      if (status != Status.WAITING_FOR_SIZE) {
        return;
      }
      status = Status.RUNNING;

      ...

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

Status.WAITING_FOR_SIZE

@Override
  public void begin() {
    synchronized (requestLock) {
      assertNotCallingCallbacks();
      stateVerifier.throwIfRecycled();
      startTime = LogTime.getLogTime();
      if (model == null) {
        if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
          width = overrideWidth;
          height = overrideHeight;
        }
        // Only log at more verbose log levels if the user has set a fallback drawable, because
        // fallback Drawables indicate the user expects null models occasionally.
        int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
        onl oadFailed(new GlideException("Received null model"), logLevel);
        return;
      }

      if (status == Status.RUNNING) {
        throw new IllegalArgumentException("Cannot restart a running request");
      }

      // If we're restarted after we're complete (usually via something like a notifyDataSetChanged
      // that starts an identical request into the same Target or View), we can simply use the
      // resource and size we retrieved the last time around and skip obtaining a new size, starting
      // a new load etc. This does mean that users who want to restart a load because they expect
      // that the view size has changed will need to explicitly clear the View or Target before
      // starting the new load.
      if (status == Status.COMPLETE) {
        onResourceReady(
 //......
      status = Status.WAITING_FOR_SIZE;
      
     // ....

      if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
          && canNotifyStatusChanged()) {
        target.onLoadStarted(getPlaceholderDrawable());
      }
      if (IS_VERBOSE_LOGGABLE) {
        logV("finished run method in " + LogTime.getElapsedMillis(startTime));
      }
    }
  }
@Override
  public boolean isRunning() {
    synchronized (requestLock) {
      return status == Status.RUNNING || status == Status.WAITING_FOR_SIZE;
    }
  }
/** A callback method that should never be invoked directly. */
  @Override
  public void onSizeReady(int width, int height) {
    //...
      if (status != Status.WAITING_FOR_SIZE) {
        return;
      }
      status = Status.RUNNING;

     //...

Status.COMPLETE

public void begin() {
 // If we're restarted after we're complete (usually via something like a notifyDataSetChanged
      // that starts an identical request into the same Target or View), we can simply use the
      // resource and size we retrieved the last time around and skip obtaining a new size, starting
      // a new load etc. This does mean that users who want to restart a load because they expect
      // that the view size has changed will need to explicitly clear the View or Target before
      // starting the new load.
      if (status == Status.COMPLETE) {
        onResourceReady(
            resource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
        return;
      }
@Override
  public boolean isComplete() {
    synchronized (requestLock) {
      return status == Status.COMPLETE;
    }
  }
@Override
  public boolean isAnyResourceSet() {
    synchronized (requestLock) {
      return status == Status.COMPLETE;
    }
  }
/** A callback method that should never be invoked directly. */
  @SuppressWarnings("unchecked")
  @Override
  public void onResourceReady(
  ....
        if (!canSetResource()) {
          toRelease = resource;
          this.resource = null;
          // We can't put the status to complete before asking canSetResource().
          status = Status.COMPLETE;

    ....
/**
   * Internal {@link #onResourceReady(Resource, DataSource, boolean)} where arguments are known to
   * be safe.
   *
   * @param resource original {@link Resource}, never <code>null</code>
   * @param result object returned by {@link Resource#get()}, checked for type and never <code>null
   *     </code>
   */
  // We're using experimental APIs...
  @SuppressWarnings({"deprecation", "PMD.UnusedFormalParameter"})
  @GuardedBy("requestLock")
  private void onResourceReady(
      Resource<R> resource, R result, DataSource dataSource, boolean isAlternateCacheKey) {
    // We must call isFirstReadyResource before setting status.
    boolean isFirstResource = isFirstReadyResource();

    status = Status.COMPLETE;

    this.resource = resource;
...

Status.FAILED

private void onl oadFailed(GlideException e, int maxLogLevel) {
    stateVerifier.throwIfRecycled();
    synchronized (requestLock) {
      e.setOrigin(requestOrigin);
      int logLevel = glideContext.getLogLevel();
      if (logLevel <= maxLogLevel) {
        Log.w(
            GLIDE_TAG, "Load failed for " + model + " with size [" + width + "x" + height + "]", e);
        if (logLevel <= Log.INFO) {
          e.logRootCauses(GLIDE_TAG);
        }
      }

      loadStatus = null;
....
      status = Status.FAILED;
....

Status.CLEARED

/**
   * Cancels the current load if it is in progress, clears any resources held onto by the request
   * and replaces the loaded resource if the load completed with the placeholder.
   *
   * <p>Cleared requests can be restarted with a subsequent call to {@link #begin()}
   *
   * @see #cancel()
   */
  @Override
  public void clear() {
    Resource<R> toRelease = null;
    synchronized (requestLock) {
      assertNotCallingCallbacks();
      stateVerifier.throwIfRecycled();

      if (status == Status.CLEARED) {
        return;
      }
cancel();
      // Resource must be released before canNotifyStatusChanged is called.
  
//.................
      status = Status.CLEARED;
//.............
@Override
  public boolean isCleared() {
    synchronized (requestLock) {
      return status == Status.CLEARED;
    }
  }

总结

之前的篇章作者都是讲结构很少涉及方法里的具体实现,这是因为具体涉及到业务了,如果不了解业务那理解上就有点障碍。状态模式这章之所以涉及到实现细节是因为这个状态模式的实现选择了枚举的方式,在方法里面进行逻辑业务实现。这个使用方式也是作者本人经常用的。状态模式在其他行业里有状态机的别称。不过本质上结构是一样的,就是具体的实现和行业应用场景有关。如果没有记错是硬件开发方面的说法。

在实际的项目应用开发中,作者在使用状态模式解决过复杂的if-else业务重构,类比同一应用场景下bug明显减少,代码质量明显提高。这个是测试人员反馈的测试结果。由此和测试人员关系很融洽。第二次是公司有一个项目被客户方给整成捆绑合同有一个需求迟迟解决不了,导致这个项目接近半年没有交付,由此导致销售总监、销售人员、部门老大、部门开发经理压力很大。为了实现这个需求不得不加班加点的开会写解决方案,在加上第三方硬件供应商的协调增加了实现的复杂性。。。在部门经理把这个需求转交给作者负责后,经过一个星期的设计,一个星期的调试开发。最后采用了状态模式来解决的。当然细节比介绍的状态模式复杂。不过主体使用的状态模式结构。如此花了半个月基本满足了客户的需求。也因此在开发团队中加分不少。

也是因为作者亲身经历,所以对状态模式感触很深。后来的一些项目也使用状态模式提高了开发代码质量和降低迭代扩展维护的难度。熟练的使用状态模式加深对面向对象编程的理解和应用。对于理解项目的模块分割和模块设计都有很好的促进作用。

总之作为开发掌握状态模式会有很多意想不到的好处。

自研产品推荐

历时一年半多开发终于smartApi-v1.0.0版本在2023-09-15晚十点正式上线
smartApi是一款对标国外的postman的api调试开发工具,由于开发人力就作者一个所以人力有限,因此v1.0.0版本功能进行精简,大功能项有:

  • api参数填写
  • api请求响应数据展示
  • PDF形式的分享文档
  • Mock本地化解决方案
  • api列表数据本地化处理
  • 再加上UI方面的打磨

为了更好服务大家把之前的公众号和软件激活结合,如有疑问请大家反馈到公众号即可,下个版本30%以上的更新会来自公众号的反馈。

嗯!先解释不上服务端原因,API调试工具的绝大多数时候就是一个数据模型、数据处理、数据模型理解共识的问题解决工具,所以作者结合自己十多年开发使用的一些痛点来打造的,再加上服务端开发一般是面向企业的,作者目前没有精力和时间去打造企业服务。再加上没有资金投入所以服务端开发会滞后,至于什么时候会进行开发,这个要看募资情况和用户反馈综合考虑。虽然目前国内有些比较知名的api工具了,但作者使用后还是觉得和实际使用场景不符。如果有相关吐槽也可以在作者的公众号里反馈蛤!

下面是一段smartApi使用介绍:

Glide源码阅读之状态模式[SingleRequest<R>.Status]_java_03

下载地址:

https://pan.baidu.com/s/1kFAGbsFIk3dDR64NwM5y2A?pwd=csdn


标签:Status,status,resource,Glide,状态,模式,requestLock,源码
From: https://blog.51cto.com/u_16264967/7523780

相关文章

  • Glide源码阅读之策略模式4总结
    《Android源码设计模式解析与实践》定义策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化使用场景针对同一类型问题的多种处理方式。仅仅是具体行为有差别时需要安全地封装多种同一类型的操作时出现同一抽......
  • Glide源码阅读之工厂模式2【DiskCache.Factory】
    DiskCache.Factory包路径:com.bumptech.glide.load.engine.cache.DiskCachecom.bumptech.glide.load.engine.cache.DiskCache.Factory/**Aninterfaceforwritingtoandreadingfromadiskcache.*/一种向磁盘缓存写入和从磁盘缓存读取数据的接口。publicinterfaceDiskCa......
  • Glide源码阅读之工厂模式1【ModelLoaderFactory】【ModelLoader】
    使用场景介绍摘自菜鸟教程|设计模式|工厂模式意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。主要解决:主要解决接口选择的问题。何时使用:我们明确地计划不同条件下创建不同实例时。如何解决:让其子类实现工厂接口,返回的也是一个......
  • Glide源码阅读之建造者(builder)模式3【RequestOptions】【BaseRequestOptions】
    官方定义本来解析完GlideBuilder、RequestBuilder就已经觉得建造者模式用的变化有些大了,但随着解析的进行发现还要下面的这两种变种应用。先解析出来给大家看看,说不定在某些场景下能启发读者使用这种模式应用方式builder模式应用变化一结构:publicstaticRequestOptionssizeXXXX(p......
  • 万字长文深度解读Java线程池,硬核源码分析
    前言本文将深入分析Java线程池的源码,包括线程池的创建、任务提交、工作线程的执行和线程池的关闭等过程。通过对线程池源码的解析,我们能够更好地理解线程池的原理和机制,为我们在实际开发中合理使用线程池提供指导。文章内容较长,建议找个安静的环境慢慢细读,由于线程池涉及的内容......
  • 云HIS医院信息化管理系统源码:集团化管理,多租户机制,满足医院业务需求
    随着云计算、大数据、物联网等新兴技术的迅猛发展,HIS模式的理念、运行机制更新,衍生出了新的HIS模式——云HIS。云HIS是基于云计算、大数据、互联网等高新技术研发的医疗卫生信息平台,它实现了医院信息化从局域网向互联网转型,并重新定义了医疗卫生信息化建设的理念、框架、功能以及运......
  • qemu源码分析(6)--Apple的学习笔记
    一,前言由于看到了类似的写法,都用到了object_dynamic_cast_assert函数,所以分析下。二,源码分析看到如下代码的写法,很眼熟CortexMBoardState*board=CORTEXM_BOARD_STATE(machine);machine的类型是MachineState*#defineCORTEXM_BOARD_STATE(obj)\OBJECT_CHECK(CortexMBoardSt......
  • 如何给OpenWrt的源码打patch
    如何给OpenWrt的源码打patch目录如何给OpenWrt的源码打patch给package打patch给kernel打patch给package打patchOpenWrt有成熟的打patch机制,可以直接从服务器download源码,校验后,再结合自己开发的patch,打上,然后编译。开发环境:基于Ubuntu14.0432bit主机,编译demo_board机型。需......
  • [SpringSecurity5.6.2源码分析十一]:CorsFilter
    前言• 跨域:两个域名的(协议、域名/ip、端口)有任意一个不同即视为跨域• 跨域资源共享(Cors):即浏览器允许访问其他跨域的资源• 而CorsFilter就是SpringSecurity用来处理Cors的过滤器1.CorsConfigurer• CorsConfigurer是CorsFilter对应的配置类,其中就只有一个重要方法• co......
  • 人人都能学的数据分析体系课(16周完整版+源码+PDF课件)
    点击下载——人人都能学的数据分析体系课(16周完整版+源码+PDF课件)  提取码:nsep 人人都能学的数据分析体系课(16周完整版+源码+PDF课件),数据也称为观测值,是实验、测量、观察、调查等的结果。数据分析中所处理的数据分为定性数据和定量数据。只能归入某一类而不能用数值进行测度的数......