首页 / Android 入门教程 / Retrofit 入门

Android 入门教程

Retrofit 入门

本教程共 100 篇 · 第 70 篇 · 更新于 2026-07-28 · 约 8 分钟阅读

AndroidAndroid 入门教程Retrofit网络REST协程OkHttp

70. Retrofit 入门

本节目标:把 Retrofit 跑起来,学会定义 API 接口、构造 Retrofit 实例、用 suspend 方法发 GET/POST 请求,把 JSON 自动解析成 Kotlin 对象。

上一章讲了网络基础,知道 Android 联网要权限、要子线程、要 HTTPS。这一章直接上 Retrofit,这是目前 Android 网络开发的事实标准。

Retrofit 是什么

Retrofit 是 Square 公司出的类型安全 HTTP 客户端,底层是 OkHttp。

打个比方,OkHttp 像你自己手写 HTTP 报文发出去再收回来,每步都得管;Retrofit 像给你配了个翻译,你只管用 Kotlin 写接口方法,它帮你把方法调用翻译成 HTTP 请求,再把响应翻译成 Kotlin 对象。

它的好处:

  • 类型安全:方法返回 User 就是 User,不用手动解析 JSON。
  • 接口化:一个接口对应一组 API,方法上贴注解(@GET@POST)说明 HTTP 方法和路径。
  • 编译期校验:URL 写错、参数对不上直接编译报错。
  • 协程原生支持:方法加 suspend,自动切线程。

四步搭起来

第一步:加依赖

打开 app/build.gradle.kts

dependencies {
    // Retrofit
    implementation("com.squareup.retrofit2:retrofit:2.11.0")
    // Moshi 转换器(推荐,比 Gson 更适配 Kotlin)
    implementation("com.squareup.retrofit2:converter-moshi:2.11.0")
    implementation("com.squareup.moshi:moshi-kotlin:1.15.1")
    // 如果用 Gson 也行
    // implementation("com.squareup.retrofit2:converter-gson:2.11.0")
}

加 INTERNET 权限到 AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET" />
Note

Moshi 和 Gson 都能解析 JSON。Moshi 是 Square 出的,对 Kotlin 的可空类型、默认值支持更好,新项目建议用 Moshi。本教程两种都讲,主线用 Moshi。

第二步:定义数据类

后端返回的 JSON 长这样:

{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere",
  "body": "quia et suscipit..."
}

对应 Kotlin 数据类:

import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class Post(
    val userId: Int,
    val id: Int,
    val title: String,
    val body: String
)

字段名和 JSON 的 key 一致就行。不一致用 @Json(name = "...") 映射:

data class Post(
    @Json(name = "user_id") val userId: Int,  // JSON 里是 user_id
    val id: Int,
    val title: String,
    val body: String
)
Tip

Moshi 的 @JsonClass(generateAdapter = true) 会在编译期生成解析代码,比运行时反射快得多,还能减少启动开销。记得配 KSP 才能用。

第三步:定义 API 接口

接口里每个方法对应一个 HTTP 请求:

import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Query

interface PostApi {

    @GET("posts")
    suspend fun listPosts(): List<Post>

    @GET("posts/{id}")
    suspend fun getPost(@Path("id") id: Int): Post

    @GET("posts")
    suspend fun getPostsByUser(@Query("userId") userId: Int): List<Post>
}

看几个细节:

  • @GET("posts") 表示 GET 请求,路径拼在 baseUrl 后面。
  • {id} 是路径占位符,用 @Path 注入实际值。
  • @Query 拼到 URL 查询参数上,?userId=1 这样。
  • 方法是 suspend,Retrofit 自动切 IO 线程,调用方在协程里直接 await。

第四步:构造 Retrofit 实例

写个单例对象集中管理:

import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory

object RetrofitClient {

    private const val BASE_URL = "https://jsonplaceholder.typicode.com/"

    val api: PostApi by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(MoshiConverterFactory.create())
            .build()
            .create(PostApi::class.java)
    }
}

要点:

  • baseUrl 必须以 / 结尾,否则报错。
  • addConverterFactory 装上 JSON 转换器,Moshi 或 Gson 选一个。
  • create(PostApi::class.java) 这一步把接口「实现」出来,返回的就是个能调用的对象。
Warning

baseUrl 路径和 @GET 路径会拼接,规则容易搞混。简单记:baseUrl 以 / 结尾,@GET 路径不要以 / 开头。https://api.com/ + posts = https://api.com/posts,对。

发起请求

在协程里调用就行:

class PostRepository(private val api: PostApi = RetrofitClient.api) {

    suspend fun fetchPost(id: Int): Post {
        return api.getPost(id)
    }

    suspend fun fetchAll(): List<Post> {
        return api.listPosts()
    }
}

ViewModel 或 Compose 里用:

val scope = rememberCoroutineScope()
var post by remember { mutableStateOf<Post?>(null) }
var error by remember { mutableStateOf<String?>(null) }

Button(onClick = {
    scope.launch {
        try {
            post = PostRepository().fetchPost(1)
        } catch (e: Exception) {
            error = e.message
        }
    }
}) {
    Text("拉取")
}

post?.let { Text("标题:${it.title}") }

POST 请求

POST 一般带请求体,用 @Body 注入对象:

import retrofit2.http.Body
import retrofit2.http.POST

interface PostApi {
    @POST("posts")
    suspend fun createPost(@Body post: Post): Post
}

调用:

suspend fun publishPost(title: String, body: String): Post {
    val newPost = Post(
        userId = 1,
        id = 0,  // 后端会分配
        title = title,
        body = body
    )
    return api.createPost(newPost)
}

后端返回创建好的资源,自动解析成 Post 对象给你。

表单和文件上传

表单提交用 @FormUrlEncoded

import retrofit2.http.FormUrlEncoded
import retrofit2.http.Field

interface AuthApi {
    @FormUrlEncoded
    @POST("login")
    suspend fun login(
        @Field("username") username: String,
        @Field("password") password: String
    ): LoginResponse
}

文件上传用 @Multipart

import retrofit2.http.Multipart
import retrofit2.http.Part
import okhttp3.MultipartBody

interface UploadApi {
    @Multipart
    @POST("upload")
    suspend fun uploadImage(
        @Part image: MultipartBody.Part
    ): UploadResponse
}

构造 MultipartBody.Part

val file = File(cacheDir, "photo.jpg")
val requestFile = file.asRequestBody("image/jpeg".toMediaType())
val multipart = MultipartBody.Part.createFormData("image", file.name, requestFile)
api.uploadImage(multipart)

下一章进阶会展开讲拦截器和错误处理。

处理响应

直接返回对象用着舒服,但拿不到 HTTP 状态码。要拿原始响应,包一层 Response<T>

@GET("posts/{id}")
suspend fun getPostRaw(@Path("id") id: Int): Response<Post>

调用:

val response = api.getPostRaw(1)
if (response.isSuccessful) {
    val post = response.body()  // 拿到 Post
    println(post?.title)
} else {
    val code = response.code()  // 404, 500...
    val errorBody = response.errorBody()?.string()  // 错误响应体
}
Tip

习惯做法:业务接口直接返回对象(让 Retrofit 不成功时抛异常),需要细判状态码的接口用 Response<T>,二者按场景选。

常见坑

  1. baseUrl 不以 / 结尾:直接抛 IllegalArgumentException
  2. JSON 字段名对不上:返回 null 或者数据类构造失败。用 @Json(name = ...) 映射。
  3. suspend 方法忘了写:调用 api.getPost(1) 不返回协程,会卡死或者编译报错。
  4. JSON 字段可空但数据类写非空:解析失败抛异常。后端可能返回 null 的字段要写成 String?
  5. Moshi 没配 KSP@JsonClass(generateAdapter = true) 不生效,退回到反射模式,性能差。

小结

Retrofit 三步就能用:加依赖、定义接口、构造实例。接口方法用 suspend 配合协程,JSON 自动解析成 Kotlin 对象,省掉一大堆样板代码。GET 用 @GET + @Path / @Query,POST 用 @POST + @Body,表单和文件用 @FormUrlEncoded / @Multipart

下一章讲拦截器、Header、错误处理、超时配置这些进阶话题。