Glide 源码阅读笔记(三)

Glide 源码阅读笔记(三)

在第一篇文章中我简单介绍了 Glide 实例的创建过程,重点介绍了 Glide 的内存缓存实现和磁盘的缓存实现:Glide 源码阅读笔记(一)

在第二篇文章中介绍了 Glide 对生命周期的管理: Glide 源码阅读笔记(二)

今天我们继续今天的内容。

源码阅读基于 Glide 4.16.0 版本

Reqeust 和 RequestCoordinator

Request 比较好理解,它就是一个图片加载的任务。RequestCoordinator 它也是继承于 Request,它是用来协调它内部的多个 Request,这么说比较抽象。我这里简单举一个例子。我们可以在使用 Glide 时,添加一个异常时请求的任务,当主任务加载失败时 Glide 就会去加载这个异常时请求的任务。例如以下代码:

Kotlin 复制代码
val errorRequestBuilder = Glide.with(this)
    .load("https://www.tans.com/error.png")
Glide.with(this)
    .load("https://upload-images.jianshu.io/upload_images/5809200-a99419bb94924e6d.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240")
    .error(errorRequestBuilder)
    .into(findViewById<ImageView>(R.id.my_iv))

其中如何协调主 Request 与错误相关的 Request,就是 ErrorRequestCoordinator 的工作,它的工作原理非常简单,首先触发主 Request,当它请求失败时再去加载异常的 Request。类似原理的还有 ThumbnailRequestCoordinator,它是协调缩略图加载的 RequestCoordinator。后面的代码分析中我们都能看到他们。通过 RequestCoordinator 可以把单个 Request 构建成一个 Request 的请求树,可以用这个请求树来执行复杂的请求逻辑。

我们在使用 Glide 构建完 RequestBuilder 后,会通过 into() 方法把最后的结果渲染到对应 ImageView (当然我们也可以不渲染到 ImageView 中,这需要我们自定义)。所以我们也以 RequestBuilder#into() 方法作为今天分析的入口函数:

Java 复制代码
  @NonNull
  public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
    // 校验必须在主线程
    Util.assertMainThread();
    Preconditions.checkNotNull(view);

    BaseRequestOptions<?> requestOptions = this;
    // 如果没有设置图片的裁剪方式,根据 ImageView 的 ScaleType 来计算。
    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(
        // 将 ImageView 封装成 Target
        glideContext.buildImageViewTarget(view, transcodeClass),
        /* targetListener= */ null,
        requestOptions,
        // 回调线程设置成主线程
        Executors.mainThreadExecutor());
  }

简单说明下上面的代码:

  1. into() 方法必须在主线程调用。
  2. 如果 RequestBuilder 没有设置裁剪方式,需要通过 ImageView#getScaleType() 来计算,处理裁剪方式的类是 Transformations,它被保存在 options 中,后面我们会再看到它的出现。
  3. 然后通过 GlideContext#buildImageViewTarget() 将 ImageView 封装成 ViewTarget 对象,这里要注意我们的 trascodeClass 是 Drawable,它表示 ViewTarget 最后能够处理渲染的对象格式,然后继续调用 into() 方法。

我们去看看 GlideContext#buildImageViewTarget() 方法是如何构建 ViewTarget 的:

Java 复制代码
  @NonNull
  public <X> ViewTarget<ImageView, X> buildImageViewTarget(
      @NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
    return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
  }

继续调用 ImageViewTargetFactory#buildTarget() 方法:

Java 复制代码
  @NonNull
  @SuppressWarnings("unchecked")
  public <Z> ViewTarget<ImageView, Z> buildTarget(
      @NonNull ImageView view, @NonNull Class<Z> clazz) {
    if (Bitmap.class.equals(clazz)) {
      return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
    } else if (Drawable.class.isAssignableFrom(clazz)) {
      return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
    } else {
      throw new IllegalArgumentException(
          "Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
    }
  }

它所支持的 transcodeClass 只能是 Bitmap 或者 Drawable,我们的 demo 中是 Drawable,最终的 ViewTarget 实现类是 DrawableImageViewTarget()。

在看完了 ViewTarget 创建后,我们继续我们的主流程,继续看 into() 方法:

Java 复制代码
  private <Y extends Target<TranscodeType>> Y into(
      @NonNull Y target,
      @Nullable RequestListener<TranscodeType> targetListener,
      BaseRequestOptions<?> options,
      Executor callbackExecutor) {
    Preconditions.checkNotNull(target);
    // 如果没有这是 model,直接报错。
    if (!isModelSet) {
      throw new IllegalArgumentException("You must call #load() before calling #into()");
    }
    // 构建 Request
    Request request = buildRequest(target, targetListener, options, callbackExecutor);
    
    // 获取上次的 Request
    Request previous = target.getRequest();
    // 如果上次的 Request 和 当前的请求一致,直接开始上次的请求
    if (request.isEquivalentTo(previous)
        && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
      if (!Preconditions.checkNotNull(previous).isRunning()) {
        previous.begin();
      }
      return target;
    }
    
    // 清除 Target 中上次的请求。
    requestManager.clear(target);
    // 将当前的 Request 设置到 Target 中。
    target.setRequest(request);
    // 将 target 和 request 通知 RequestManager
    requestManager.track(target, request);

    return target;
  }

简单说明下上面的代码:

  1. 检查是否有设置 model,如果没有就直接报错,我们设置的请求 url 就是 model。
  2. 通过 buildRequest() 构建 Request(后面重点分析这个方法)。
  3. 判断 ViewTarget 中上次的 Request 和当前的 Request 是否一致,如果一致,就直接返回当前方法(返回之前会判断一下上次的 Request 的运行状态,如果没有运行调用对应的 begin() 方法)。
  4. 通过 RequestManager#clear() 方法清除 Target 中的 Request。
  5. 通过 Target#setRequest() 方法将当前 Request 添加到 Target 中。
  6. 将 Target 和 Request 通知 RequestManager。

我们简单看看 ViewTarget#setRequest() 方法是如何设置 Request 的:

Java 复制代码
  @Override
  public void setRequest(@Nullable Request request) {
    setTag(request);
  }
  
  private void setTag(@Nullable Object tag) {
    isTagUsedAtLeastOnce = true;
    view.setTag(tagId, tag);
  }

Request 其实就是被添加到 View 的 tag 上,对应的 tagId 是 R.id.glide_custom_view_target_tag。

我们再看看 RequestManager#track() 方法是如何处理 Target 和 Request 的:

Java 复制代码
  synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
    targetTracker.track(target);
    requestTracker.runRequest(request);
  }

这里 target 交由 TargetTracker 处理;request 交由 RequestTracker 来处理。

Java 复制代码
public void track(@NonNull Target<?> target) {  
   targets.add(target);  
}

TargetTracker 处理非常简单,直接添加到列表中。

再看看 RequestTracker#runRequest() 方法:

Java 复制代码
  public void runRequest(@NonNull Request request) {
    requests.add(request);
    if (!isPaused) {
      request.begin();
    } else {
      request.clear();
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Paused, delaying request");
      }
      pendingRequests.add(request);
    }
  }

将 request 添加到 requests 列表中,如果当前没有暂停(也就是 onStart() 生命周期)调用 Request#begin() 方法;如果当前已经被暂停,会调用 Request#clear() 方法,然后添加到 pendingRequests 列表中。

我们继续看看 RequestBuilder#into() 方法中调用过的 buildeRequest() 方法(希望你还没有忘记),看看它是如何构建 Request 的。

Java 复制代码
  private Request buildRequest(
      Target<TranscodeType> target,
      @Nullable RequestListener<TranscodeType> targetListener,
      BaseRequestOptions<?> requestOptions,
      Executor callbackExecutor) {
    return buildRequestRecursive(
        /* requestLock= */ new Object(),
        target,
        targetListener,
        /* parentCoordinator= */ null,
        transitionOptions,
        requestOptions.getPriority(),
        requestOptions.getOverrideWidth(),
        requestOptions.getOverrideHeight(),
        requestOptions,
        callbackExecutor);
  }

继续看 buildRequestRecursive() 方法的实现:

Java 复制代码
  private Request buildRequestRecursive(
      Object requestLock,
      Target<TranscodeType> target,
      @Nullable RequestListener<TranscodeType> targetListener,
      @Nullable RequestCoordinator parentCoordinator,
      TransitionOptions<?, ? super TranscodeType> transitionOptions,
      Priority priority,
      int overrideWidth,
      int overrideHeight,
      BaseRequestOptions<?> requestOptions,
      Executor callbackExecutor) {

    // Build the ErrorRequestCoordinator first if necessary so we can update parentCoordinator.
    ErrorRequestCoordinator errorRequestCoordinator = null;
    // 如果有 ErrorBuilder,构建一个 ErrorRequestCoordinator,同时它作为 parentCoordinator
    if (errorBuilder != null) {
      errorRequestCoordinator = new ErrorRequestCoordinator(requestLock, parentCoordinator);
      parentCoordinator = errorRequestCoordinator;
    }
    
    // 构建 MainRequest
    Request mainRequest =
        buildThumbnailRequestRecursive(
            requestLock,
            target,
            targetListener,
            parentCoordinator,
            transitionOptions,
            priority,
            overrideWidth,
            overrideHeight,
            requestOptions,
            callbackExecutor);

    if (errorRequestCoordinator == null) {
      // 如果没有对 Error 的处理,直接返回 mainRequest
      return mainRequest;
    }

    int errorOverrideWidth = errorBuilder.getOverrideWidth();
    int errorOverrideHeight = errorBuilder.getOverrideHeight();
    if (Util.isValidDimensions(overrideWidth, overrideHeight) && !errorBuilder.isValidOverride()) {
      errorOverrideWidth = requestOptions.getOverrideWidth();
      errorOverrideHeight = requestOptions.getOverrideHeight();
    }
    
    // 通过 errorBuilder 构建 errorRequest。
    Request errorRequest =
        errorBuilder.buildRequestRecursive(
            requestLock,
            target,
            targetListener,
            errorRequestCoordinator,
            errorBuilder.transitionOptions,
            errorBuilder.getPriority(),
            errorOverrideWidth,
            errorOverrideHeight,
            errorBuilder,
            callbackExecutor);
    // 将 mainRequest 和 errorRequest 添加到 ErrorRequestCoordinator 中        
    errorRequestCoordinator.setRequests(mainRequest, errorRequest);
    return errorRequestCoordinator;
  }

简单说明下上面的方法:

  1. 如果有 errorBuilder,构建一个 ErrorRequestCoordinator,同时它也是后续请求的 parent。
  2. 通过 buildThumbnailRequestRecursive() 构建 mainRequest。如果没有 errorBuilder,直接把 mainRequest 作为结果返回方法。
  3. 通过 errorBuilder 构建 errorRequest。
  4. 将 mainRequest 和 errorRequest 添加到 ErrorRequestCoordinator 中,然后把它作为结果返回。

我们继续看看 buildThumbnailRequestRecursive() 方法是如何 mainRequest 的。

Java 复制代码
  private Request buildThumbnailRequestRecursive(
      Object requestLock,
      Target<TranscodeType> target,
      RequestListener<TranscodeType> targetListener,
      @Nullable RequestCoordinator parentCoordinator,
      TransitionOptions<?, ? super TranscodeType> transitionOptions,
      Priority priority,
      int overrideWidth,
      int overrideHeight,
      BaseRequestOptions<?> requestOptions,
      Executor callbackExecutor) {
    // 判断是否有 thumbnailBuilder  
    if (thumbnailBuilder != null) {
      if (isThumbnailBuilt) {
        throw new IllegalStateException(
            "You cannot use a request as both the main request and a "
                + "thumbnail, consider using clone() on the request(s) passed to thumbnail()");
      }

      TransitionOptions<?, ? super TranscodeType> thumbTransitionOptions =
          thumbnailBuilder.transitionOptions;

      if (thumbnailBuilder.isDefaultTransitionOptionsSet) {
        thumbTransitionOptions = transitionOptions;
      }

      Priority thumbPriority =
          thumbnailBuilder.isPrioritySet()
              ? thumbnailBuilder.getPriority()
              : getThumbnailPriority(priority);

      int thumbOverrideWidth = thumbnailBuilder.getOverrideWidth();
      int thumbOverrideHeight = thumbnailBuilder.getOverrideHeight();
      if (Util.isValidDimensions(overrideWidth, overrideHeight)
          && !thumbnailBuilder.isValidOverride()) {
        thumbOverrideWidth = requestOptions.getOverrideWidth();
        thumbOverrideHeight = requestOptions.getOverrideHeight();
      }
      
      // 创建一个 ThumbnailRequestCoordinator 对象
      ThumbnailRequestCoordinator coordinator =
          new ThumbnailRequestCoordinator(requestLock, parentCoordinator);
      // 构建 fullRequest,这其实就是我们的主请求    
      Request fullRequest =
          obtainRequest(
              requestLock,
              target,
              targetListener,
              requestOptions,
              coordinator,
              transitionOptions,
              priority,
              overrideWidth,
              overrideHeight,
              callbackExecutor);
      isThumbnailBuilt = true;
      // 构建 thumbRequest。
      Request thumbRequest =
          thumbnailBuilder.buildRequestRecursive(
              requestLock,
              target,
              targetListener,
              coordinator,
              thumbTransitionOptions,
              thumbPriority,
              thumbOverrideWidth,
              thumbOverrideHeight,
              thumbnailBuilder,
              callbackExecutor);
      isThumbnailBuilt = false;
      // 将 fullRequest 和 thumbRequest 添加到 ThumbnailRequestCoordinator 中
      coordinator.setRequests(fullRequest, thumbRequest);
      return coordinator;
    } else if (thumbSizeMultiplier != null) {
      // ...
      return coordinator;
    } else {
      // 没有 thumbnailBuilder,直接构建一个 SingleRequest
      return obtainRequest(
          requestLock,
          target,
          targetListener,
          requestOptions,
          parentCoordinator,
          transitionOptions,
          priority,
          overrideWidth,
          overrideHeight,
          callbackExecutor);
    }
  }
  
  private Request obtainRequest(
      Object requestLock,
      Target<TranscodeType> target,
      RequestListener<TranscodeType> targetListener,
      BaseRequestOptions<?> requestOptions,
      RequestCoordinator requestCoordinator,
      TransitionOptions<?, ? super TranscodeType> transitionOptions,
      Priority priority,
      int overrideWidth,
      int overrideHeight,
      Executor callbackExecutor) {
    return SingleRequest.obtain(
        context,
        glideContext,
        requestLock,
        model,
        transcodeClass,
        requestOptions,
        overrideWidth,
        overrideHeight,
        priority,
        target,
        targetListener,
        requestListeners,
        requestCoordinator,
        glideContext.getEngine(),
        transitionOptions.getTransitionFactory(),
        callbackExecutor);
  }

上面的代码看似很多,其实非常简单:

  1. 判断是否有 thumbnailBuilder (也就是缩略图),如果没有,直接构建一个 SingleRequest 返回,真实的请求实现都是 SingleRequest 兑现。
  2. 构建一个 ThumbnailRequestCoordinator 对象,它会作为后续请求的 parent。
  3. 构建主请求 fullRequest 和缩略图请求 thumbRequest,并把他们添加到 ThumbnailRequestCoordinator 中,然后将它作为结果返回。

所以到这里我们可以做一个请求构建的总结了,我们以最复杂的情况为例子来说明,也就是同时有 ErrorRequest 和 ThumbnailReqeust 的请求:

首先构建一个 ErrorRequestCoordinator,然后通过 errorRequestBuilder 构建一个 errorRequest,继续构建一个 thumbnailRequest。他们都会被添加到 ErrorRequestCoordinator 中,他们的 parent 也都是 ErrorRequestCoordinator。

构建 thumnailRequest 时,先创建一个 ThumbnailRequestCoordinator,然后构建一个 fullRequest,也就是主请求,然后构建一个 thumbnailRequest,也就是缩略图请求。他们也都被添加到 ThumbnailRequestCoordinator 中,parent 也都是它。

ErrorRequestCoordinator

我们先简单看看 ErrorRequestCoordinator#begin() 方法的实现:

Java 复制代码
  @Override
  public void begin() {
    synchronized (requestLock) {
      if (primaryState != RequestState.RUNNING) {
        primaryState = RequestState.RUNNING;
        primary.begin();
      }
    }
  }

直接触发主请求的 Request#begin()。

我们继续看看 ErrorRequestCoordinator#onRequestFailed() 方法中是如何处理失败请求的:

Java 复制代码
  @Override
  public void onRequestFailed(Request request) {
    synchronized (requestLock) {
      // 如果当前失败的请求不是 errorRequest,也就是表示主请求失败了
      if (!request.equals(error)) {
        // 触发 error#begin() 方法
        primaryState = RequestState.FAILED;
        if (errorState != RequestState.RUNNING) {
          errorState = RequestState.RUNNING;
          error.begin();
        }
        return;
      }
      
      // 以下的情况表示主请求和失败请求都失败了
      errorState = RequestState.FAILED;

      if (parent != null) {
        // 通知 parent 请求失败了
        parent.onRequestFailed(this);
      }
    }
  }

如果主请求失败,就会触发 error 请求开始;如果主请求和错误的请求都失败,就表示当前请求失败了,然后它会通知 parent。

ThumbnailRequestCoordinator

Java 复制代码
  @Override
  public void begin() {
    synchronized (requestLock) {
      isRunningDuringBegin = true;
      try {
        // 如果 full 请求没有完成,同时 thumb 也没有在请求中,触发 thumb 请求。
        if (fullState != RequestState.SUCCESS && thumbState != RequestState.RUNNING) {
          thumbState = RequestState.RUNNING;
          thumb.begin();
        }
        // 如果 full 没有在请求中,触发 full 的请求。
        if (isRunningDuringBegin && fullState != RequestState.RUNNING) {
          fullState = RequestState.RUNNING;
          full.begin();
        }
      } finally {
        isRunningDuringBegin = false;
      }
    }
  }

如果 full 请求没有完成,同时 thumb 也没有在请求中,触发 thumb 请求;如果 full 没有在请求中,触发 full 的请求。也就是它会同时触发 full 和 thumb 的请求。

我们继续看看 onRequestSuccess() 方法是如何处理请求成功的任务的:

Java 复制代码
  @Override
  public void onRequestSuccess(Request request) {
    synchronized (requestLock) {
      if (request.equals(thumb)) {
        thumbState = RequestState.SUCCESS;
        return;
      }
      fullState = RequestState.SUCCESS;
      if (parent != null) {
        parent.onRequestSuccess(this);
      }
      if (!thumbState.isComplete()) {
        thumb.clear();
      }
    }
  }

如果是 thumb 请求成功,只是简单更新状态,然后返回;如果是 full 请求成功,会通知 parent,然后把 thumb 的请求取消掉。

我们再看看它是如何处理请求失败的任务的:

Java 复制代码
  @Override
  public void onRequestFailed(Request request) {
    synchronized (requestLock) {
      if (!request.equals(full)) {
        thumbState = RequestState.FAILED;
        return;
      }
      fullState = RequestState.FAILED;

      if (parent != null) {
        parent.onRequestFailed(this);
      }
    }
  }

也是朴实无华的代码,当 thumb 请求失败了,也是简单更新状态;full 请求失败了,更新 状态,同时通知 parent。

简单介绍 Registry

Registry 中注册了许多的重要的功能组件,它是通过 RegistryFactory 来创建实例和初始化的:

Java 复制代码
  @Synthetic
  static Registry createAndInitRegistry(
      Glide glide,
      List<GlideModule> manifestModules,
      @Nullable AppGlideModule annotationGeneratedModule) {

    BitmapPool bitmapPool = glide.getBitmapPool();
    ArrayPool arrayPool = glide.getArrayPool();
    Context context = glide.getGlideContext().getApplicationContext();

    GlideExperiments experiments = glide.getGlideContext().getExperiments();

    Registry registry = new Registry();
    // 注册默认的系统组件
    initializeDefaults(context, registry, bitmapPool, arrayPool, experiments);
    // 注册自定义的组件,或者替换默认的系统组件
    initializeModules(context, glide, registry, manifestModules, annotationGeneratedModule);
    return registry;
  }

通过 initializeDefaults() 来注册系统组件,等我们后续需要时再来查看这个方法;通过 initializeModules() 来添加默认的组件,我们先来看看它的实现:

Java 复制代码
  private static void initializeModules(
      Context context,
      Glide glide,
      Registry registry,
      List<GlideModule> manifestModules,
      @Nullable AppGlideModule annotationGeneratedModule) {
    for (GlideModule module : manifestModules) {
      try {
        module.registerComponents(context, glide, registry);
      } catch (AbstractMethodError e) {
        throw new IllegalStateException(
            "Attempting to register a Glide v3 module. If you see this, you or one of your"
                + " dependencies may be including Glide v3 even though you're using Glide v4."
                + " You'll need to find and remove (or update) the offending dependency."
                + " The v3 module name is: "
                + module.getClass().getName(),
            e);
      }
    }
    if (annotationGeneratedModule != null) {
      annotationGeneratedModule.registerComponents(context, glide, registry);
    }
  }

不知道你是否还记得我们是怎么把原来的网络请求替换成 OkHttp 的?就是要通过上面方法触发我们自定义的 AppGlideModule 的 registerComponents() 方法,通过这个方法我们就可以替换原有的网络请求为 OkHttp。

看到这里你可能还是不太清楚 Registry 中具体注册了什么样功能的组件。我这里以一次简单的网络请求任务来描述一下其中会用到哪些组件:

首先我们输入的 url 是一个 String 对象,在 Glide 内部中被称为 model (它也可以是 File 类型,Drawable 类型等等),首先就需要 ModelLoader 来转换处理 model。所以 ModelLoader 就有一个输入类型和输出类型,首先要找到一个 ModelLoader 将 String 类型的 model 转换成 Url;然后继续找到一个 ModelLoader 将 Url 类型的 model 转换成 GlideUrl;最后找到将 GlideUrl 转换成 InputStream 类型的 ModelLoader,没错这个 ModelLoader 其实也就是执行的网络请求,它也是处理的最后一个 ModelLoader,最后的处理结果就需要交由下一站的组件来处理。

这也是为什么替换原有的网络请求为 OkHttp,的代码是下面这样:

Kotlin 复制代码
    override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
        super.registerComponents(context, glide, registry)
        registry.replace(
            GlideUrl::class.java,
            InputStream::class.java,
            OkHttpUrlLoader.Factory(appOkHttpClient)
        )
    }

ModelLoader 处理的下一站就是 Decoder,我们的 ModelLoader 的输出类型是 InputStream,我们就需要找到一个 Decoder 支持的输入类型是 InputStream的,把 InputStream 解码成 Bitmap。

在到下一站就是 Transcoder,同样的它也是有一个输入类型和输出类型,Decoder 传过来的类型是 Bitmap,我们需要将它处理成 ViewTarget 需要的 Drawable 类型,然后 ImageView 就能够直接渲染 Drawable 了。

我只是简单介绍了 Registry 中的 ModelLoader、Decoder 和 Transcoder 组件,他们是非常核心的组件。其中还有一些别的组件,比如在缓存到文件时就需要将 Bitmap 重新编码,这时就需要 Encoder。

最后

本篇文件介绍了 Reqeust 和 RequestCoordinator,还简单介绍了 Registry 中的核心组件(但是没有看源码)。由于本篇文章的篇幅也不短了,为了不像第一篇文章那样过长,所以后续的文章继续讲 SingleRequest 中是如何执行请求的。

相关推荐
半摆烂日常6 小时前
低代码平台API对接实践:接口鉴权与数据同步的完整实现
android·低代码·rxjava
事圆则缓7 小时前
volatile 使用场景全解析:它能保证什么,又解决不了什么
android
蒲公英内测分发7 小时前
智能投影仪参加海外展会,未上架的 Android 配套 App 怎么让客户限时试用?
android
ii_best8 小时前
按键精灵手机端开发安卓版实战:手写一个可最小化、可拖动的「运行日志悬浮窗」(附完整 源码 + 踩坑记录)
android·ios·智能手机·自动化·ai编程·按键精灵
终端安全笔记8 小时前
安卓做 MDM 管控,先分清三种注册入口
android·安全·智能手机
乌萨达9 小时前
手机模拟器安卓怎么用?电脑安装应用、玩手游与账号同步指南
android·智能手机·电脑·雷电模拟器
美狐美颜SDK开放平台9 小时前
Android与iOS直播APP美颜有什么区别?视频美颜SDK开发详解
android·ios·音视频·视频美颜sdk
程序猿阿越10 小时前
containerd如何拉取镜像
后端·kubernetes·源码阅读
春猿火11 小时前
某市-2026【网安·论道】misc-afterimage_note-wp
android
恋猫de小郭12 小时前
Shopify 回应为什么从 RN 回到原生,为什么不用 KMP ?
android·前端·flutter