从Retrofit支持suspend协程请求说开去

news2024/9/22 5:20:25

在现代Android开发中,异步请求已经成为不可或缺的一部分。传统的异步请求往往涉及大量的回调逻辑,使代码难以维护和调试。随着Kotlin协程的引入,异步编程得到了极大的简化。而作为最流行的网络请求库之一,Retrofit早在Kotlin协程的早期就开始支持suspend函数的请求。本文将从源码角度,深度解析Retrofit如何实现对suspend函数的支持,并探讨这种支持带来的开发体验的提升。

一、Retrofit的演变:从回调到协程

  1. 传统的异步请求
    在Kotlin协程出现之前,Retrofit通过回调机制处理异步请求。我们需要在请求方法中定义回调接口,Retrofit会在请求完成后调用回调函数。这种方式虽然解决了异步问题,但会导致"回调地狱"。

  2. Kotlin协程的引入
    Kotlin协程通过简洁的语法,将异步代码编写得如同同步代码一样。这种变革让我们能够更加轻松地处理复杂的异步逻辑。Retrofit也迅速跟进,增加了对suspend函数的支持,使得网络请求能够以一种更加直观的方式进行。

二、Retrofit如何支持suspend请求

2.1 Retrofit接口定义

开发者在使用Retrofit时,通常会定义一个接口,其中的方法会被Retrofit动态代理实现。自从支持协程以来,这些方法可以被声明为suspend函数。比如:

interface ApiService {
    @GET("users/{id}")
    suspend fun getUser(@Path("id") userId: String): User
}
2.2 suspend函数的实现原理

为了理解Retrofit如何支持suspend请求,我们需要从Kotlin协程的工作原理开始。Kotlin中的suspend函数会被编译器转换为一个带有Continuation参数的函数。这意味着在编译后,原本的suspend函数变成了以下形式:

public final Object getUser(String userId, Continuation<? super User> continuation) {
    // 内部实现
}

这个Continuation参数其实是一个回调接口,用于恢复协程的执行。它包含了resumeWith方法,用于在异步操作完成后继续执行协程。
在Retrofit中,针对这种转换,Retrofit使用了自定义的CallAdapter来适配这种形式。接下来,我们会深入分析CallAdapter的源码。

2.3 CallAdapter与协程的结合

Retrofit的设计中,CallAdapter用于将底层的Call对象转换为用户需要的形式。在支持协程之前,CallAdapter主要负责将Call对象转换为同步或者异步的回调请求。而在协程支持引入后,Retrofit增加了对suspend函数的支持。

以下是CallAdapter接口的定义:

public interface CallAdapter<R, T> {
 /**
   * Returns the value type that this adapter uses when converting the HTTP response body to a Java
   * object. For example, the response type for {@code Call<Repo>} is {@code Repo}. This type is
   * used to prepare the {@code call} passed to {@code #adapt}.
   *
   * <p>Note: This is typically not the same type as the {@code returnType} provided to this call
   * adapter's factory.
   */
    Type responseType();
    /**
   * Returns an instance of {@code T} which delegates to {@code call}.
   *
   * <p>For example, given an instance for a hypothetical utility, {@code Async}, this instance
   * would return a new {@code Async<R>} which invoked {@code call} when run.
   *
   * <pre><code>
   * &#64;Override
   * public &lt;R&gt; Async&lt;R&gt; adapt(final Call&lt;R&gt; call) {
   *   return Async.create(new Callable&lt;Response&lt;R&gt;&gt;() {
   *     &#64;Override
   *     public Response&lt;R&gt; call() throws Exception {
   *       return call.execute();
   *     }
   *   });
   * }
   * </code></pre>
   */
    T adapt(Call<R> call);
}

为了支持suspend,Retrofit2内部引入了SuspendForBody,它是CallAdapter的一个组合器,继承自HttpServiceMethod,专门用于处理suspend函数请求。

static final class SuspendForBody<ResponseT> extends HttpServiceMethod<ResponseT, Object> {
    private final CallAdapter<ResponseT, Call<ResponseT>> callAdapter;
    // ....
    @Override
    protected Object adapt(Call<ResponseT> call, Object[] args) {
      call = callAdapter.adapt(call);

      //noinspection unchecked Checked by reflection inside RequestFactory.
      Continuation<ResponseT> continuation = (Continuation<ResponseT>) args[args.length - 1];

      // ...
      // 实际触发最终调用
      KotlinExtensions.await(call, continuation);
    }
  }
}
// KotlinExtensions
@JvmName("awaitNullable")
suspend fun <T : Any> Call<T?>.await(): T? {
  return suspendCancellableCoroutine { continuation ->
    continuation.invokeOnCancellation {
      cancel()
    }
    enqueue(object : Callback<T?> {
      override fun onResponse(call: Call<T?>, response: Response<T?>) {
        if (response.isSuccessful) {
          continuation.resume(response.body())
        } else {
          continuation.resumeWithException(HttpException(response))
        }
      }

      override fun onFailure(call: Call<T?>, t: Throwable) {
        continuation.resumeWithException(t)
      }
    })
  }
}

上面的代码是SuspendForBody的核心逻辑。关键点如下:

  • suspendCancellableCoroutine:这是Kotlin提供的一个函数,用于将异步代码转换为协程代码。它允许你在协程中执行异步操作,并在操作完成后恢复协程。

  • invokeOnCancellation:该函数用于在协程被取消时,取消网络请求,避免资源浪费。

  • call.enqueue:Retrofit的网络请求是异步执行的,enqueue方法用于执行请求并在完成后调用回调。在回调中,成功时调用resume恢复协程,失败时调用resumeWithException抛出异常。

2.4 还有一个问题,retrofit是如何判断是否为suspend函数呢?

如上文所言,suspend函数在编译后会加入Continuation对象作为参数,

在这里插入图片描述

// retrofit2.RequestFactory.Builder#parseParameter
 try {
     if (Utils.getRawType(parameterType) == Continuation.class) {
     // 如果有参数为Continuation 则判断为suspend函数
        this.isKotlinSuspendFunction = true;
        return null;
    }
} catch (NoClassDefFoundError e) {
}                   
2.4 异常处理与协程

在协程环境中,异常处理变得更加简洁直观。Retrofit的suspend支持允许开发者直接使用try-catch块来捕获请求中的异常,而无需像过去那样处理回调中的错误。

try {
    val user = apiService.getUser("123")
} catch (e: Exception) {
    // 处理异常
}

在这个场景中,如果请求失败,Retrofit内部会调用continuation.resumeWithException(t),然后在协程中抛出异常,最终被catch捕获。这使得异常处理与同步代码中的处理方式完全一致,极大地简化了异步代码的编写。

2.5 Retrofit源码中的调度器管理

虽然SuspendForBody负责将异步请求转换为协程形式,但协程的执行依赖于调度器。Retrofit的设计默认使用了OkHttp的内部线程池来管理请求的执行。然而,开发者可以通过自定义调度器来控制请求的执行上下文,从而避免主线程阻塞等问题。

Retrofit.Builder()
    .callbackExecutor(Dispatchers.IO.asExecutor())
    .build()

通过这样的设置,可以确保网络请求在后台线程池中执行,而不会阻塞主线程。

三、总结

从支持suspend的角度来看,Retrofit展示了其在现代Android开发中的灵活性与强大性。通过源码的解析,我们可以深入理解它是如何将Kotlin的协程特性融入其中,从而带来了更加简洁、直观的编程体验。对于我们开发者而言,充分利用协程与Retrofit的结合,能够显著提升代码的可读性和可维护性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2048308.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JavaEE】深入探索SpringBoot的日志管理功能与实践应用

目录 SpringBoot 日志日志概述日志使用打印日志在程序中得到⽇志对象使用日志对象打印日志 ⽇志框架介绍(了解)⻔⾯模式(外观模式)SLF4J 框架介绍日志格式的说明⽇志级别日志级别的分类日志级别的使用 ⽇志配置配置⽇志级别⽇志持久化配置⽇志⽂件分割配置⽇志格式 更简单的⽇志…

IBMS管理平台,推动建筑行业向智能化发展

智能建筑管理系统是一套集成了多种技术和功能的综合性管理平台&#xff0c;旨在提高建筑设施的运行效率、减少能源消耗、改善室内环境质量&#xff0c;并为建筑管理者提供全面的监控、控制和分析手段。IBMS管理平台的特点和优势如下&#xff1a; IBMS系统融合了建筑自动化、能源…

2024 国内自闭症学校排名榜:突破边界,创造无限可能

在 2024 年&#xff0c;当家长们面对国内自闭症学校的排名榜时&#xff0c;心中或许充满了期待与困惑。然而&#xff0c;这些排名榜虽然能提供一定的参考&#xff0c;但绝不能成为选择学校的唯一依据。家长们更需要深入了解每所学校的真实情况&#xff0c;通过线下考察&#xf…

网络协议栈应用层的意义(内含思维导图和解析图通俗易懂超易理解)

绪论​&#xff1a; “节省时间的方法就是全力以赴的将所要做的事情完美快速的做完&#xff0c;不留返工重新学习的时间&#xff0c;才能省下时间给其他你认为重要的东西。” 本章主要讲到OSI网络协议栈中的应用层的作用和再次在应用层的角度理解协议的具体意义&#xff0c;以及…

#Datawhale AI夏令营第4期#多模态大模型Task3

写在前面的碎碎念》 为时一个礼拜的学习&#xff0c;即将结束了。回顾这一个礼拜&#xff0c;因此这次的任务较难&#xff0c;大部分的时间都花在跑模型上了&#xff0c;跑一次一天就没了&#xff0c;所以基本没有很好的去尝试优化上分&#xff0c;一个礼拜&#xff0c;差不多…

微商城系统 goods.php SQL注入漏洞复现

0x01 产品简介 微商城系统,又称微信商城系统,是基于微信等社交平台构建的一种小型电子商务系统。该系统融合了社交媒体的互动性和网络商城的交易功能,为商家提供了一个集商品展示、在线交易、营销推广、用户管理、数据分析等功能于一体的综合性电商平台。系统充分利用了微信…

【SecureLock】藏起你的秘密文件!

我们都知道&#xff0c;在 Windows 中可以右键文件夹&#xff0c;选择”属性“&#xff0c;勾选”隐藏“来实现隐藏某个文件夹。 我们还知道&#xff0c;在 Windows 中可以选择勾选 ”显示隐藏的项目和文件夹“&#xff0c;来使上述方法变得形同虚设。 本工具就是用于解决以上…

使用Linux内核自带的V4L2设备驱动 采集图像

一、定义 V4L2代表Video for Linux Two&#xff0c;它是Linux内核的一部分&#xff0c;提供了一种统一的方式来访问各种视频输入/输出设备&#xff0c;如摄像头、电视卡等。 二、工作流程&#xff08;重点&#xff09; 打开设备&#xff0d;> 检查和设置设备属性&#xf…

Elasticsearch-关键词随机查询(8.x)

目录 一、查询语句 二、Java代码实现 基础介绍&#xff1a; ES自定义评分机制:function_score查询详解-阿里云开发者社区ES自定义评分机制:function_score查询详解https://developer.aliyun.com/article/1054571 开发版本详见&#xff1a;Elasticsearch-经纬度查询(8.x-半径…

面向对象程序设计(C++)之 vector(初阶)

1. vector 的构造 vector 需要显式实例化类模版&#xff0c;在创建 vector 类型的容器时可以直接创建&#xff0c;也可以进行初始化&#xff0c;例如 v2 &#xff0c;也可以使用迭代器的方式创建&#xff0c;具体关于更多vector的知识: vector //模版类只能显式实例化 vector&l…

Linux ubuntu 24.04 安装运行《帝国时代3》免安装绿色版游戏,解决 “Could not load DATAP.BAR”等问题

Linux ubuntu 24.04 安装运行《帝国时代3》游戏&#xff0c;解决 “Could not load DATAP.BAR" 等问题 《帝国时代 3》是一款比较经典的即时战斗游戏&#xff0c;伴随了我半个高中时代&#xff0c;周末有时间就去泡网吧&#xff0c;可惜玩的都是简单人机&#xff0c;高难…

构建具有音频功能的中英翻译器:一个Python应用程序的旅程

在当今的全球化世界中&#xff0c;语言翻译工具变得越来越重要。作为一名软件开发者&#xff0c;我最近完成了一个有趣的项目&#xff1a;一个结合了翻译、文字转语音和数据管理功能的中英翻译器。在这篇博客中&#xff0c;我将分享这个应用程序的主要特性和开发过程中的一些见…

CSC7261BH PD20瓦快充芯片

CSC7261BH是一款20瓦内置高压MOS的高性能、多工作模式的PWM控制芯片&#xff0c;内置多种保护机制。当系统为空载和轻载时&#xff0c;CSC7261BH 采用Burst和Green控制模式可有效地减少了空载和轻载时的损耗。当系统为中载和重载时&#xff0c;CSC7261BH采用CCM模式可有效提升电…

【kubernetes】K8S常见的发布方式

一、K8S常见的发布方式 蓝绿发布 两套环境交替升级&#xff0c;旧版本保留一定时间便于回滚 优点&#xff1a;对用户无感&#xff0c;是最安全的发布方式&#xff0c;业务稳定 缺点&#xff1a;需要两套系统&#xff0c;对资源要求比较高&#xff0c;成本特别高 灰度发布&…

STM32标准库学习笔记-3.外部中断

参考教程&#xff1a;【STM32入门教程-2023版 细致讲解 中文字幕】 中断 中断含义&#xff1a;在计算机执行主程序运行过程中&#xff0c;出现了特定的中断触发条件&#xff08;中断源&#xff09;&#xff0c;使得CPU暂停当前正在运行的程序&#xff0c;转而去处理中断程序&…

使用docker compose一键部署 Portainer

使用docker compose一键部署 Portainer Portainer 是一款轻量级的应用&#xff0c;它提供了图形化界面&#xff0c;用于方便地管理Docker环境&#xff0c;包括单机环境和集群环境。 1、创建安装目录 mkdir /data/partainer/ -p && cd /data/partainer2、创建docker…

【C语言篇】数组和函数的实践:扫雷游戏(附源码)

文章目录 前言扫雷游戏的分析和设计扫雷游戏的功能说明游戏的分析和设计文件结构设计 扫雷游戏的代码实现初始化棋盘打印棋盘布置雷排查雷 扫雷游戏的拓展 前言 源码在最后 扫雷游戏的分析和设计 经典扫雷游戏 扫雷游戏的功能说明 使⽤控制台实现经典的扫雷游戏 游戏可以通…

8月14日微语报,星期三,农历七月十一

8月14日微语报&#xff0c;星期三&#xff0c;农历七月十一&#xff0c;工作愉快&#xff0c;生活喜乐&#xff01; 一份微语报&#xff0c;众览天下事&#xff01; 1、巴黎奥运会&#xff1a;32项次世界纪录被刷新&#xff0c;125项次奥运纪录被改写。 2、国家邮政局&#…

鸿蒙应用程序框架基础

鸿蒙应用程序框架基础 应用程序包基础知识应用的多Module设计机制Module类型 Stage模型应用程序包结构开发态包结构编译包形态发布台包结构选择合适的包类型 应用程序包基础知识 应用的多Module设计机制 **支持模块化开发&#xff1a;**一个应用通常会包含多种功能&#xff0…

【stm32项目】多功能智能家居室内灯光控制系统设计与实现(完整工程资料源码)

多功能智能家居室内灯光控制系统设计与实现 目录&#xff1a; 目录&#xff1a; 前言&#xff1a; 一、项目背景与目标 二、国内外研究现状&#xff1a; 2.1 国内研究现状&#xff1a; 2.2 国外研究现状&#xff1a; 2.3 发展趋势 三、硬件电路设计 3.1 总体概述 3.2 硬件连接总…