第2章 Retrofit基础接口定义:定义API接口方法、配置BaseUrl、使用注解描述请求

Retrofit 的核心价值在于将 HTTP API 声明为 Java 或 Kotlin 接口,通过注解直接描述请求的路径、参数、方法等,从而让网络层与业务逻辑解耦。本章将详细讲解如何定义接口方法、配置 BaseUrl 以及使用关键注解,为后续引入 WorkManager 做后台同步打下基础。

2.1 接口定义原理

Retrofit 在运行时通过动态代理将接口方法调用转换为 HTTP 请求。开发者只需提供一个普通接口(interface),在方法上添加注解(如 @GET、@POST),Retrofit 就会根据这些信息自动拼接 URL、填充参数、解析返回结果。整个过程无需开发者手动创建 HttpURLConnection 或管理线程池。

原理要点: 接口方法的方法名、参数、注解共同构成一个完整的 HTTP 请求描述;Retrofit 负责将其翻译为 OkHttp 的 Request 对象。
Retrofit 接口定义流程 Retrofit 接口定义 → 请求描述 流程 接口定义 public interface ApiService 方法 + 注解 @GET("users/{id}") 动态代理 调用方法 → 生成请求 BaseUrl + 路径 拼接完整URL OkHttp请求 执行并返回结果

2.2 核心注解说明

Retrofit 提供了丰富的注解用于描述请求。下表列出最常用的注解及其作用:

注解 作用 示例 备注
@GET 发送 GET 请求 @GET("users") 路径可包含路径参数占位符 {}
@POST 发送 POST 请求 @POST("users") 配合 @Body 发送请求体
@Path 替换 URL 中的占位符 @Path("id") int id 必须有对应的 {} 占位符
@Query 添加查询参数,如 ?page=1 @Query("page") int page 自动拼接在 URL 末尾
@Body 设置请求体(非表单) @Body User user 默认转为 JSON(需配合 Converter)
@Header 添加请求头 @Header("Authorization") 可动态传入值
@FormUrlEncoded 表示表单编码的 POST 请求 类级别注解 配合 @Field 使用
注意: 注解中的路径不能以 '/' 开头(除非你希望忽略 BaseUrl 的路径部分),且 BaseUrl 必须以 '/' 结尾。否则会导致 URL 拼接异常。

2.3 定义接口方法

以下是一个完整的接口定义示例,演示不同注解的组合:

// Kotlin 示例(Java 语法类似)
interface GitHubApi {

    @GET("users")
    suspend fun getUsers(): List

    @GET("users/{id}")
    suspend fun getUserById(@Path("id") userId: Long): User

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

    @GET("search/users")
    suspend fun searchUsers(
        @Query("q") query: String,
        @Query("per_page") perPage: Int = 20
    ): SearchResult

    @HEAD("users/{id}")
    suspend fun headUser(@Path("id") userId: Long): Long // 返回 Content-Length
}

关键点:

  • 使用 suspend 关键字支持协程(Kotlin 推荐方式),Java 则返回 Call<T>。
  • 路径参数 {id} 必须与 @Path("id") 中的名称一致。
  • @Query 参数可为空,Retrofit 会忽略 null 值。

2.4 配置 BaseUrl

BaseUrl 是接口所有请求的基础地址,通常在创建 Retrofit 实例时设置。标准做法:

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.github.com/")   // 必须以 '/' 结尾
    .addConverterFactory(GsonConverterFactory.create())
    .build()

val gitHubApi = retrofit.create(GitHubApi::class.java)
最佳实践: 将 BaseUrl 定义在全局常量中(如 BuildConfig.API_BASE_URL),便于切换测试/生产环境。同时确保末尾是 '/',否则 Retrofit 会抛出 IllegalArgumentException。

2.5 完整示例:接口定义 + 调用

实际项目中,通常会结合对象转换器一起使用。下面是一个完整的可运行片段:

// 1. 数据类
data class User(val id: Long, val login: String, val avatarUrl: String)

// 2. 接口
interface GitHubApi {
    @GET("users")
    suspend fun getUsers(): List<User>
}

// 3. 构建 Retrofit
private const val BASE_URL = "https://api.github.com/"

fun createApi(): GitHubApi {
    return Retrofit.Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build()
        .create(GitHubApi::class.java)
}

// 4. 使用协程调用
suspend fun loadUsers(): List<User> {
    val api = createApi()
    return api.getUsers()
}

2.6 调试与常见错误

  • BaseUrl 尾部缺少 '/': Retrofit 启动时会抛出 IllegalArgumentException: baseUrl must end with /。
  • 路径注解写死 '/' 开头: 如 @GET("/users"),Retrofit 会忽略 BaseUrl 的路径部分,只保留 Scheme + Host,导致端口或路径丢失。
  • 缺少 ConverterFactory: 如果接口返回非 ResponseBody 或 String,必须添加对应的 Converter(如 Gson、Moshi),否则运行时抛出 IllegalArgumentException: ... Unable to create converter for ...。
  • 调试方法: 在 OkHttpClient 中加入 HttpLoggingInterceptor 即可打印完整请求 URL 和参数。配置代码:
val logging = HttpLoggingInterceptor().apply {
    level = HttpLoggingInterceptor.Level.BASIC   // 或 HEADERS / BODY
}
val client = OkHttpClient.Builder()
    .addInterceptor(logging)
    .build()

val retrofit = Retrofit.Builder()
    .baseUrl(BASE_URL)
    .client(client)   // 注入自定义 client
    .addConverterFactory(GsonConverterFactory.create())
    .build()
稳定性优化: 可为 OkHttpClient 设置超时时间,避免接口定义返回慢时永久阻塞:
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)