上一篇 下一篇 回到顶部 目录 返回首页
目录

Retrofit 从入门到精通:类型安全的 HTTP 客户端实战全指南

发表于
更新于
8 68.5~88.0 分钟 30808

前言

Retrofit 是 Square 公司开源的 类型安全 HTTP 客户端库,官方定义只有一句话:

“Retrofit turns your HTTP API into a Java interface.”

它将 HTTP API 抽象为 Java(或 Kotlin)接口,通过注解描述请求方法、URL、参数和返回值,底层基于 OkHttp 执行真正的网络请求。这种设计使得网络层代码极度简洁——开发者只需要关注接口定义,而不需要写冗长的网络请求代码。

本文以 Retrofit 官方文档为基准,结合实际开发场景,从零开始逐步深入到高级用法,覆盖:依赖安装、核心注解、转换器、适配器、拦截器、Kotlin 协程、文件上传下载、统一封装、源码解析等。


第一部分:入门篇

1.1 Retrofit 的核心设计思想

在传统 HTTP 请求中,你需要手动拼接 URL、构建 RequestBody、解析 JSON、处理线程切换……而 Retrofit 将这些重复工作全部封装在注解和工厂模式中:

┌──────────────────────────────────────────────────────┐
│  Your Code                                          │
│  interface GitHubService {                           │
│      @GET("users/{user}/repos")                      │
│      Call<List<Repo>> listRepos(@Path("user") String) │
│  }                                                   │
└──────────────────────┬───────────────────────────────┘
                       │ Retrofit.create()
                       ▼
┌──────────────────────────────────────────────────────┐
│  Retrofit (注解解析 → 动态代理 → 构建 OkHttp Request) │
└──────────────────────┬───────────────────────────────┘
                       │
                       ▼
┌──────────────────────────────────────────────────────┐
│  OkHttp (网络连接 → 请求发送 → 响应接收)               │
└──────────────────────┬───────────────────────────────┘
                       │
                       ▼
┌──────────────────────────────────────────────────────┐
│  Converter (JSON → Java/Kotlin 对象)                  │
└──────────────────────────────────────────────────────┘

1.2 依赖安装

以 Android Gradle 项目为例,Retrofit 3.x 的依赖如下:

dependencies {
    // Retrofit 核心库
    implementation("com.squareup.retrofit2:retrofit:2.11.0")
    
    // Gson 转换器(JSON 序列化/反序列化)
    implementation("com.squareup.retrofit2:converter-gson:2.11.0")
    
    // OkHttp(Retrofit 底层网络库,通常显式引入以便使用拦截器)
    implementation("com.squareup.okhttp3:okhttp:4.12.0")
    
    // 日志拦截器(开发环境调试用)
    implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
}

注意:Retrofit 本身不包含 JSON 解析能力,需要搭配转换器(Converter)使用。最常用的是 Gson,但如果你使用 Kotlin,Moshi 是更好的选择(Square 出品,性能更好,对 Kotlin 数据类友好)。

1.3 第一个 Retrofit 接口

按照官方文档的经典示例:

public interface GitHubService {
    @GET("users/{user}/repos")
    Call<List<Repo>> listRepos(@Path("user") String user);
}

这个接口声明了一个 GET 请求:

  • @GET("users/{user}/repos") 指定请求路径

  • {user} 是路径占位符,会被 @Path("user") 参数的实际值替换

  • 返回值 Call<List<Repo>> 是 Retrofit 的泛型封装,Repo 是自定义的数据类

1.4 创建 Retrofit 实例

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.github.com/")
        .addConverterFactory(GsonConverterFactory.create())
        .build();

GitHubService service = retrofit.create(GitHubService.class);

关键配置说明:

方法

说明

baseUrl()

必须调用,设置 API 的根地址,必须以 / 结尾

addConverterFactory()

添加数据转换器,将 JSON 转为 Java 对象

addCallAdapterFactory()

添加适配器,支持 RxJava、Kotlin 协程等

client()

传入自定义的 OkHttpClient

callbackExecutor()

指定回调执行线程(Android 默认在主线程)

重要:Retrofit 实例应当全局单例复用。创建 Retrofit 实例的成本较高(反射扫描、注解解析),且每个实例内部有缓存。一个应用通常只需要一个 Retrofit 实例。

1.5 发起请求

Call<List<Repo>> call = service.listRepos("octocat");

// 同步请求(会阻塞当前线程)
List<Repo> repos = call.execute().body();

// 异步请求(回调在指定线程执行)
call.enqueue(new Callback<List<Repo>>() {
    @Override
    public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
        if (response.isSuccessful()) {
            List<Repo> repos = response.body();
            // 处理数据
        }
    }

    @Override
    public void onFailure(Call<List<Repo>> call, Throwable t) {
        // 网络异常、超时等
    }
});

同步 vs 异步的选择

  • 同步请求不能在 Android 主线程调用,否则会抛出 NetworkOnMainThreadException

  • 异步请求通过 enqueue() 发起,回调默认在主线程(Android 场景),可直接更新 UI

  • 一个 Call 对象只能执行一次,如需重复请求需调用 clone()


第二部分:注解篇

2.1 HTTP 方法注解

Retrofit 提供了 7 个 HTTP 方法注解,覆盖了所有 RESTful 操作:

public interface ApiService {
    // GET — 获取资源
    @GET("users/{id}")
    Call<User> getUser(@Path("id") String id);

    // POST — 创建资源
    @POST("users")
    Call<User> createUser(@Body User user);

    // PUT — 完整更新资源
    @PUT("users/{id}")
    Call<User> updateUser(@Path("id") String id, @Body User user);

    // PATCH — 局部更新资源
    @PATCH("users/{id}")
    Call<User> patchUser(@Path("id") String id, @Body Map<String, Object> fields);

    // DELETE — 删除资源
    @DELETE("users/{id}")
    Call<Void> deleteUser(@Path("id") String id);

    // HEAD — 仅获取响应头(适合检查资源是否存在)
    @HEAD("users/{id}")
    Call<Void> headUser(@Path("id") String id);

    // HTTP — 自定义 HTTP 方法(当以上注解不满足时)
    @HTTP(method = "CUSTOM", path = "users/{id}", hasBody = true)
    Call<ResponseBody> customRequest(@Path("id") String id, @Body RequestBody body);
}

2.2 URL 参数注解

2.2.1 @Path — 路径参数替换

@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId);

// 调用: groupList(1) → GET /group/1/users

注意@Path 默认会对参数值进行 URL 编码。如果路径本身就是已编码好的(如 a/b/c),可以设置 encoded = true

@GET("files/{path}")
Call<ResponseBody> downloadFile(@Path(value = "path", encoded = true) String filePath);
// 调用: downloadFile("a/b/c") → GET /files/a/b/c

2.2.2 @Query — 查询参数

@GET("group/{id}/users")
Call<List<User>> groupList(
    @Path("id") int groupId,
    @Query("page") int page,
    @Query("size") int size,
    @Query("sort") String sort
);

// 调用: groupList(1, 2, 20, "asc")
// → GET /group/1/users?page=2&size=20&sort=asc

2.2.3 @QueryMap — 动态查询参数集合

当查询参数不固定时,用 @QueryMap 传入 Map:

@GET("group/{id}/users")
Call<List<User>> groupList(
    @Path("id") int groupId,
    @QueryMap Map<String, String> options
);

// 调用: groupList(1, Map.of("page", "2", "size", "20"))
// → GET /group/1/users?page=2&size=20

2.2.4 @Url — 动态 URL

有时 URL 在编译时无法确定(如第三方回调地址、动态跳转链接),使用 @Url

@GET
Call<ResponseBody> downloadFile(@Url String fileUrl);

// 调用: downloadFile("https://example.com/large-file.zip")

注意:使用 @Url 时不需要在方法注解上写 path。如果 baseUrl 有路径前缀,@Url 会拼接在后面。如果 @Urlhttp://https:// 开头,则完全使用绝对地址。

2.3 请求体注解

2.3.1 @Body — 对象转请求体

@Body 将 Java/Kotlin 对象通过 Converter 序列化为请求体:

@POST("users")
Call<User> createUser(@Body User user);

如果配置了 Gson 转换器,User 对象会被自动转为 JSON 字符串,并设置 Content-Type: application/json

2.3.2 @Field / @FieldMap — 表单字段

发送 application/x-www-form-urlencoded 格式的表单,需要配合 @FormUrlEncoded 注解:

@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(
    @Field("name") String name,
    @Field("email") String email,
    @Field("age") int age
);

// → Content-Type: application/x-www-form-urlencoded
// → name=张三&email=test%40example.com&age=25

动态表单字段用 @FieldMap

@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@FieldMap Map<String, String> fields);

2.3.3 @Part / @PartMap — 多部分请求(文件上传)

发送 multipart/form-data 请求需要配合 @Multipart 注解:

@Multipart
@POST("user/avatar")
Call<ResponseBody> uploadAvatar(
    @Part("description") RequestBody description,
    @Part MultipartBody.Part avatar
);

调用示例:

// 文本部分
RequestBody descBody = RequestBody.create(
    "My avatar", MediaType.parse("text/plain"));

// 文件部分
File file = new File("avatar.jpg");
RequestBody fileBody = RequestBody.create(
    file, MediaType.parse("image/jpeg"));
MultipartBody.Part part = MultipartBody.Part.createFormData(
    "avatar", file.getName(), fileBody);

service.uploadAvatar(descBody, part).enqueue(...);

也可以用 @PartMap 上传多个文件:

@Multipart
@POST("upload/multi")
Call<ResponseBody> uploadMultiple(@PartMap Map<String, RequestBody> files);

2.4 请求头注解

2.4.1 @Header — 动态请求头

@GET("users")
Call<List<User>> getUsers(@Header("Authorization") String token);

2.4.2 @Headers — 静态请求头

@Headers({
    "Accept: application/json",
    "Cache-Control: max-age=600"
})
@GET("users")
Call<List<User>> getUsers();

2.4.3 @HeaderMap — 动态请求头集合

@GET("users")
Call<List<User>> getUsers(@HeaderMap Map<String, String> headers);

2.5 特殊注解

2.5.1 @Streaming — 流式响应

用于大文件下载,避免将整个响应体加载到内存:

@Streaming
@GET("files/{name}")
Call<ResponseBody> downloadFile(@Path("name") String name);

返回的 ResponseBody 需要手动读取流:

Response<ResponseBody> response = call.execute();
InputStream is = response.body().byteStream();
// 手动写入文件或 SD 卡

2.5.2 @FormUrlEncoded / @Multipart — 标记注解

这两个注解不是方法注解,而是标记注解,告诉 Retrofit 请求体的编码格式:

  • @FormUrlEncoded — 表单编码(application/x-www-form-urlencoded

  • @Multipart — 多部分编码(multipart/form-data

2.6 注解完整速查表

注解

类型

说明

@GET

方法

GET 请求

@POST

方法

POST 请求

@PUT

方法

PUT 请求

@DELETE

方法

DELETE 请求

@PATCH

方法

PATCH 请求

@HEAD

方法

HEAD 请求

@HTTP

方法

自定义 HTTP 方法

@Path

参数

路径参数替换

@Query

参数

查询参数

@QueryMap

参数

查询参数集合

@Body

参数

请求体对象

@Field

参数

表单字段

@FieldMap

参数

表单字段集合

@Part

参数

多部分请求体

@PartMap

参数

多部分请求体集合

@Header

参数

动态请求头

@HeaderMap

参数

请求头集合

@Headers

方法

静态请求头

@FormUrlEncoded

方法

表单编码标记

@Multipart

方法

多部分编码标记

@Streaming

方法

流式响应

@Url

参数

动态完整 URL


第三部分:Converter 转换器

3.1 为什么需要 Converter

Retrofit 的底层网络请求返回的是 ResponseBody(字节流)。Converter 的作用是将这些字节流自动转换为你需要的 Java/Kotlin 对象。

HTTP Response Body (JSON)
        │
        ▼
┌───────────────────┐
│   Converter       │ ← GsonConverterFactory / MoshiConverterFactory / ...
└────────┬──────────┘
         ▼
    User { name, email }

3.2 常用 Converter 一览

转换器

依赖

适用场景

Gson

converter-gson

最通用,Java/Kotlin 均可

Moshi

converter-moshi

Kotlin 项目首选,支持 Codegen

Jackson

converter-jackson

已有 Jackson 生态的项目

Protobuf

converter-protobuf

gRPC / Protocol Buffers

Wire

converter-wire

Square 自家的 Protobuf 替代

SimpleXML

converter-simplexml

XML 格式响应

Scalars

converter-scalars

返回 String / int / boolean 等基础类型

3.3 添加多个 Converter

Retrofit 支持注册多个 Converter,按注册顺序匹配:

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.example.com/")
        .addConverterFactory(ScalarsConverterFactory.create())  // 优先匹配基础类型
        .addConverterFactory(GsonConverterFactory.create())     // 再匹配 JSON 对象
        .build();

匹配规则:Retrofit 按添加顺序依次尝试,找到第一个能处理该返回类型的 Converter。所以基础类型转换器要放在前面。

3.4 Moshi 转换器(Kotlin 项目推荐)

Moshi 是 Square 为 Kotlin 优化的 JSON 库,相比 Gson 有以下优势:

  • 原生支持 Kotlin 数据类(默认值、非空检查)

  • 支持 Codegen 模式(编译期生成适配器,零反射)

  • 更好的性能和更小的包体积

// build.gradle.kts
dependencies {
    implementation("com.squareup.retrofit2:converter-moshi:2.11.0")
    implementation("com.squareup.moshi:moshi-kotlin:1.15.1")
    kapt("com.squareup.moshi:moshi-kotlin-codegen:1.15.1")  // Codegen 模式
}
@JsonClass(generateAdapter = true)
data class User(
    val id: Long,
    val name: String,
    val email: String? = null
)

val moshi = Moshi.Builder().build()
val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .addConverterFactory(MoshiConverterFactory.create(moshi))
    .build()

3.5 自定义 Converter

当 API 返回的数据不是标准 JSON(如纯文本、自定义加密格式)时,需要自定义 Converter:

public class StringConverterFactory extends Converter.Factory {
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(
            Type type, Annotation[] annotations, Retrofit retrofit) {
        if (type == String.class) {
            return new Converter<ResponseBody, String>() {
                @Override
                public String convert(ResponseBody value) throws IOException {
                    return value.string();
                }
            };
        }
        return null;  // 不能处理,交给下一个 Converter
    }

    @Override
    public Converter<?, RequestBody> requestBodyConverter(
            Type type, Annotation[] parameterAnnotations,
            Annotation[] methodAnnotations, Retrofit retrofit) {
        if (type == String.class) {
            return (value) -> RequestBody.create(
                (String) value, MediaType.parse("text/plain"));
        }
        return null;
    }
}

第四部分:CallAdapter 适配器

4.1 什么是 CallAdapter

默认情况下,Retrofit 接口方法的返回值必须是 Call<T>。CallAdapter 让你可以用其他类型替代 Call,比如:

  • RxJavaObservable<T> / Flowable<T>

  • Kotlin 协程suspend 函数直接返回 T

  • Java 8CompletableFuture<T>

4.2 Kotlin 协程适配器

Retrofit 2.6+ 内置了对 Kotlin 协程的支持,无需额外添加适配器,只需要将方法声明为 suspend

interface ApiService {
    @GET("users/{id}")
    suspend fun getUser(@Path("id") id: Long): User

    @GET("users")
    suspend fun listUsers(@Query("page") page: Int): List<User>
}

调用时直接在协程中使用:

lifecycleScope.launch {
    try {
        val user = api.getUser(1L)
        // 直接使用 user
    } catch (e: Exception) {
        // 处理异常
    }
}

4.3 RxJava 适配器

如果你使用 RxJava,需要添加适配器:

implementation("com.squareup.retrofit2:adapter-rxjava3:2.11.0")
Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.example.com/")
        .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
        .addConverterFactory(GsonConverterFactory.create())
        .build();

interface ApiService {
    @GET("users/{id}")
    Observable<User> getUser(@Path("id") String id);
}

第五部分:OkHttp 拦截器

5.1 拦截器的定位

严格来说,拦截器属于 OkHttp 的功能,但 Retrofit 的网络请求全部走 OkHttp,所以在实际开发中,拦截器是 Retrofit 方案中不可或缺的一环。

OkHttp 的拦截器分为两种:

类型

添加方式

执行时机

适用场景

Application Interceptor

addInterceptor()

应用层,不关心底层网络细节

Token 注入、日志、公共参数

Network Interceptor

addNetworkInterceptor()

网络层,能访问原始请求/响应

重定向处理、重试判断

App Interceptor → OkHttp (重试、缓存、连接池) → Network Interceptor → Server

5.2 日志拦截器

开发时最常用的是 HttpLoggingInterceptor,可以查看请求和响应的完整信息:

HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);

OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(logging)
        .build();

日志级别:

级别

说明

NONE

不记录日志(生产环境推荐)

BASIC

仅记录请求方法、URL、响应状态码、耗时

HEADERS

记录 Basic + 请求头/响应头

BODY

记录 Headers + 请求体/响应体

生产环境注意BODY 级别会打印敏感数据(密码、Token 等),上线前应关闭或降级为 NONE

5.3 Token 注入拦截器

自动为每个请求添加认证 Token:

public class AuthInterceptor implements Interceptor {
    private final String token;

    public AuthInterceptor(String token) {
        this.token = token;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request().newBuilder()
                .addHeader("Authorization", "Bearer " + token)
                .build();
        return chain.proceed(request);
    }
}

5.4 公共参数拦截器

为所有 GET 请求统一添加公共查询参数(如 App 版本、设备 ID):

public class CommonParamsInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request original = chain.request();
        HttpUrl url = original.url().newBuilder()
                .addQueryParameter("app_version", "2.1.0")
                .addQueryParameter("device_id", "xxx-xxx-xxx")
                .build();

        Request request = original.newBuilder()
                .url(url)
                .build();
        return chain.proceed(request);
    }
}

5.5 Token 过期自动刷新

Token 过期时自动刷新并重试原始请求:

public class TokenRefreshInterceptor implements Interceptor {
    private final SharedPreferences prefs;

    @Override
    public Response response = chain.proceed(chain.request());

    // 401 说明 Token 过期
    if (response.code() == 401) {
        synchronized (this) {
            String newToken = refreshToken();
            if (newToken != null) {
                // 用新 Token 重试
                Request newRequest = chain.request().newBuilder()
                        .header("Authorization", "Bearer " + newToken)
                        .build();
                response.close();
                return chain.proceed(newRequest);
            }
        }
    }
    return response;
    }

    private String refreshToken() {
        // 调用刷新 Token 的 API
        // 返回新 Token 或 null
    }
}

5.6 缓存拦截器

使用 OkHttp 内置缓存,减少不必要的网络请求:

// 配置缓存目录和大小
File cacheDir = new File(context.getCacheDir(), "http-cache");
Cache cache = new Cache(cacheDir, 10 * 1024 * 1024); // 10MB

OkHttpClient client = new OkHttpClient.Builder()
        .cache(cache)
        .addInterceptor(new CacheInterceptor())
        .build();

// 自定义缓存策略
public class CacheInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();

        // 无网络时强制使用缓存
        if (!isNetworkAvailable()) {
            request = request.newBuilder()
                    .header("Cache-Control", "public, only-if-cached, max-stale=86400")
                    .build();
        }

        Response response = chain.proceed(request);

        // 有网络时仍然缓存(默认 OkHttp 只对特定 Cache-Control 响应缓存)
        String cacheControl = "public, max-age=60";
        return response.newBuilder()
                .header("Cache-Control", cacheControl)
                .build();
    }
}

第六部分:Kotlin 协程实战

6.1 基础用法

如前所述,Retrofit 2.6+ 直接支持 suspend 函数,无需额外配置适配器:

interface ApiService {
    @GET("articles")
    suspend fun getArticles(
        @Query("page") page: Int,
        @Query("size") size: Int
    ): ApiResponse<List<Article>>
}

6.2 统一响应体封装

实际项目中通常需要一个统一的响应包装类来处理错误码:

data class ApiResponse<T>(
    val code: Int,
    val message: String,
    val data: T?
) {
    val isSuccess: Boolean
        get() = code == 200
}

// 使用
lifecycleScope.launch {
    val response = api.getArticles(1, 20)
    if (response.isSuccess) {
        adapter.submitList(response.data)
    } else {
        Toast.makeText(context, response.message, Toast.LENGTH_SHORT).show()
    }
}

6.3 Flow 流式数据

对于需要多次发射数据的场景(如分页、WebSocket),可以使用 Kotlin Flow:

interface ApiService {
    @GET("articles")
    fun getArticlesFlow(
        @Query("page") page: Int,
        @Query("size") size: Int
    ): Flow<ApiResponse<List<Article>>> = flow {
        var currentPage = page
        while (true) {
            val response = getArticles(currentPage, size)
            emit(response)
            if (response.data.isNullOrEmpty() || response.data.size < size) break
            currentPage++
        }
    }
}

// 使用
lifecycleScope.launch {
    api.getArticlesFlow(1, 20)
        .catch { e -> Log.e("Flow", "Error", e) }
        .collect { response ->
            adapter.submitList(response.data)
        }
}

6.4 协程 + ViewModel + StateFlow 标准架构

Android MVVM 标准写法:

class ArticleViewModel(
    private val api: ApiService
) : ViewModel() {
    private val _uiState = MutableStateFlow<ArticleUiState>(ArticleUiState.Loading)
    val uiState: StateFlow<ArticleUiState> = _uiState

    fun loadArticles(page: Int) {
        viewModelScope.launch {
            _uiState.value = ArticleUiState.Loading
            try {
                val response = api.getArticles(page, 20)
                _uiState.value = if (response.isSuccess) {
                    ArticleUiState.Success(response.data!!)
                } else {
                    ArticleUiState.Error(response.message)
                }
            } catch (e: Exception) {
                _uiState.value = ArticleUiState.Error(e.message ?: "Unknown error")
            }
        }
    }
}

sealed class ArticleUiState {
    object Loading : ArticleUiState()
    data class Success(val articles: List<Article>) : ArticleUiState()
    data class Error(val message: String) : ArticleUiState()
}

第七部分:文件上传与下载

7.1 单文件上传

interface UploadService {
    @Multipart
    @POST("upload/avatar")
    suspend fun uploadAvatar(
        @Part("userId") userId: RequestBody,
        @Part avatar: MultipartBody.Part
    ): ApiResponse<UploadResult>
}

// 调用
suspend fun uploadAvatarImage(userId: String, imageFile: File) {
    val userIdBody = userId.toRequestBody(MediaType.parse("text/plain"))
    val fileBody = imageFile.asRequestBody(MediaType.parse("image/*"))
    val part = MultipartBody.Part.createFormData("avatar", imageFile.name, fileBody)

    val result = uploadService.uploadAvatar(userIdBody, part)
    if (result.isSuccess) {
        println("上传成功: ${result.data?.url}")
    }
}

7.2 多文件上传

interface UploadService {
    @Multipart
    @POST("upload/album")
    suspend fun uploadPhotos(
        @Part("albumId") albumId: RequestBody,
        @Part photos: List<MultipartBody.Part>
    ): ApiResponse<UploadResult>
}

// 调用
suspend fun uploadAlbum(albumId: String, files: List<File>) {
    val parts = files.map { file ->
        val body = file.asRequestBody(MediaType.parse("image/*"))
        MultipartBody.Part.createFormData("photos", file.name, body)
    }
    uploadService.uploadPhotos(albumId.toRequestBody(MediaType.parse("text/plain")), parts)
}

7.3 文件下载

interface DownloadService {
    @Streaming
    @GET
    suspend fun downloadFile(@Url fileUrl: String): ResponseBody
}

// 调用(带进度显示)
suspend fun downloadWithProgress(url: String, destFile: File) {
    val response = downloadService.downloadFile(url)
    val totalBytes = response.contentLength()
    val inputStream = response.byteStream()

    destFile.outputStream().use { output ->
        val buffer = ByteArray(8 * 1024)
        var bytesRead: Int
        var totalRead = 0L
        while (inputStream.read(buffer).also { bytesRead = it } != -1) {
            output.write(buffer, 0, bytesRead)
            totalRead += bytesRead
            val progress = if (totalBytes > 0) (totalRead * 100 / totalBytes).toInt() else -1
            // 更新 UI 进度
        }
    }
}

第八部分:工程化封装

8.1 Retrofit 单例管理

object RetrofitClient {
    private const val BASE_URL = "https://api.example.com/"

    val instance: ApiService by lazy {
        val logging = HttpLoggingInterceptor().apply {
            level = if (BuildConfig.DEBUG) {
                HttpLoggingInterceptor.Level.BODY
            } else {
                HttpLoggingInterceptor.Level.NONE
            }
        }

        val authInterceptor = AuthInterceptor(getToken())

        val client = OkHttpClient.Builder()
            .addInterceptor(logging)
            .addInterceptor(authInterceptor)
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)
            .retryOnConnectionFailure(true)
            .build()

        val retrofit = Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(client)
            .addConverterFactory(GsonConverterFactory.create())
            .build()

        retrofit.create(ApiService::class.java)
    }
}

8.2 统一异常处理

sealed class NetworkResult<out T> {
    data class Success<out T>(val data: T) : NetworkResult<T>()
    data class Error(val code: Int, val message: String) : NetworkResult<Nothing>()
    object Loading : NetworkResult<Nothing>()
}

suspend fun <T> safeApiCall(
    apiCall: suspend () -> T
): NetworkResult<T> = try {
    val response = apiCall()
    NetworkResult.Success(response)
} catch (e: HttpException) {
    when (e.code()) {
        401 -> NetworkResult.Error(401, "未授权,请重新登录")
        403 -> NetworkResult.Error(403, "没有权限")
        404 -> NetworkResult.Error(404, "资源不存在")
        else -> NetworkResult.Error(e.code(), e.message())
    }
} catch (e: SocketTimeoutException) {
    NetworkResult.Error(-1, "请求超时,请检查网络")
} catch (e: UnknownHostException) {
    NetworkResult.Error(-1, "无法连接服务器")
} catch (e: IOException) {
    NetworkResult.Error(-1, "网络异常:${e.message}")
} catch (e: Exception) {
    NetworkResult.Error(-1, "未知错误:${e.message}")
}

// 使用
lifecycleScope.launch {
    val result = safeApiCall { api.getUser(1L) }
    when (result) {
        is NetworkResult.Success -> showUser(result.data)
        is NetworkResult.Error -> showError(result.message)
        else -> {}
    }
}

8.3 多 baseUrl 支持

当一个项目需要请求多个不同域名时:

object ApiManager {
    private val defaultClient = OkHttpClient.Builder()
        .addInterceptor(HttpLoggingInterceptor().apply {
            level = HttpLoggingInterceptor.Level.BODY
        })
        .build()

    // 主 API
    val mainApi: MainApiService by lazy {
        Retrofit.Builder()
            .baseUrl("https://api.main.com/")
            .client(defaultClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(MainApiService::class.java)
    }

    // 第三方 API
    val thirdPartyApi: ThirdPartyApiService by lazy {
        Retrofit.Builder()
            .baseUrl("https://api.thirdparty.com/")
            .client(defaultClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ThirdPartyApiService::class.java)
    }
}

第九部分:高级主题

9.1 动态 baseUrl(使用 @Url)

如果同一个接口需要请求不同环境的服务器:

interface ApiService {
    @POST("sync")
    suspend fun sync(@Url baseUrl: String, @Body data: SyncData): ApiResponse<Void>
}

// 调用
api.sync("https://staging.example.com/", syncData)
api.sync("https://prod.example.com/", syncData)

9.2 请求取消

在页面销毁时取消未完成的请求,避免内存泄漏:

// Call 方式
val call = service.listRepos("octocat")
call.cancel()  // 取消单个请求

// OkHttpClient 级别取消(批量)
val call = service.listRepos("octocat")
call.cancel()  // 取消单个请求

// 协程方式(自动随 CoroutineScope 生命周期取消)
lifecycleScope.launch {
    val user = api.getUser(1L)  // 当 lifecycleScope 取消时,请求自动取消
}

9.3 动态添加 Header(通过动态代理)

如果需要为所有接口动态添加 Header,除了拦截器外还可以使用动态代理方式:

public class HeaderInterceptorProxy implements InvocationHandler {
    private final Object delegate;
    private final Map<String, String> headers;

    public HeaderInterceptorProxy(Object delegate, Map<String, String> headers) {
        this.delegate = delegate;
        this.headers = headers;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在调用前修改 Header
        // 这种方式更灵活但复杂度较高,通常拦截器更推荐
        return method.invoke(delegate, args);
    }
}

9.4 请求重试

OkHttp 本身不支持自动重试,可以通过拦截器实现:

public class RetryInterceptor implements Interceptor {
    private final int maxRetries;

    public RetryInterceptor(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = null;
        IOException lastException = null;

        for (int i = 0; i <= maxRetries; i++) {
            try {
                response = chain.proceed(request);
                if (response.isSuccessful()) {
                    return response;
                }
                response.close();
            } catch (IOException e) {
                lastException = e;
            }
        }

        throw lastException != null ? lastException : new IOException("Retry failed");
    }
}

9.5 Mock API(测试环境)

开发阶段可以使用 Mock 数据,不依赖真实后端:

// 方案一:使用 MockWebServer(Square 官方测试库)
dependencies {
    testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0")
}

@Test
fun testGetUser() = runBlocking {
    val server = MockWebServer()
    server.enqueue(MockResponse()
        .setBody("""{"id":1,"name":"test","email":"test@test.com"}""")
        .setResponseCode(200))
    server.start()

    val retrofit = Retrofit.Builder()
        .baseUrl(server.url("/"))
        .addConverterFactory(GsonConverterFactory.create())
        .build()

    val api = retrofit.create(ApiService::class.java)
    val user = api.getUser(1L)

    assertEquals("test", user.name)
    server.shutdown()
}

9.6 Retrofit 3.x 新特性

Retrofit 3.x 的最新版本带来了一些变化:

  • 最低 Java 版本提升到 Java 8+

  • 对 Kotlin 的兼容性更好

  • 部分内部 API 重构

  • 依赖版本需要与 OkHttp 4.x 匹配

迁移建议:如果你在使用 Retrofit 2.x,升级到 3.x 基本不需要改代码,但要注意 Java/Kotlin 版本要求和依赖兼容性。


第十部分:常见问题 FAQ

Q1: 为什么 Retrofit 不自己实现网络请求?

Retrofit 专注于"将 HTTP API 转化为类型安全的接口",网络层交给 OkHttp。这种职责分离让两个库都能保持精简和专注。OkHttp 本身就是一个成熟的 HTTP 客户端,提供了连接池、GZIP、缓存、重试等功能。

Q2: Retrofit 和 OkHttp 是什么关系?

  • Retrofit:网络请求的封装层,负责将接口注解转化为 OkHttp 的 Request

  • OkHttp:底层 HTTP 客户端,负责实际的网络通信

  • 你可以单独使用 OkHttp,但不能单独使用 Retrofit(Retrofit 内部必须依赖 OkHttp)

Q3: 能不能在主线程发同步请求?

Android 4.0+ 禁止在主线程执行网络操作,否则会抛出 NetworkOnMainThreadException。同步请求应该在后台线程执行,异步请求的回调默认在主线程(Android 场景下)。

Q4: Retrofit 支持 WebSocket 吗?

不支持。WebSocket 应该使用 OkHttp 直接实现:

val client = OkHttpClient()
val request = Request.Builder().url("wss://example.com/ws").build()
val ws = client.newWebSocket(request, object : WebSocketListener() {
    override fun onMessage(webSocket: WebSocket, text: String) {
        // 处理消息
    }
})

Q5: 如何查看 Retrofit 的源码?

Retrofit 的源码在 GitHub 上公开。核心类:

  • Retrofit.java — 核心构建器

  • ServiceMethod.java — 注解解析

  • OkHttpCall.java — OkHttp 适配

  • Platform.java — 平台适配(Android / JVM)

Q6: 如何调试 Retrofit 请求?

  1. 添加 HttpLoggingInterceptor(最直接)

  2. 使用 Charles / Fiddler 等代理工具

  3. Android Studio 的 Network Profiler

  4. 使用 Stetho(Facebook 出品,可在 Chrome DevTools 查看网络请求)

Q7: 文件上传时如何显示进度?

Retrofit 本身不直接支持上传进度回调。需要自定义 RequestBody 并重写 writeTo 方法:

class ProgressRequestBody(
    private val file: File,
    private val listener: (bytesWritten: Long, totalBytes: Long) -> Unit
) : RequestBody() {
    override fun contentType() = MediaType.parse("application/octet-stream")
    override fun contentLength() = file.length()

    override fun writeTo(sink: BufferedSink) {
        val buffer = ByteArray(8 * 1024)
        var written = 0L
        file.inputStream().use { input ->
            var read: Int
            while (input.read(buffer).also { read = it } != -1) {
                sink.write(buffer, 0, read)
                written += read
                listener(written, contentLength())
            }
        }
    }
}

附录:完整项目模板

// === 1. 数据类 ===
data class User(val id: Long, val name: String, val email: String)

data class ApiResponse<T>(
    val code: Int,
    val message: String,
    val data: T?
)

// === 2. API 接口 ===
interface ApiService {
    @GET("users/{id}")
    suspend fun getUser(@Path("id") id: Long): ApiResponse<User>

    @POST("users")
    suspend fun createUser(@Body user: User): ApiResponse<User>

    @GET("users")
    suspend fun listUsers(
        @Query("page") page: Int,
        @Query("size") size: Int
    ): ApiResponse<List<User>>

    @Multipart
    @POST("users/{id}/avatar")
    suspend fun uploadAvatar(
        @Path("id") id: Long,
        @Part avatar: MultipartBody.Part
    ): ApiResponse<Unit>
}

// === 3. Retrofit 单例 ===
object NetworkModule {
    private const val BASE_URL = "https://api.example.com/"

    private val okHttpClient by lazy {
        OkHttpClient.Builder()
            .addInterceptor(HttpLoggingInterceptor().apply {
                level = if (BuildConfig.DEBUG) BODY else NONE
            })
            .addInterceptor(AuthInterceptor { getToken() })
            .connectTimeout(15, TimeUnit.SECONDS)
            .readTimeout(15, TimeUnit.SECONDS)
            .build()
    }

    val api by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }
}

// === 4. ViewModel ===
class UserViewModel : ViewModel() {
    private val api = NetworkModule.api
    private val _userState = MutableStateFlow<UserUiState>(UserUiState.Loading)
    val userState: StateFlow<UserUiState> = _userState

    fun loadUser(id: Long) {
        viewModelScope.launch {
            _userState.value = UserUiState.Loading
            try {
                val response = api.getUser(id)
                _userState.value = if (response.code == 200) {
                    UserUiState.Success(response.data!!)
                } else {
                    UserUiState.Error(response.message)
                }
            } catch (e: Exception) {
                _userState.value = UserUiState.Error(e.message ?: "未知错误")
            }
        }
    }
}

sealed class UserUiState {
    object Loading : UserUiState()
    data class Success(val user: User) : UserUiState()
    data class Error(val message: String) : UserUiState()
}

总结

Retrofit 的核心思想可以用官方的一句话概括:

“Retrofit turns your HTTP API into a Java interface.”

掌握 Retrofit,你只需要理解三个概念:

  1. 注解描述请求@GET@POST@Path@Body

  2. Converter 转换数据 — Gson / Moshi 将 JSON 转为对象

  3. OkHttp 拦截器处理通用逻辑 — Token、日志、重试、缓存

从入门的接口定义到高级的自定义 Converter、拦截器链、Kotlin 协程集成,Retrofit 用极简的设计覆盖了几乎所有网络请求场景。希望这篇指南能帮助你从"会用"走向"精通"。