网络 - Retrofit
2026/9/11 10:49:52 网站建设 项目流程

一、概念

是由Square公司开发的一款 RESTful API 请求框架,它通过注解将HTTP请求抽象为Java/Kotlin接口,简化了网络请求的定义与调用,其核心价值是将网络请求的“配置逻辑”(如URL、请求方法、参数)与“执行逻辑”解耦,让代码更简洁、可维护。

Retrofit本身不执行网络请求,而是将接口定义的请求参数转换为 OkHttp 可识别的 Request 对象,最终通过 OkHttp 完成TCP连接、数据传输等底层操作。

HttpClientAndroid 6中移除(API数量多扩展困难)。
HttpURLConnection目前官方集成的。
OKHttpSquare公司出品,底层通讯的实现。
RetrofitSquare公司出品,上层接口的封装(注解代替代码)更方便进行网络请求。

二、基本使用

Retrofit把网络请求的 URL 分成了两部分设置:创建Retrofit实例时通过 .baseUrl("...") 设置的 + 网络访问接口的函数注解 @GET("...") 设置的。

2.1 添加依赖

查看最新版本

implementation 'com.squareup.retrofit2:retrofit:2.9.0' //会连带下载 OkHttp和Okio // Gson implementation("com.squareup.retrofit2:converter-gson:2.9.0") // Moshi implementation("com.squareup.retrofit2:converter-moshi:2.9.0") ksp("com.squareup.moshi:moshi-kotlin-codegen:1.15.1") //编译期代码生成,性能更好,体积更小 implementation("com.squareup.moshi:moshi:2.0.0-alpha.1") //核心库 implementation("com.squareup.moshi:moshi-kotlin:1.14.0") //支持反射,处理数据类和空安全

2.2 构建Retrofit对象

若需为不同接口配置不同的超时/拦截器,需创建多个OkHttpClient实例,再分别初始化多个Retrofit实例(每个Retrofit关联一个OkHttpClient)。

val retrofit = Retrofit.Builder() .baseUrl("https://www.baidu.com/") // 配置重复的根路径 .client(okHttpClient) // 关联自定义的OkHttpClient .build()

2.3 数据转换接口 Converter

是Retrofit的数据转换接口,负责将HTTP请求的“请求体”(如Java对象)序列化为网络传输的字节流(如JSON字符串),以及将HTTP响应的“响应体”(如JSON字符串)反序列化为Java/Kotlin对象。Android中常用的JSON解析方式可分为原生解析和第三方库解析两类,核心差异在于易用性、性能和功能扩展性。

2.3.1 如何选型

MoshiGson
Kotlin 支持原生:非空检查、默认值、data class反射硬套 Java 规则
非空字段被塞 null当场抛 JsonDataException默默放行,崩在更远的 UI 层
维护状态Square 活跃维护(与 Retrofit 同生态)已进入维护模式
  • Gson 使用 @SerializedName("json_key")。Moshi 使用 @Json(name = "json_key")。

2.3.2 使用 Moshi

val moshi = Moshi.Builder() // 让 Moshi 支持 Kotlin 数据类,必须放最后 // Java 反射处理 Kotlin 类,非空字段会被 null 悄悄穿透 // 加了它,服务端返回 "user_name": null 会立刻报错 .addLast(KotlinJsonAdapterFactory()) .build() val retrofit = Retrofit.Builder() .addConverterFactory(MoshiConverterFactory.create(moshi)) .build()
// 给数据类添加注解 // 编译期生成适配器——无反射、更快、对 R8 更友好。 // 注意:没开 codegen 就别标那个注解,运行时直接崩。 @JsonClass(generateAdapter = true) data class PartData ( @Json(name = "user_id") var id: Long, var itemName: String )

手动使用 Moshi 类转换

// 1. 创建 Moshi 实例 val moshi = Moshi.Builder() .addLast(KotlinJsonAdapterFactory()) // 让 Moshi 支持 Kotlin 数据类 .build() // 2. 为 User 类创建适配器 val adapter = moshi.adapter<User>(User::class.java) // 3. 将 JSON 字符串反序列化为 User 对象 val jsonString = """{"id":1,"name":"Alice"}""" val user = adapter.fromJson(jsonString) println(user) // 输出: User(userId=1, name=Alice) // 4. 将 User 对象序列化为 JSON 字符串 val newUser = User(2, "Bob") val newJson = adapter.toJson(newUser) println(newJson) // 输出: {"id":2,"name":"Bob"}

2.3.3 使用 Gson

简单使用

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

自定义

val gson = GsonBuilder() //默认情况下,Gson将Date对象序列化为时间戳,可自定义格式 .setDateFormat("yyyy-MM-dd HH:mm:ss") //下划线命名(如user_name → userName) .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .create() val retrofit = Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create(gson))

手动使用 Gson 类转换

//解析List<T>、Map<K,V>等泛型类型时,需通过TypeToken获取泛型类型(因Java泛型擦除,无法直接用List<User>.class)。 val gson = Gson() //通过 GsonBuilder 可以更多配置,如为时间戳格式的Date指定时间格式 //反序列化:Json字符串 -> Object实例 val dataBean = gson.fromJson(jsonStr) //序列化:Object实例 -> Json字符串 val jsonStr = gson.toJson(user)

2.2.2 返回值转换器 CallAdapter

是Retrofit的返回值转换接口,负责将Retrofit默认的Call<T>返回值转换为其他类型(如RxJava的Observable<T>),从而扩展异步请求的实现方式。 Kotlin协程无需额外配置,Retrofit v2.6.0 开始内置 CoroutineCallAdapterFactory。

2.43 定义实体类(数据模型)

根据Json内容,编写对应的实体类。字段名需与Json键名完全一致(大小写敏感),若不一致,需使用@SerializedName注解映射。

data class PersonBean( //建议类名后缀加Bean与正常类区分 val name: String = "", //非空类型+默认值,避免Jon中字段缺失带来null问题 @SerializedName("age") //这里与Json键名一致就行 val a_g_e: Int = 0 transient val type = false //transient标记的字段不参与序列化/反序列化 )

2.5 定义网络访问接口

根据 API 编写网络访问接口。Retrofit 将 Http 请求抽象成接口,并在接口里面采用注解来配置网络请求参数(每个形参都要注解),用动态代理将该接口“翻译”成一个 Http 请求再执行。

命名通常以功能名称开头+Service结尾。返回值类型必须声明成 Retrofit 内置的 Call 类型,通过泛型指定服务器返回的具体数据类型(使用Call<ResponseBody>则返回没经过Gson转换的原始数据类即json字符串)(使用RxJava声明的是Observable<Person>类型)(使用协程可直接返回对象类型,见下文Android封装用法)。

函数注解说明

@GET

从服务器获取数据。
@POST向服务器提交数据。
@DELETE删除服务器上的数据。

@PUT

@PATCH

修改服务器上的数据。put更新资源、patch部分更新。
@Headers添加固定请求头
参数注解说明
@Header动态添加请求头。
@Path替换路径占位符。
@Query查询参数(通常结合get请求)。
@Feild表单提交(通常结合post请求)。
@FormUrlEncoded用于表单数据数据提交。

BaseUrl 必须以 “/” 结尾,否则会抛异常。注解路径不带前缀 “/”,否则会把 BaseUrl 的 path 部分整个裁掉。

假设 baseUrl 是 https://api.example.com/v1/,各写法的实际结果:

注解写法实际请求说明
@GET("users")/v1/users推荐写法(相对当前"目录")。
@GET("/users")/users/开头 = 从域名根开始,/v1被裁掉。
@GET("users/list")/v1/users/list相对路径可以带子路径。
@GET("../users")/users.. 回上一级(/v1/的上一级就是根)。
@GET("https://cdn.other.com/f")直接用该 URL完整 URL 时 baseUrl 整个被忽略。

2.5.1 GET 示例

interface GetService { //接口1:https://www.baidu.com/person.json @GET("person.json") //表示发起的是GET请求,传入请求的地址(相对路径,重复根路径在后面配置) fun getPerson(): Call<list<Person>> //接口2:https://www.baidu.com/<page>/person.json @GET("{page}/get_data.json") //使用 {page} 占位 fun getData(@Path("page") page: Int): Call<Data> //使用 @Path("page")注解来声明对应参数 //接口3:https://www.baidu.com/person.json?u=<user>&t=<token> @GET("person.json") fun getData(@Query("u") user: String, @Query("t") token: String): Call<Data> //接口4:https://api.caiyunapp.com/v2/place?query=北京&token={token}&lang=zh_CN @GET("v2/place?token=${GlobalApplication.TOKEN}&lang=zh_CN") //不变的参数固定写在GET里 fun searchPlaces(@Query("query") query: String): Call<PlaceResponse> }

2.5.2 POST 示例

interface PostService { //接口6:https://www.baidu.com/data/create{"id": 1, "content": "The description for this data."} @POST("data/create") fun createData(@Body data: Data): Call<ResponseBody> //将Data对象中的数据转换成JSON格式的文本,并放到HTTP请求的body部分 }

2.5.3 DELETE 示例

interface DeleteService { //接口5:https://www.baidu.com/data/<id> @DELETE("data/{id}") fun deleteData(@Path("id") id: String): Call<ResponseBody> //该泛型表示能接受任意类型切不会进行解析 }

2.5.4 Headers 示例

interface PersonService { /*接口7:http://example.com/get_data.json User-Agent: okhttp //header参数就是键值对 Cache-Control: max-age=0 */ //静态声明(添加固定请求头) @Headers("User-Agent: okhttp", "Cache-Control: max-age=0") @GET("get_data.json") fun getData(): Call<Data> //动态声明(动态添加请求头) @GET("get_data.json") fun getData(@Header("User-Agent") userAgent: String, @Header("Cache-Control") cacheControl: String): Call<Data> }

2.6 发起网络请求

//创建网络请求接口的实例 val personService = retrofit.create(PersonService::class.java) //获取Call对象(对发送请求进行封装) val personCall: Call<list<Person>> = personService.getPerson() //发送网络请求(异步是.enqueue(),同步是.excute()) personCall.enqueue(object : Callback<List<person>> { //接口回调 override fun onResponse(call: Call<List<person>>, response: Response<List<person>>) { //对response做判断 val list = response.body() //得到解析后的对象 } override fun onFailure(call: Call<List<person>>, t: Trouble) { t.printStackTrace() } })

三、Android开发写法

2.6.1 Retrofit客户端

object RetrofitClient { //Cookie private val cookieJar by lazy { PersistentCookieJar(SetCookieCache(), SharedPrefsCookiePersistor(APP.context)) } //OkHttpClient private val okHeepClient by lazy { OkHttpClient.Builder() .cookieJar(cookieJar) .build() } //Retrofit private val retrofit by lazy { Retrofit.Builder() .client(okHeepClient) .baseUrl(ApiService.BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .build() } //ApiService val apiService: ApiService by lazy { retrofit.create(ApiService::class.java) } }

2.6.2 返回数据的基类

//返回数据的基类 data class ApiResponse<T>( val errorCode: Int, val errorMsg: String, val data: T? ) { fun getResult(): Result<T> { //使用Result类包装返回结果(数据或异常) return if (errorCode == 0 && data != null) { Result.success(data) } else { Result.failure(ApiException(errorMsg)) } } } //异常封装 class ApiException( errorMsg: String ) : Exception(errorMsg)

2.6.3 API接口

在接口方法前添加suspend关键字,返回值直接为数据模型(无需Call<T>)。

interface ApiService { companion object { const val BASE_URL = "https://www.wanandroid.com/" } //登录 @FormUrlEncoded @POST("user/login") suspend fun login( @Field("username") userName: String, @Field("password") password: String ): ApiResponse<LoginBean> }

2.6.4 协程使用

//数据源 //为了“静态”调用,该类无需其他功能就定义成 object 类,有的话使用伴生对象写联网方法 object LoginRemoteDataResource { suspend fun login(userName: String, password: String) = RetrofitClient.apiService.login(userName, password).getResult() } //仓库 interface IRepository { suspend fun login(userName: String, password: String): Result<LoginBean> } class Repository : IRepository { override suspend fun login(userName: String, password: String) = LoginRemoteDataResource.login(userName, password) } //ViewModel class SplashViewModel : ViewModel() { private val repository: IRepository = Repository() private val _loginData = MutableLiveData<Result<LoginBean>>() val loginData = _loginData as LiveData<Result<LoginBean>> //幕后属性,提供不可变版本供外部访问 suspend fun login(userName: String, password: String){ val result = repository.login(userName, password) _loginData.value = result } } //UI viewModel.loginData.observe(this) { it -> //对Result包装的返回结果是数据还是异常做判断 it.onSuccess { activity.switchFragment(R.id.action_splashLoginFragment_to_mainActivity) activity.finish() }.onFailure { showToast(it.message.orEmpty()) } } }

四、异常处理

封装工具类(如ErrorHandler),集中处理不同类型的错误,避免重复代码。 根据错误类型向用户展示提示(如网络异常提示“检查网络连接”,401提示“登录已过期”)。 在Release版本中仅记录关键错误信息,避免敏感数据泄露,Debug版本可打印详细日志便于调试。

错误类型原因异常类型、处理方式
网络异常设备无网络、网络信号差、服务器不可达、请求超时、连接失败。IOException(如SocketTimeoutException、UnknownHostException)
HTTP错误码Response.isSuccessful()返回false(响应码不在200-299范围内,如请求参数错误(400)、未授权(401)、资源不存在(404)、服务器内部错误(500)等。通过Response.code()获取错误码,Response.errorBody()获取错误信息(需转换为字符串)。HttpException。
数据解析异常服务器返回的JSON与本地数据模型字段不匹配(如字段缺失、类型错误)。在Call.enqueue()的onFailure()或协程的catch块中捕获。JsonParseException、MoshiJsonException。
runCatching { RetrofitClient.apiService.getData("") }.onFailure { throwable -> when(throwable) { is IOException -> { //网络异常 } is JsonParseException ->{ //解析异常 } is HttpException -> { //http异常(4xx/5xx) val errorCode = throwable.code() val errorMsg = throwable.response()?.errorBody()?.toString() ?: "未知错误" } } }

五、解决 http 链接无法访问

Android 9开始默认只允许使用 HTTPS 类型的网络请求,HTTP明文传输因为有安全隐患不再支持。net:ERR_CLEARTEXT_NOT_PERMITTED

3.1 方式一

直接在 AndroidManifest 的 <application> 中添加如下代码:

android:usesCleartextTraffic="true"

3.2 方式二

右键res目录→New→Directory→创建一个xml目录,右键xml目录→New→File→创建一个network_security_config.xml文件,修改内容如下:

<?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config cleartextTrafficPermitted="false"> <domain includeSubdomains="true">www.baidu.com</domain> </domain-config> </network-security-config>
Manifest { //添加网络访问权限 <uses-permission android:name="android.permission.INTERNET" /> //允许HTTP访问 <application android:networkSecurityConfig="@xml/network_security_config" </application> }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询