当前位置: 首页 > news >正文

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

在现代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;// ....@Overrideprotected 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#parseParametertry {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的结合,能够显著提升代码的可读性和可维护性。

相关文章:

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

在现代Android开发中&#xff0c;异步请求已经成为不可或缺的一部分。传统的异步请求往往涉及大量的回调逻辑&#xff0c;使代码难以维护和调试。随着Kotlin协程的引入&#xff0c;异步编程得到了极大的简化。而作为最流行的网络请求库之一&#xff0c;Retrofit早在Kotlin协程的…...

深入浅出:你需要了解的用户数据报协议(UDP)

文章目录 **UDP概述****1. 无连接性****2. 尽最大努力交付****3. 面向报文****4. 多种交互通信支持****5. 较少的首部开销** **UDP报文的首部格式****详细解释每个字段** **UDP的多路分用模型****多路分用的实际应用** **检验和的计算方法****伪首部的详细内容****检验和计算步…...

C++的Magic Static

什么是“Magic Static”&#xff1f; C 中&#xff0c;函数内部的静态变量只会在第一次执行该函数时被初始化&#xff0c;而且这种初始化在 C11 标准之后是线程安全的。这意味着即使多个线程同时第一次调用该函数&#xff0c;静态变量也只会被初始化一次&#xff0c;并且在初始…...

vscode添加宏定义

1 起因 在用vscode看项目代码时&#xff0c;如果源文件中的代码块被某个宏定义给包裹住了&#xff0c;则在vscode的默认配置下&#xff0c;不会高亮显示这块被包裹住的代码&#xff0c;如下图中229行开始的代码被STM32F40_41xxx所控制&#xff0c;没有高亮显示。 由于STM32F4…...

Postman接口关联

接口关联 接口之间存在依赖关系&#xff0c;接口B要依赖于接口A的返回值。 例如&#xff1a;现在有两个接口&#xff0c;接口1&#xff1a;获取接口统一鉴权码token接口&#xff0c;接口2&#xff1a;创建标签接口。接口2里的请求参数需要依赖接口1返回的值&#xff0c;即需要…...

用Python制作开心消消乐游戏|附源码

制作一个完整的“开心消消乐”风格的游戏在Python中是一个相对复杂的项目&#xff0c;因为它涉及到图形界面、游戏逻辑、动画效果以及用户交互等多个方面。不过&#xff0c;我可以为你提供一个简化的版本和概念框架&#xff0c;帮助你理解如何开始这个项目&#xff0c;并提供一…...

ArcGIS10.8 安装教程

目录 一、环境及安装包准备 二、安装流程 1、解压安装包ArcGIS_108.rar 2、安装 三、汉化 四、激活 五、自定义菜单&#xff08;可选&#xff09; 六、打开软件按查看 七、安装过程中出现的报错 八、其他 一、环境及安装包准备 安装环境&#xff1a;win7 安装包下载…...

2024网络安全学习路线,最全保姆级教程,学完直接拿捏!

关键词&#xff1a; 网络安全入门、渗透测试学习、零基础学安全、网络安全学习路线 首先咱们聊聊&#xff0c;学习网络安全方向通常会有哪些问题 前排提示&#xff1a;文末有CSDN独家网络安全资料包&#xff01; 1、打基础时间太长 学基础花费很长时间&#xff0c;光语言都有…...

Apache Doris 中Compaction问题分析和典型案例

说明 此文档主要说明一些常见compaction问题的排查思路和临时处理手段。这些问题包括 Compaction socre高Compaction失败compaction占用资源多Compaction core 如果问题紧急&#xff0c;可联系社区同学处理 如果阅读中有问题&#xff0c;可以反馈给社区同学。 1 compaction …...

redis面试(十七)MultiLock加锁和释放锁

MultiLock MultiLock&#xff0c;英语直译为多个锁。 redisson分布式锁中的MultiLock这个机制&#xff0c;可以将多个锁合并为一个大锁&#xff0c;对一个大锁进行统一的申请加锁以及释放锁 一次性锁定多个资源&#xff0c;再去处理一些事情&#xff0c;然后事后一次性释放所…...

电脑开机LOGO修改教程_BIOS启动图片替换方法

准备工具&#xff1a;刷BIOS神器和change logo&#xff0c;打包下载地址&#xff1a;https://download.csdn.net/download/baiseled/89374686 一.打开刷BIOS神器&#xff0c;点击备份BIOS&#xff0c;保存到桌面 二.打开change logo&#xff0c;1.点击load image&#xff0c;选…...

微前端架构的持续集成与持续部署实践

在软件开发中&#xff0c;持续集成&#xff08;Continuous Integration, CI&#xff09;和持续部署&#xff08;Continuous Deployment, CD&#xff09;是实现高效、自动化软件交付的关键实践。微前端架构通过将应用拆分为多个自治的子应用&#xff0c;带来了开发和部署上的灵活…...

【STM32 FreeRTOS】事件标志组

事件标志组简介 事件标志组&#xff1a;用一个比特位来表示事件是否发生 事件标志组是一组事件标志位的集合&#xff0c;可以简单理解为事件标志组就是一个整数。 事件标志组的特点&#xff1a; 它的每一位表示一个事件&#xff08;高八位不算&#xff09;每一位事件的含义…...

【启动centos报错】另一个程序已锁定文件的一部分,进程无法访问,打不开磁盘.

启动centos报错 另一个程序已锁定文件的一部分&#xff0c;进程无法访问打不开磁盘“D:\Program2\CentOS\CentOS7\CentOS7.vmdk”或它所依赖的某个快照磁盘。模块“Disk”启动失败。未能启动虚拟机。解决方法 删除.lck文件...

基于YOLOv8-pose的手部关键点检测(3)- 实现实时手部关键点检测

目录 前言 1.扩大检测框区域 2.先检测手部&#xff0c;后检测手部关键点 3.正面视角检测 4.侧面视角检测 5.摄像头视角检测 6.遮挡视角检测 7.结论 前言 使用YOLOv8-m对图像进行手部检测&#xff0c;然后扩大检测框区域&#xff0c;并对该区域使用YOLOv8-s-pose使用关键…...

kylin系统永久关闭iptables

1 关闭iptables, 并且相关规则写入文件firewall.rules sudo iptables-save > /root/firewall.rules iptables -X iptables -t nat -F iptables -t nat -X iptables -t mangle -F iptables -t mangle -X iptables -P INPUT ACCEPT iptables -P FORWARD ACCEPT iptables -P …...

写一个githubDemo

1.List组件 <template><div class"container"><!-- 展示用户列表 --><div class"row"><divv-show"info.users.length"v-for"(item, index) in info.users":key"item.id"><div class"…...

java入门-成员内部类和静态内部类的访问

&#xff08;一&#xff09;成员内部类 package InnerClass;import javax.print.attribute.standard.MediaSize;public class Outer {//2外部类中的成员private int age99;public static String a;public class Inner{//普通的成员内部类//1.1成员变量public String name;priva…...

ansible【自动化配置】(thirty day)

回顾 1、mysql和python &#xff08;1&#xff09;不需要执行mysql_ssl_rsa_setup &#xff08;2&#xff09;Change_master_to.不需要get public key 2、可以使用pymysql非交互的管理mysql &#xff08;1&#xff09;connpymysql.connect(host,user,password,database,prot) …...

GitOps Tekton+ArgoCD

GitOps 提供了一种基于 Git 的操作理念&#xff0c;而 Tekton 和 ArgoCD 分别作为 CI/CD 工具&#xff0c;共同实现了这一理念在 Kubernetes 集群中的应用 k8s只是jenkins 流水线中的一环&#xff0c;但是在tekton中&#xff0c;k8s是基础设施 工作流程&#xff1a; 代码提交…...

(十)学生端搭建

本次旨在将之前的已完成的部分功能进行拼装到学生端&#xff0c;同时完善学生端的构建。本次工作主要包括&#xff1a; 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

(二)TensorRT-LLM | 模型导出(v0.20.0rc3)

0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述&#xff0c;后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作&#xff0c;其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...

解锁数据库简洁之道:FastAPI与SQLModel实战指南

在构建现代Web应用程序时&#xff0c;与数据库的交互无疑是核心环节。虽然传统的数据库操作方式&#xff08;如直接编写SQL语句与psycopg2交互&#xff09;赋予了我们精细的控制权&#xff0c;但在面对日益复杂的业务逻辑和快速迭代的需求时&#xff0c;这种方式的开发效率和可…...

自然语言处理——Transformer

自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效&#xff0c;它能挖掘数据中的时序信息以及语义信息&#xff0c;但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN&#xff0c;但是…...

爬虫基础学习day2

# 爬虫设计领域 工商&#xff1a;企查查、天眼查短视频&#xff1a;抖音、快手、西瓜 ---> 飞瓜电商&#xff1a;京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空&#xff1a;抓取所有航空公司价格 ---> 去哪儿自媒体&#xff1a;采集自媒体数据进…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

SQL慢可能是触发了ring buffer

简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...

LOOI机器人的技术实现解析:从手势识别到边缘检测

LOOI机器人作为一款创新的AI硬件产品&#xff0c;通过将智能手机转变为具有情感交互能力的桌面机器人&#xff0c;展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家&#xff0c;我将全面解析LOOI的技术实现架构&#xff0c;特别是其手势识别、物体识别和环境…...

FFmpeg avformat_open_input函数分析

函数内部的总体流程如下&#xff1a; avformat_open_input 精简后的代码如下&#xff1a; int avformat_open_input(AVFormatContext **ps, const char *filename,ff_const59 AVInputFormat *fmt, AVDictionary **options) {AVFormatContext *s *ps;int i, ret 0;AVDictio…...