前言
Retrofit是现在比较流行的网络请求框架,可以理解为okhttp的加强版,底层封装了Okhttp。
准确来说,Retrofit是一个RESTful的http网络请求框架的封装。
因为网络请求工作本质上是由okhttp来完成,而Retrofit负责网络请求接口的封装。
https://square.github.io/retrofit/
代理HTTPS无法请求
Android要配置networkSecurityConfig
查看
https://www.psvmc.cn/article/2019-04-03-win-proxy.html
基本配置
添加网络权限
1
| <uses-permission android:name="android.permission.INTERNET"/>
|
添加依赖
1 2
| implementation ("com.squareup.retrofit2:retrofit:2.9.0") implementation ("com.squareup.retrofit2:converter-gson:2.9.0")
|
Kotlin整体示例
新版本的Retrofit已经可以方便使用Kotlin的协程。
添加实体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import java.util.List;
public class ResultListVo<T> { private int code; private String msg; private List<T> obj;
public int getCode() { return code; }
public void setCode(int code) { this.code = code; }
public String getMsg() { return msg; }
public void setMsg(String msg) { this.msg = msg; }
public List<T> getObj() { return obj; }
public void setObj(List<T> obj) { this.obj = obj; } }
|
用户
1
| data class UserItem(val name: String, val sex: String)
|
服务接口
添加suspend关键字,返回类型Response<T>
ServiceUser.kt
1 2 3 4 5 6 7 8 9 10 11 12
| package com.xhkjedu.zxs_android.api
import com.xhkjedu.zxs_android.model.ResultListVo import com.xhkjedu.zxs_android.model.UserItem import retrofit2.Response import retrofit2.http.GET
interface ServiceUser { @GET("userlist.json") suspend fun getUserList(): Response<ResultListVo<UserItem>> }
|
suspend 关键字的核心作用是允许函数暂停协程的执行(而非阻塞线程),并在适当的时候恢复,它是 Kotlin 协程实现 “高效异步编程” 的基础。
Retrofit 支持用 suspend 标记接口方法,直接在协程中调用,无需回调;
管理类
ApiManager.kt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
| import android.annotation.SuppressLint import com.xhkjedu.zxs_android.common.CommonData import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import java.security.SecureRandom import java.security.cert.CertificateException import java.security.cert.X509Certificate import java.util.concurrent.TimeUnit import javax.net.ssl.HostnameVerifier import javax.net.ssl.SSLContext import javax.net.ssl.SSLSocketFactory import javax.net.ssl.TrustManager import javax.net.ssl.X509TrustManager
object ApiManager { private lateinit var retrofit: Retrofit private lateinit var userRetrofit: Retrofit lateinit var appService: ServiceApp lateinit var userService: ServiceUser
@SuppressLint("CustomX509TrustManager") fun getUnsafeOkHttpClient(): OkHttpClient { try { val trustAllCerts = arrayOf<TrustManager>(
object : X509TrustManager { override fun checkClientTrusted( chain: Array<out java.security.cert.X509Certificate>?, authType: String? ) { } override fun checkServerTrusted( chain: Array<out java.security.cert.X509Certificate>?, authType: String? ) { }
override fun getAcceptedIssuers(): Array<java.security.cert.X509Certificate> = arrayOf() } )
val sslContext = SSLContext.getInstance("TLS") sslContext.init(null, trustAllCerts, SecureRandom())
val allHostsValid = HostnameVerifier { _, _ -> true }
return OkHttpClient.Builder() .sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager) .hostnameVerifier(allHostsValid) .build() } catch (e: Exception) { throw RuntimeException(e) } }
init { if (!::retrofit.isInitialized) { val client = OkHttpClient().newBuilder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(20, TimeUnit.SECONDS) .writeTimeout(20, TimeUnit.SECONDS) .addInterceptor(HeaderInterceptor()) .build();
retrofit = Retrofit.Builder() .baseUrl(CommonData.apiUrl) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build() }
if (!::userRetrofit.isInitialized) { val client = getUnsafeOkHttpClient().newBuilder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(20, TimeUnit.SECONDS) .writeTimeout(20, TimeUnit.SECONDS) .build();
userRetrofit = Retrofit.Builder() .baseUrl("https://www.psvmc.cn/") .client(client) .addConverterFactory(GsonConverterFactory.create()) .build() }
if (!::appService.isInitialized) { appService = retrofit.create(ServiceApp::class.java) }
if (!::userService.isInitialized) { userService = userRetrofit.create(ServiceUser::class.java) } } }
|
简单调用
1 2 3 4
| GlobalScope.launch { var result = ApiManager.appService.getUserList().body() Log.i("API", Gson().toJson(result)) }
|
ViewModel中调用
BaseViewModel
如果使用Jetpack Compose
BaseViewModel.kt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext
open class BaseViewModel : ViewModel() { var zIsShowMsg = mutableStateOf(false) var zShowMsgContent = mutableStateOf("")
fun mLaunch(block: suspend () -> Unit) { viewModelScope.launch(Dispatchers.IO) { try { block() } catch (e: Exception) { withContext(Dispatchers.Main){ zIsShowMsg.value = true zShowMsgContent.value = "接口请求失败" } } } } }
|
调用
UserViewModel
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import android.util.Log import androidx.compose.runtime.mutableStateListOf import com.google.gson.Gson import com.xhkjedu.zxs_android.api.ApiManager import com.xhkjedu.zxs_android.model.UserItem import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.withContext
class UserViewModel : BaseViewModel() { private val TAG = "UserViewModel" val userList = mutableStateListOf<UserItem>()
fun loadUserList() { mLaunch { val result = ApiManager.userService.getUserList().body() Log.i("API", Gson().toJson(result)) withContext(Dispatchers.Main) { result?.let { Log.i(TAG, "Thread: " + Thread.currentThread().name) Log.i(TAG, "UserViewModel: " + Gson().toJson(it)) if (it.code == 0) { if (it.obj == null) { it.obj = mutableListOf() } userList.addAll(it.obj) mShowMsg.value = false } else { mShowMsg.value = true mShowMsgContent.value = it.msg } } } } } }
|
常见配置
请求头设置
HeaderInterceptor.kt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import android.util.Log import com.xhkjedu.zxs_android.common.CommonData import okhttp3.Interceptor import okhttp3.Response import java.io.IOException
class HeaderInterceptor : Interceptor { var TAG: String = "HeaderInterceptor"
@Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { val original = chain.request() try { val mBuilder = original.newBuilder() if (CommonData.Authorization != "") { mBuilder.header("Authorization", CommonData.Authorization) }
val request = mBuilder .method(original.method, original.body) .build()
val response = chain.proceed(request) val headers = response.headers headers.forEach { header -> Log.d("ResponseHeader", "${header.first} ${header.second}") if (header.first == "Authorization") { CommonData.Authorization = header.second } } return response } catch (e: Exception) { return chain.proceed(original) } } }
|
其中
intercept 会在每次请求的时候都触发。
超时时间设置
1 2 3 4 5 6 7 8 9 10 11 12
| val client = OkHttpClient().newBuilder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(20, TimeUnit.SECONDS) .writeTimeout(20, TimeUnit.SECONDS) .addInterceptor(HeaderInterceptor()) .build();
retrofit = Retrofit.Builder() .baseUrl(CommonData.apiUrl) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build()
|
这三个超时时间分别是:
连接超时时间(Connect Timeout):指的是客户端连接到服务器所需的最大时间。
如果在规定的时间内未能建立连接,则会抛出 java.net.SocketTimeoutException 异常。
读取超时时间(Read Timeout):指的是客户端从服务器读取数据的最大时间。比如下载。
如果在规定的时间内未能读取到数据,则会抛出 java.net.SocketTimeoutException 异常。
写入超时时间(Write Timeout):指的是客户端向服务器发送数据的最大时间。比如上传。
如果在规定的时间内未能将数据发送完毕,则会抛出 java.net.SocketTimeoutException 异常。
建议:
上传下载和普通接口请求的超时时间分开设置。因为两个的时间差别较大。
信任所有证书
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| @SuppressLint("CustomX509TrustManager") fun getUnsafeOkHttpClient(): OkHttpClient { try { val trustAllCerts = arrayOf<TrustManager>(
object : X509TrustManager { override fun checkClientTrusted( chain: Array<out java.security.cert.X509Certificate>?, authType: String? ) { } override fun checkServerTrusted( chain: Array<out java.security.cert.X509Certificate>?, authType: String? ) { }
override fun getAcceptedIssuers(): Array<java.security.cert.X509Certificate> = arrayOf() } )
val sslContext = SSLContext.getInstance("TLS") sslContext.init(null, trustAllCerts, SecureRandom())
val allHostsValid = HostnameVerifier { _, _ -> true }
return OkHttpClient.Builder() .sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager) .hostnameVerifier(allHostsValid) .build() } catch (e: Exception) { throw RuntimeException(e) } }
|
请求报错
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found
可以按照上面信任证书
结合Flow
BaseViewModel.kt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext
open class BaseViewModel : ViewModel() { var mShowMsg = mutableStateOf(false) var mShowMsgContent = mutableStateOf("") var isLoading = mutableStateOf(false)
fun mLaunch(block: suspend () -> Unit) { viewModelScope.launch { try { block() } catch (e: Exception) { withContext(Dispatchers.Main) { e.printStackTrace() isLoading.value = false mShowMsg.value = true mShowMsgContent.value = "数据加载失败" } } } } }
|
UserViewModel.kt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import android.util.Log import androidx.compose.runtime.mutableStateListOf import com.google.gson.Gson import com.xhkjedu.zxs_android.api.ApiManager import com.xhkjedu.zxs_android.model.UserItem import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.withContext
class UserViewModel : BaseViewModel() { private val TAG = "UserViewModel" val userList = mutableStateListOf<UserItem>()
fun loadUserListUseFlow() { mLaunch { flow { Log.i(TAG, "Thread-Flow: " + Thread.currentThread().name) emit(ApiManager.userService.getUserList().body()) } .flowOn(Dispatchers.IO) .catch { e -> e.printStackTrace() }.collect { Log.i(TAG, "Thread-Collect: " + Thread.currentThread().name) it?.let { Log.i(TAG, "UserViewModel: " + Gson().toJson(it)) if (it.code == 0) { if (it.obj == null) { it.obj = mutableListOf() } userList.addAll(it.obj) mShowMsg.value = false } else { mShowMsg.value = true mShowMsgContent.value = it.msg } }
} } } }
|
服务参数
Query参数
1 2 3 4
| @GET("/user/list") suspend fun apiUserList( @Query("page") page: Int, @Query("limit") limit: Int ): Response<ResultVo<Collection<UserModel>>>
|
Path参数
1 2
| @GET("users/{userId}") fun getUser(@Path("userId") userId: Int): Response<ResultVo<UserModel>>
|
Body参数
数据类
首先定义数据类:
1 2 3 4 5
| data class UserRequest( val username: String, val password: String, val age: Int )
|
然后在 Retrofit 接口中使用 @Body 注解:
1 2 3 4 5 6 7 8
| import retrofit2.Response import retrofit2.http.Body import retrofit2.http.POST
interface ApiService { @POST("user/login") suspend fun login(@Body request: UserRequest): Response<UserModel> }
|
MutableMap
1 2 3 4
| interface ApiService { @POST("user/login") suspend fun login(@Body params: Map<String, @JvmSuppressWildcards Any>): Response<UserModel> }
|
使用时
1 2 3 4 5 6
| val params = mapOf( "username" to "test", "password" to "123456", "rememberMe" to true ) apiService.login(params)
|
报错
Parameter type must not include a type variable or wildcard: java.util.Map<java.lang.String, ?>
原因是:
因为参数Map的value类型Any,在Java中这个value的类型是Object,可以被Retrofit识别,但对于kotlin来说,retrofit会把Any识别成 ?,所以就报出了错误。
解决方法
Any前要添加@JvmSuppressWildcards。
请求体是空
如果请求体要求是{}
不传参数的话,请求体是空,不是{},写成Map<String, Any?>这样也不行,会提示不知道值的类型。
我们可以写成这样
1 2
| @POST("/api/sys/facility/resource/queryList") suspend fun apiSysFacilityResourceQuerylist(@Body params: Map<String, String>): Response<ResultVo<Collection<TopMenuItem>>>
|
调用
1
| ApiManager.menuService.apiSysFacilityResourceQuerylist(mapOf())
|
匿名对象
1 2 3 4
| interface ApiService { @POST("user/info") suspend fun updateInfo(@Body params: Any): Response<UserModel> }
|
使用时
1 2 3 4 5 6 7
| apiService.updateInfo( object { val nickname = "新昵称" val age = 25 val phone = "13800138000" } )
|
@Field 强制依赖 @FormUrlEncoded,会自动设置请求头为表单类型。
单个参数
1 2 3 4 5 6
| @FormUrlEncoded @POST("/user/login") suspend fun apiUserLogin( @Field("username") username: String, @Field("password") password: String ): Response<ResultVo<UserModel>>
|
表单Map
1 2 3 4 5
| interface ApiService { @FormUrlEncoded @POST("user/register") suspend fun register(@FieldMap params: Map<String, String>): Response<RegisterResponse> }
|
使用时
1 2 3 4 5 6
| val params = mapOf( "username" to "test", "password" to "123456", "email" to "test@example.com" ) apiService.register(params)
|
MutiPart
1 2 3 4 5 6 7 8
| interface ApiService { @Multipart @POST("user/update") suspend fun updateUser( @Part("username") username: RequestBody, @Part avatar: MultipartBody.Part ): Response<UserResponse> }
|