Retrofit 从入门到精通:类型安全的 HTTP 客户端实战全指南
前言
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);
关键配置说明:
重要: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会拼接在后面。如果@Url以http://或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 注解完整速查表
第三部分:Converter 转换器
3.1 为什么需要 Converter
Retrofit 的底层网络请求返回的是 ResponseBody(字节流)。Converter 的作用是将这些字节流自动转换为你需要的 Java/Kotlin 对象。
HTTP Response Body (JSON)
│
▼
┌───────────────────┐
│ Converter │ ← GsonConverterFactory / MoshiConverterFactory / ...
└────────┬──────────┘
▼
User { name, email }
3.2 常用 Converter 一览
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,比如:
RxJava 的
Observable<T>/Flowable<T>Kotlin 协程 的
suspend函数直接返回TJava 8 的
CompletableFuture<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 的拦截器分为两种:
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();
日志级别:
生产环境注意:
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 请求?
添加
HttpLoggingInterceptor(最直接)使用 Charles / Fiddler 等代理工具
Android Studio 的 Network Profiler
使用 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,你只需要理解三个概念:
注解描述请求 —
@GET、@POST、@Path、@Body等Converter 转换数据 — Gson / Moshi 将 JSON 转为对象
OkHttp 拦截器处理通用逻辑 — Token、日志、重试、缓存
从入门的接口定义到高级的自定义 Converter、拦截器链、Kotlin 协程集成,Retrofit 用极简的设计覆盖了几乎所有网络请求场景。希望这篇指南能帮助你从"会用"走向"精通"。