彻底搞懂 Retrofit:使用、封装与 Converter 原理
版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/
Retrofit 简介
Retrofit 是 Square 公司开发的一个 类型安全的 HTTP 网络请求库,广泛应用于 Android 开发中。它极大地简化了 REST API 的调用方式,让网络请求就像调用本地接口一样简单。
基于 OkHttp 实现底层通信,提供了:
注解方式定义 API 接口
自动将 JSON/XML 响应解析为 Java/Kotlin 对象
支持多种 Converter(如 Gson、Moshi、Protobuf)
支持协程、RxJava、LiveData 等异步处理方式
与 OkHttp 关系:
Retrofit 是高级封装,负责将请求/响应转换为 Java 对象
OkHttp 是底层网络传输库,处理连接、缓存、拦截器等
Retrofit 默认使用 OkHttp,可以无缝集成 OkHttp 的功能(如添加 Token 拦截器)
开源地址:https://github.com/square/retrofit
集成 Retrofit
在 app 的 build.gradle.kts 添加如下依赖:
// Retrofit 核心库
implementation("com.squareup.retrofit2:retrofit:2.9.0")
// JSON 转换器(使用 Gson)
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
// OkHttp
implementation("com.squareup.okhttp3:okhttp:4.12.0")
// OkHttp 日志拦截器(可选,用于调试)
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
然后点击 “Sync Now” 进行 Gradle 同步。
编辑 AndroidManifest.xml,添加访问网络权限:
<uses-permission android:name="android.permission.INTERNET" />
使用实例
1. 定义数据类
package com.cyrus.example.retrofit.model
data class User(
val id: Int,
val name: String,
val email: String
)
2. Retrofit 接口定义(API Interface)
使用注解描述请求类型、路径、参数等:
package com.cyrus.example.retrofit.network
import com.cyrus.example.retrofit.model.User
import retrofit2.http.GET
import retrofit2.http.Path
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") id: Int): User
}
3. Retrofit 实例构建
package com.cyrus.example.retrofit.network
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object RetrofitClient {
private const val BASE_URL = "https://jsonplaceholder.typicode.com/"
private val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
private val client = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
val api: ApiService by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
}
4. 发起请求
CoroutineScope(Dispatchers.IO).launch {
try {
val user = RetrofitClient.api.getUser(1)
resultText = "用户1: ${user.name} (${user.email})"
} catch (e: Exception) {
resultText = "请求失败: ${e.message}"
}
}
Retrofit 会自动将请求结果 JSON 响应解析为 Kotlin 对象
日志输出如下:
2025-07-17 23:01:50.355 3886-18211 okhttp.OkHttpClient com.cyrus.example I --> GET https://jsonplaceholder.typicode.com/users/1
2025-07-17 23:01:50.355 3886-18211 okhttp.OkHttpClient com.cyrus.example I --> END GET
2025-07-17 23:01:52.416 3886-18211 okhttp.OkHttpClient com.cyrus.example I <-- 200 https://jsonplaceholder.typicode.com/users/1 (2060ms)
2025-07-17 23:01:52.417 3886-18211 okhttp.OkHttpClient com.cyrus.example I date: Thu, 17 Jul 2025 15:01:52 GMT
2025-07-17 23:01:52.417 3886-18211 okhttp.OkHttpClient com.cyrus.example I content-type: application/json; charset=utf-8
2025-07-17 23:01:52.417 3886-18211 okhttp.OkHttpClient com.cyrus.example I access-control-allow-credentials: true
2025-07-17 23:01:52.417 3886-18211 okhttp.OkHttpClient com.cyrus.example I cache-control: max-age=43200
2025-07-17 23:01:52.417 3886-18211 okhttp.OkHttpClient com.cyrus.example I etag: W/"1fd-+2Y3G3w049iSZtw5t1mzSnunngE"
2025-07-17 23:01:52.417 3886-18211 okhttp.OkHttpClient com.cyrus.example I expires: -1
2025-07-17 23:01:52.417 3886-18211 okhttp.OkHttpClient com.cyrus.example I nel: {"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}
2025-07-17 23:01:52.418 3886-18211 okhttp.OkHttpClient com.cyrus.example I pragma: no-cache
2025-07-17 23:01:52.418 3886-18211 okhttp.OkHttpClient com.cyrus.example I report-to: {"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=YWQRu2P%2FvZPdRO%2BFKUJHT1Kv87wV6qY2%2BEllROkIyHs%3D\u0026sid=e11707d5-02a7-43ef-b45e-2cf4d2036f7d\u0026ts=1751617599"}],"max_age":3600}
2025-07-17 23:01:52.418 3886-18211 okhttp.OkHttpClient com.cyrus.example I reporting-endpoints: heroku-nel="https://nel.heroku.com/reports?s=YWQRu2P%2FvZPdRO%2BFKUJHT1Kv87wV6qY2%2BEllROkIyHs%3D&sid=e11707d5-02a7-43ef-b45e-2cf4d2036f7d&ts=1751617599"
2025-07-17 23:01:52.418 3886-18211 okhttp.OkHttpClient com.cyrus.example I server: cloudflare
2025-07-17 23:01:52.418 3886-18211 okhttp.OkHttpClient com.cyrus.example I vary: Origin, Accept-Encoding
2025-07-17 23:01:52.418 3886-18211 okhttp.OkHttpClient com.cyrus.example I via: 2.0 heroku-router
2025-07-17 23:01:52.418 3886-18211 okhttp.OkHttpClient com.cyrus.example I x-content-type-options: nosniff
2025-07-17 23:01:52.419 3886-18211 okhttp.OkHttpClient com.cyrus.example I x-powered-by: Express
2025-07-17 23:01:52.419 3886-18211 okhttp.OkHttpClient com.cyrus.example I x-ratelimit-limit: 1000
2025-07-17 23:01:52.419 3886-18211 okhttp.OkHttpClient com.cyrus.example I x-ratelimit-remaining: 999
2025-07-17 23:01:52.419 3886-18211 okhttp.OkHttpClient com.cyrus.example I x-ratelimit-reset: 1751617644
2025-07-17 23:01:52.419 3886-18211 okhttp.OkHttpClient com.cyrus.example I age: 21523
2025-07-17 23:01:52.419 3886-18211 okhttp.OkHttpClient com.cyrus.example I cf-cache-status: HIT
2025-07-17 23:01:52.419 3886-18211 okhttp.OkHttpClient com.cyrus.example I server-timing: cfCacheStatus;desc="HIT"
2025-07-17 23:01:52.419 3886-18211 okhttp.OkHttpClient com.cyrus.example I server-timing: cfEdge;dur=5,cfOrigin;dur=0
2025-07-17 23:01:52.420 3886-18211 okhttp.OkHttpClient com.cyrus.example I cf-ray: 960a9df9ab308969-PDX
2025-07-17 23:01:52.420 3886-18211 okhttp.OkHttpClient com.cyrus.example I alt-svc: h3=":443"; ma=86400
2025-07-17 23:01:52.424 3886-18211 okhttp.OkHttpClient com.cyrus.example I {
2025-07-17 23:01:52.424 3886-18211 okhttp.OkHttpClient com.cyrus.example I "id": 1,
2025-07-17 23:01:52.424 3886-18211 okhttp.OkHttpClient com.cyrus.example I "name": "Leanne Graham",
2025-07-17 23:01:52.424 3886-18211 okhttp.OkHttpClient com.cyrus.example I "username": "Bret",
2025-07-17 23:01:52.424 3886-18211 okhttp.OkHttpClient com.cyrus.example I "email": "Sincere@april.biz",
2025-07-17 23:01:52.424 3886-18211 okhttp.OkHttpClient com.cyrus.example I "address": {
2025-07-17 23:01:52.424 3886-18211 okhttp.OkHttpClient com.cyrus.example I "street": "Kulas Light",
2025-07-17 23:01:52.424 3886-18211 okhttp.OkHttpClient com.cyrus.example I "suite": "Apt. 556",
2025-07-17 23:01:52.424 3886-18211 okhttp.OkHttpClient com.cyrus.example I "city": "Gwenborough",
2025-07-17 23:01:52.424 3886-18211 okhttp.OkHttpClient com.cyrus.example I "zipcode": "92998-3874",
2025-07-17 23:01:52.424 3886-18211 okhttp.OkHttpClient com.cyrus.example I "geo": {
2025-07-17 23:01:52.425 3886-18211 okhttp.OkHttpClient com.cyrus.example I "lat": "-37.3159",
2025-07-17 23:01:52.425 3886-18211 okhttp.OkHttpClient com.cyrus.example I "lng": "81.1496"
2025-07-17 23:01:52.425 3886-18211 okhttp.OkHttpClient com.cyrus.example I }
2025-07-17 23:01:52.425 3886-18211 okhttp.OkHttpClient com.cyrus.example I },
2025-07-17 23:01:52.425 3886-18211 okhttp.OkHttpClient com.cyrus.example I "phone": "1-770-736-8031 x56442",
2025-07-17 23:01:52.425 3886-18211 okhttp.OkHttpClient com.cyrus.example I "website": "hildegard.org",
2025-07-17 23:01:52.425 3886-18211 okhttp.OkHttpClient com.cyrus.example I "company": {
2025-07-17 23:01:52.425 3886-18211 okhttp.OkHttpClient com.cyrus.example I "name": "Romaguera-Crona",
2025-07-17 23:01:52.425 3886-18211 okhttp.OkHttpClient com.cyrus.example I "catchPhrase": "Multi-layered client-server neural-net",
2025-07-17 23:01:52.425 3886-18211 okhttp.OkHttpClient com.cyrus.example I "bs": "harness real-time e-markets"
2025-07-17 23:01:52.425 3886-18211 okhttp.OkHttpClient com.cyrus.example I }
2025-07-17 23:01:52.425 3886-18211 okhttp.OkHttpClient com.cyrus.example I }
2025-07-17 23:01:52.425 3886-18211 okhttp.OkHttpClient com.cyrus.example I <-- END HTTP (509-byte body)
请求失败
日志输出如下:
2025-07-17 23:23:59.994 3886-19788 okhttp.OkHttpClient com.cyrus.example I --> GET https://jsonplaceholder.typicode.com/users/2
2025-07-17 23:23:59.994 3886-19788 okhttp.OkHttpClient com.cyrus.example I --> END GET
2025-07-17 23:25:10.160 3886-19788 okhttp.OkHttpClient com.cyrus.example I <-- HTTP FAILED: java.net.SocketTimeoutException: failed to connect to jsonplaceholder.typicode.com/104.21.64.1 (port 443) from /192.168.0.101 (port 47128) after 10000ms
当请求出错时会抛出异常,现在是通过 try-catch 去捕获处理请求出错的情况,但这样做代码中会存在大量 try-catch。
如何更优雅的处理请求结果?
更优雅地处理请求结果
封装一个更优雅的 request 方法来实现以下功能:
自动在 IO 线程中执行网络请求;
自动在主线程中处理结果;
支持链式调用;
不再依赖外部 try-catch。
代码实现如下:
package com.cyrus.example.retrofit.network
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class RequestResult<T>(
private val block: suspend () -> T
) {
private var success: ((T) -> Unit)? = null
private var failure: ((Throwable) -> Unit)? = null
fun onSuccess(block: (T) -> Unit): RequestResult<T> {
success = block
return this
}
fun onFailure(block: (Throwable) -> Unit): RequestResult<T> {
failure = block
return this
}
fun launch(scope: CoroutineScope) {
scope.launch(Dispatchers.IO) {
try {
val result = block()
withContext(Dispatchers.Main) {
success?.invoke(result)
}
} catch (e: Throwable) {
withContext(Dispatchers.Main) {
failure?.invoke(e)
}
}
}
}
}
fun <T> request(block: suspend () -> T): RequestResult<T> {
return RequestResult(block)
}
调用方式如下:
request {
RetrofitClient.api.getUser(2)
}.onSuccess {
resultText = "用户2: ${it.name} (${it.email})"
}.onFailure {
resultText = "❌ 请求失败:${it.message}"
}.launch(coroutineScope)
请求成功
请求失败
Retrofit 是如何实现类型转换的?
Retrofit 实现类型转换的核心在于其 Converter 转换器机制,它可以将 HTTP 请求的 body 或响应的 body 与 Java/Kotlin 对象之间互相转换。
1. 接口定义与创建代理
接口方法定义
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") id: Int): User
}
创建接口的 Service 实例。
retrofit.create(ApiService::class.java)
Retrofit 动态代理解析注解,构建一个 ServiceMethod,封装了请求构建器、参数处理器、响应解析器等。
Retrofit.create(MyApi.class)
└──-> validateServiceInterface(MyApi.class) // 检查传入的是接口、没有泛型参数等
└──-> Proxy.newProxyInstance(...) // 创建接口代理
└──-> ServiceMethod.loadServiceMethod(method) // 缓存 + 构建 ServiceMethod
└──-> ServiceMethod.parseAnnotations(retrofit, method) // 根据方法注解生成实际的 ServiceMethod 实例,它是 Retrofit 中对“接口方法”的抽象表示,封装了请求构建、响应转换等逻辑。
└──-> RequestFactory.parseAnnotations(...) // 解析方法和参数注解,构造请求路径、Query、Body、Header 等,生成一个 RequestFactory,能用于构造 okhttp3.Request。
└── new Builder(...).build()
└── parseParameterAnnotations(...):
for each parameter
└── parseParameterAnnotation(annotation, ...)
↳ if (annotation is @Body)
└── retrofit.requestBodyConverter(type, parameterAnnotations, methodAnnotations)
↳ 遍历 List<Converter.Factory> converterFactories
↳ 返回 Converter<T, RequestBody>
└──-> HttpServiceMethod.parseAnnotations(...) // 生成具体类型的 ServiceMethod 子类,用于实际发起请求并处理返回值。
└──-> 调用 CallAdapter 来适配返回值
└──-> 调用 Converter 将响应体 ResponseBody 转为目标类型
Retrofit 通过 Proxy.newProxyInstance 创建接口的动态代理。每个接口方法调用都会触发 invoke(…)
return Proxy.newProxyInstance(
service.getClassLoader(),
new Class<?>[] {service},
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) {
ServiceMethod<?> serviceMethod = loadServiceMethod(method);
return serviceMethod.invoke(args); // 触发网络请求
}
}
);
2. Retrofit 请求 → 响应 → responseConverter
API 接口函数(如 suspend fun getUser())
↓
Retrofit.create(...) → 动态代理 → invoke()
↓
ServiceMethod.invoke(args)
↓
HttpServiceMethod.adapt(Call<ResponseT>, args)
↓
CallAdapter.adapt(...) → Kotlin 协程适配器
↓
OkHttpCall.enqueue(...) 异步请求
↓
OkHttp 拦截器链发起真实网络请求
↓
网络响应返回(Response)
↓
responseConverter.convert(ResponseBody) → JSON 解析成对象
↓
回到协程 → Kotlin 数据类对象返回
当有多个转换器 Retrofit 如何决定使用哪个?
数据模型与 Converter 之间是如何关联的?当有多个转换器 Retrofit 如何决定使用哪个?
你可以在注册 Converter 时同时添加 Gson 和 Protobuf,比如:
Retrofit.Builder()
.baseUrl("https://your.api/")
.addConverterFactory(ProtobufConverterFactory.create()) // 优先匹配 protobuf
.addConverterFactory(GsonConverterFactory.create()) // fallback 到 Gson
.build()
这样,如果你的接口参数/返回值是 MessageLite 类型(protobuf 的基类),就会走 ProtobufConverterFactory;否则 fallback 到 Gson。
如果多个 Converter 都能处理某类型怎么办?
Retrofit 的行为是:只使用最先注册的那个可以处理的 Converter。
Converter 与类型的关联是如何发生的?
Retrofit 持有一组 Converter.Factory 列表 converterFactories
调用 responseBodyConverter(…) 方法遍历所有 factory
哪个 factory 返回了 非 null 的 Converter,就代表这个 factory “能处理”这个类型,直接使用这个 Converter
第一个非 null 的 converter 被使用,其余跳过
/**
* Returns a {@link Converter} for {@link ResponseBody} to {@code type} from the available
* {@linkplain #converterFactories() factories}.
*/
public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations) {
return nextResponseBodyConverter(null, type, annotations);
}
/**
* 实际执行查找匹配 converter 的方法。
*
* @param skipPast 从哪个 Converter.Factory 之后开始查找(通常用于链式调用时跳过之前已经尝试过的)
* @param type 数据模型的类型,例如 User::class.java、List<User>::class.java 等
* @param annotations 方法或参数上的注解,可用于影响选择(如 @Streaming)
*/
public <T> Converter<ResponseBody, T> nextResponseBodyConverter(
@Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {
// 检查参数不为空
Objects.requireNonNull(type, "type == null");
Objects.requireNonNull(annotations, "annotations == null");
// 找出从哪个 Factory 开始查找(如 skipPast = null,则从 0 开始)
int start = converterFactories.indexOf(skipPast) + 1;
// 遍历所有注册的 Converter.Factory
for (int i = start, count = converterFactories.size(); i < count; i++) {
// 调用工厂方法尝试获取能处理该类型的 Converter
Converter<ResponseBody, ?> converter =
converterFactories.get(i).responseBodyConverter(type, annotations, this);
if (converter != null) {
//noinspection unchecked
return (Converter<ResponseBody, T>) converter;
}
}
// 如果没有任何一个工厂返回非空 converter,说明 Retrofit 无法处理这个类型,抛出异常
StringBuilder builder =
new StringBuilder("Could not locate ResponseBody converter for ")
.append(type)
.append(".\n");
// 打印哪些工厂被跳过
if (skipPast != null) {
builder.append(" Skipped:");
for (int i = 0; i < start; i++) {
builder.append("\n * ").append(converterFactories.get(i).getClass().getName());
}
builder.append('\n');
}
// 打印尝试过的工厂
builder.append(" Tried:");
for (int i = start, count = converterFactories.size(); i < count; i++) {
builder.append("\n * ").append(converterFactories.get(i).getClass().getName());
}
throw new IllegalArgumentException(builder.toString());
}
/**
* 用于将数据类型转换为字符串(如 @Query("name") 参数),匹配逻辑同样遍历 converterFactories。
*/
public <T> Converter<T, String> stringConverter(Type type, Annotation[] annotations) {
Objects.requireNonNull(type, "type == null");
Objects.requireNonNull(annotations, "annotations == null");
for (int i = 0, count = converterFactories.size(); i < count; i++) {
Converter<?, String> converter =
converterFactories.get(i).stringConverter(type, annotations, this);
if (converter != null) {
//noinspection unchecked
return (Converter<T, String>) converter;
}
}
// 没找到匹配,退而求其次使用默认的 toString 转换器
//noinspection unchecked
return (Converter<T, String>) BuiltInConverters.ToStringConverter.INSTANCE;
}
GsonConverterFactory
GsonConverterFactory 是 Retrofit 中基于 Gson 实现的一个 Converter.Factory。它负责为 Retrofit 提供序列化(Java/Kotlin → JSON)和反序列化(JSON → Java/Kotlin)的能力。
GsonConverterFactory 能够处理几乎所有类型,因为它依赖 Gson 的泛型适配能力:Gson 在运行时可以通过类型推导构造适当的 TypeAdapter,而不是硬编码某个具体的类型。
override fun responseBodyConverter(
type: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
): Converter<ResponseBody, *>? {
val adapter: TypeAdapter<*> = gson.getAdapter(TypeToken.get(type))
return GsonResponseBodyConverter(gson, adapter)
}
gson.getAdapter(TypeToken.get(type)) 它会根据 type 生成对应的 TypeAdapter<T>,并交给 GsonResponseBodyConverter 使用。
所以,一般 GsonConverterFactory 放最后兜底。
如何自定义转换器?
如果某个接口返回的是纯文本(如 text/plain),并且你希望直接以 String 形式接收,可以通过自定义 Converter.Factory 来实现对特定类型(如 String.class)的支持。
示例:自定义 StringResponseConverterFactory
package com.cyrus.example.retrofit.network
import okhttp3.ResponseBody
import retrofit2.Converter
import retrofit2.Retrofit
import java.lang.reflect.Type
class StringResponseConverterFactory : Converter.Factory() {
override fun responseBodyConverter(
type: Type, annotations: Array<Annotation>, retrofit: Retrofit
): Converter<ResponseBody, *>? {
// 仅处理 String 类型的响应
return if (type == String::class.java) {
Converter<ResponseBody, String> { body -> body.string() }
} else {
null
}
}
}
当你初始化 Retrofit 时,将该工厂放在 GsonConverterFactory 之前:
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(StringResponseConverterFactory())
.addConverterFactory(GsonConverterFactory.create())
.build()
接口定义示例:
package com.cyrus.example.retrofit.network
import retrofit2.http.GET
interface ApiService {
@GET("users/1")
suspend fun getPlainText(): String
}
接口调用示例:
request {
RetrofitClient.api.getPlainText()
}.onSuccess {
resultText = it
}.onFailure {
resultText = "❌ 请求失败:${it.message}"
}.launch(coroutineScope)
效果如下: