
一、概念是由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 如何选型MoshiGsonKotlin 支持原生非空检查、默认值、data class反射硬套 Java 规则非空字段被塞 null当场抛 JsonDataException默默放行崩在更远的 UI 层维护状态Square 活跃维护与 Retrofit 同生态已进入维护模式Gson 使用 SerializedName(json_key)。Moshi 使用 Json(name json_key)。2.3.2 使用 Moshival 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.adapterUser(User::class.java) // 3. 将 JSON 字符串反序列化为 User 对象 val jsonString {id:1,name:Alice} val user adapter.fromJson(jsonString) println(user) // 输出: User(userId1, nameAlice) // 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 类转换//解析ListT、MapK,V等泛型类型时需通过TypeToken获取泛型类型因Java泛型擦除无法直接用ListUser.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默认的CallT返回值转换为其他类型如RxJava的ObservableT从而扩展异步请求的实现方式。 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 类型通过泛型指定服务器返回的具体数据类型使用CallResponseBody则返回没经过Gson转换的原始数据类即json字符串使用RxJava声明的是ObservablePerson类型使用协程可直接返回对象类型见下文Android封装用法。函数注解说明GET从服务器获取数据。POST向服务器提交数据。DELETE删除服务器上的数据。PUTPATCH修改服务器上的数据。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 { //接口1https://www.baidu.com/person.json GET(person.json) //表示发起的是GET请求传入请求的地址相对路径重复根路径在后面配置 fun getPerson(): CalllistPerson //接口2https://www.baidu.com/page/person.json GET({page}/get_data.json) //使用 {page} 占位 fun getData(Path(page) page: Int): CallData //使用 Path(page)注解来声明对应参数 //接口3https://www.baidu.com/person.json?uuserttoken GET(person.json) fun getData(Query(u) user: String, Query(t) token: String): CallData //接口4https://api.caiyunapp.com/v2/place?query北京token{token}langzh_CN GET(v2/place?token${GlobalApplication.TOKEN}langzh_CN) //不变的参数固定写在GET里 fun searchPlaces(Query(query) query: String): CallPlaceResponse }2.5.2 POST 示例interface PostService { //接口6https://www.baidu.com/data/create{id: 1, content: The description for this data.} POST(data/create) fun createData(Body data: Data): CallResponseBody //将Data对象中的数据转换成JSON格式的文本并放到HTTP请求的body部分 }2.5.3 DELETE 示例interface DeleteService { //接口5https://www.baidu.com/data/id DELETE(data/{id}) fun deleteData(Path(id) id: String): CallResponseBody //该泛型表示能接受任意类型切不会进行解析 }2.5.4 Headers 示例interface PersonService { /*接口7http://example.com/get_data.json User-Agent: okhttp //header参数就是键值对 Cache-Control: max-age0 */ //静态声明添加固定请求头 Headers(User-Agent: okhttp, Cache-Control: max-age0) GET(get_data.json) fun getData(): CallData //动态声明动态添加请求头 GET(get_data.json) fun getData(Header(User-Agent) userAgent: String, Header(Cache-Control) cacheControl: String): CallData }2.6 发起网络请求//创建网络请求接口的实例 val personService retrofit.create(PersonService::class.java) //获取Call对象对发送请求进行封装 val personCall: CalllistPerson personService.getPerson() //发送网络请求异步是.enqueue()同步是.excute() personCall.enqueue(object : CallbackListperson { //接口回调 override fun onResponse(call: CallListperson, response: ResponseListperson) { //对response做判断 val list response.body() //得到解析后的对象 } override fun onFailure(call: CallListperson, 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 ApiResponseT( val errorCode: Int, val errorMsg: String, val data: T? ) { fun getResult(): ResultT { //使用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关键字返回值直接为数据模型无需CallT。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 ): ApiResponseLoginBean }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): ResultLoginBean } 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 MutableLiveDataResultLoginBean() val loginData _loginData as LiveDataResultLoginBean //幕后属性提供不可变版本供外部访问 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、UnknownHostExceptionHTTP错误码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_PERMITTED3.1 方式一直接在 AndroidManifest 的 application 中添加如下代码android:usesCleartextTraffictrue3.2 方式二右键res目录→New→Directory→创建一个xml目录右键xml目录→New→File→创建一个network_security_config.xml文件修改内容如下?xml version1.0 encodingutf-8? network-security-config domain-config cleartextTrafficPermittedfalse domain includeSubdomainstruewww.baidu.com/domain /domain-config /network-security-configManifest { //添加网络访问权限 uses-permission android:nameandroid.permission.INTERNET / //允许HTTP访问 application android:networkSecurityConfigxml/network_security_config /application }