添加依賴
implementation 'com.squareup.retrofit2:retrofit:2.11.0'
implementation 'com.squareup.retrofit2:converter-gson:2.11.0'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
由于需要對(duì) OKHttpClient 做一些操作和定制,這里添加了 okhttp 的依賴。實(shí)體類的轉(zhuǎn)換使用了 gson,為啥用 gson,問就是項(xiàng)目里面就是用的 gson,后面再介紹一下其他的converter。
- Gson: com.squareup.retrofit2:converter-gson
- Jackson: com.squareup.retrofit2:converter-jackson
- Moshi: com.squareup.retrofit2:converter-moshi
- Protobuf: com.squareup.retrofit2:converter-protobuf
- Wire: com.squareup.retrofit2:converter-wire
- Simple XML: com.squareup.retrofit2:converter-simplexml
- JAXB: com.squareup.retrofit2:converter-jaxb
- Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
聲明請(qǐng)求接口
interface MainPageApi{
@GET("app_interface/home_pag/")
fun getMainPageInfoWithRow():Call<MainPageInfo>
}
創(chuàng)建 Retrofit 對(duì)象
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
發(fā)送請(qǐng)求
val mainPageApi = retrofit.create(MainPageApi::class.java)
mainPageApi.getMainPageInfoWithCall().enqueue(object:retrofit2.Callback<MainPageInfo>{
override fun onResponse(
call: Call<MainPageInfo>,
response: retrofit2.Response<MainPageInfo>
) {
Log.e("KotlinActivity","getMainPageInfoWithCall onResponse")
}
override fun onFailure(call: Call<MainPageInfo>, t: Throwable) {
Log.e("KotlinActivity","getMainPageInfoWithCall onFailure")
}
})
到這里為止,我們還沒有使用任何協(xié)程相關(guān)的特性,并且沒有都得寫回調(diào),和 Java 寫起來也沒啥差別。
支持協(xié)程
我們對(duì)接口的聲明加上suspend
修飾
@GET("app_interface/home_pag/")
suspend fun getMainPageInfoWithRow():Call<MainPageInfo>
這時(shí)候上面直接發(fā)送請(qǐng)求的代碼會(huì)報(bào)錯(cuò): [圖片上傳失敗...(image-ba426c-1714897564066)] 提示我們需要在協(xié)程中調(diào)用,這也簡單,kotlin 對(duì) activity 有個(gè)擴(kuò)展的lifecycleScope
成員變量,稍微修改一下:
lifecycleScope.launch(Dispatchers.IO) {
mainPageApi.getMainPageInfoWithCall().enqueue(.....)
}
不習(xí)慣這么寫的話,可以將網(wǎng)絡(luò)請(qǐng)求寫在 ViewModel 中,通過 LiveData創(chuàng)建一個(gè)可觀察對(duì)象實(shí)現(xiàn)數(shù)據(jù)綁定。
不出意外的出意外了,應(yīng)用崩潰,錯(cuò)誤信息
java.lang.IllegalArgumentException: Suspend functions should not return Call, as they already execute asynchronously.
Change its return type to class com.huangyuanlove.androidtest.kotlin.retrofit.MainPageInfo
意思是在協(xié)程中發(fā)起請(qǐng)求已經(jīng)是異步的了,不需要再返回 Call 對(duì)象了,直接返回對(duì)應(yīng)的實(shí)體即可。 簡單,修改一下接口聲明
@GET("app_interface/home_page/")
suspend fun getMainPageInfoWithRow():MainPageInfo
然后修改一下請(qǐng)求
lifecycleScope.launch(Dispatchers.IO) {
val mainPageInfo = mainPageApi.getMainPageInfo()
withContext(Dispatchers.Main) {
refreshUI(mainPageInfo)
}
}
運(yùn)行一下,一切正常。我們修改一下接口,請(qǐng)求一個(gè)不存在的地址,會(huì)返回404,不出意外,應(yīng)用還是崩潰
retrofit2.HttpException: HTTP 404
at retrofit2.KotlinExtensions$await$2$2.onResponse(KotlinExtensions.kt:53)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:164)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:929)
Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@ffa6ad2, Dispatchers.IO]
哦~異常沒有處理,粗暴點(diǎn),直接 try-catch,kotlin 中還有runCatching
這個(gè)語法糖
val mainPageInfoRow = runCatching { mainPageApi.getMainPageInfoWithRow() }
if (mainPageInfoRow.isFailure) {
ToastUtils.showToast("請(qǐng)求失敗")
} else if (mainPageInfoRow.isSuccess) {
ToastUtils.showToast("請(qǐng)求成功")
withContext(Dispatchers.Main) {
if (mainPageInfoRow.getOrNull() == null) {
ToastUtils.showToast("請(qǐng)求結(jié)果為空")
} else {
refreshViewWithLaunch(mainPageInfoRow.getOrNull()!!)
}
}
}
但是有時(shí)候我們會(huì)用HTTP狀態(tài)碼
來表示一些業(yè)務(wù)上邏輯錯(cuò)誤,并且不同的狀態(tài)碼返回的 JSON 結(jié)構(gòu)還可能不一樣。 別問為啥要這么搞,應(yīng)該是HTTP 狀態(tài)碼就應(yīng)該表示網(wǎng)絡(luò)請(qǐng)求的狀態(tài),業(yè)務(wù)狀態(tài)應(yīng)該放在返回的數(shù)據(jù)中約定字段來處理。問就是15年的老代碼,之前就是這么搞的,并且大范圍應(yīng)用,涉及到的部門、業(yè)務(wù)占半數(shù)以上。 這時(shí)候我們需要自定義CallAdapter
了
自定義 CallAdapter
這時(shí)候就應(yīng)該翻一下源碼了,在example
有個(gè)ErrorHandlingAdapter.java
,路徑在samples/src/main/java/com/example/retrofit/ErrorHandlingAdapter.java。 我們來仿寫一下,最關(guān)鍵的點(diǎn)在實(shí)現(xiàn)自己的 Call 類的時(shí)候,對(duì)callback 的處理。
定義不同的返回狀態(tài)
第一步,創(chuàng)建密閉類,來表示不同的狀態(tài),這里暫且定義了三種情況
- Success:HTTP狀態(tài)碼在
[200,300)
這個(gè)區(qū)間 - NetError:HTTP狀態(tài)碼不在
[200,300)
這個(gè)區(qū)間 - UnknownError:其他錯(cuò)誤
sealed class NetworkResponse<out T : Any, out U : Any> { data class Success(val body: T) : NetworkResponse<T, Nothing>() data class NetError(val httpCode:Int?,val errorMsg:String?,val exception: Throwable?) : NetworkResponse<Nothing, Nothing>() data class UnknownError(val error: Throwable?) : NetworkResponse<Nothing, Nothing>() }
創(chuàng)建自己的Call類
這里為了簡化方便,除了enqueue
之外必須重寫的方法,都是直接調(diào)用delegate
對(duì)應(yīng)的方法
internal class NetworkResponseCall<S : Any, E : Any>(
private val delegate: Call<S>,
private val errorConverter: Converter<ResponseBody, E>
) : Call<NetworkResponse<S, E>> {
override fun clone(): Call<NetworkResponse<S, E>> {
return NetworkResponseCall(delegate.clone(), errorConverter);
}
override fun execute(): Response<NetworkResponse<S, E>> {
throw UnsupportedOperationException("NetworkResponseCall doesn't support execute")
}
override fun isExecuted(): Boolean {
return delegate.isExecuted;
}
override fun cancel() {
delegate.cancel()
}
override fun isCanceled(): Boolean {
return delegate.isCanceled
}
override fun request(): Request {
return delegate.request()
}
override fun timeout(): Timeout {
return delegate.timeout();
}
}
下面是關(guān)鍵的enqueue
方法,在這里面,將所有的請(qǐng)求都用Response.success
返回,不再走Response.error
.并且根據(jù)不同的 HTTP 狀態(tài)碼,返回的數(shù)據(jù)等條件轉(zhuǎn)成一開始定義的密閉類。
override fun enqueue(callback: Callback<NetworkResponse<S, E>>) {
return delegate.enqueue(object : Callback<S> {
override fun onResponse(call: Call<S>, response: Response<S>) {
val body = response.body()
val code = response.code()
val error = response.errorBody()
if (response.isSuccessful) {
if (body != null) {
callback.onResponse(
this@NetworkResponseCall,
Response.success(NetworkResponse.Success(body))
)
} else {
callback.onResponse(
this@NetworkResponseCall,
Response.success(NetworkResponse.UnknownError(null))
)
}
} else {
val errorBody = when {
error == null -> null
error.contentLength() == 0L -> null
else -> NetworkResponse.NetError(code, error.toString(), null)
}
if (errorBody != null) {
callback.onResponse(
this@NetworkResponseCall,
Response.success(errorBody)
)
} else {
callback.onResponse(
this@NetworkResponseCall,
Response.success(NetworkResponse.UnknownError(null))
)
}
}
}
override fun onFailure(call: Call<S>, t: Throwable) {
val networkResponse = when (t) {
is Exception -> NetworkResponse.NetError(null,null,t)
else -> NetworkResponse.UnknownError(t)
}
callback.onResponse(this@NetworkResponseCall, Response.success(networkResponse))
}
})
}
創(chuàng)建 CallAdapter
class NetworkResponseAdapter<S : Any, E : Any>(
private val successType: Type,
private val errorBodyConverter: Converter<ResponseBody, E>
) : CallAdapter<S, Call<NetworkResponse<S, E>>> {
override fun responseType(): Type = successType
override fun adapt(call: Call<S>): Call<NetworkResponse<S, E>> {
return NetworkResponseCall(call, errorBodyConverter)
}
}
創(chuàng)建CallAdapterFactory
class NetworkResponseAdapterFactory:CallAdapter.Factory(){
override fun get(
returnType: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
// suspend functions wrap the response type in `Call`
if(Call::class.java != getRawType(returnType)){
return null
}
check(returnType is ParameterizedType){
"return type must be parameterized as Call<NetworkResponse<<Foo>> or Call<NetworkResponse<out Foo>>"
}
// get the response type inside the `Call` type
val responseType = getParameterUpperBound(0,returnType)
// if the response type is not ApiResponse then we can't handle this type, so we return null
if(getRawType(responseType) != NetworkResponse::class.java){
return null
}
// the response type is ApiResponse and should be parameterized
check(responseType is ParameterizedType) { "Response must be parameterized as NetworkResponse<Foo> or NetworkResponse<out Foo>" }
val successBodyType = getParameterUpperBound(0, responseType)
val errorBodyType = getParameterUpperBound(1, responseType)
val errorBodyConverter =
retrofit.nextResponseBodyConverter<Any>(null, errorBodyType, annotations)
return NetworkResponseAdapter<Any, Any>(successBodyType, errorBodyConverter)
}
}
構(gòu)建 Retrofit 實(shí)例時(shí)添加該 Factory
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addCallAdapterFactory(NetworkResponseAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.build()
使用typealias簡化返回類型(可選)
data class HttpError(val httpCode:Int,val errorMsg:String?,val exception: Throwable?)
// before
interface DemoApiService {
suspend fun mainPageInfo(): NetworkResponse<MainPageInfo, HttpError>
}
// after
typealias GenericResponse<S> = NetworkResponse<S, HttpError>
interface ApiService {
suspend fun mainPageInfo(): GenericResponse<MainPageInfo>
}
使用
在 Activity 中直接使用lifecycleScope啟動(dòng)協(xié)程。
lifecycleScope.launch(Dispatchers.IO) {
Log.e("KotlinActivity", "lifecycleScope.launch -->>" + Thread.currentThread().name);
val mainPageInfo = mainPageApi.getMainPageInfo()
withContext(Dispatchers.Main) {
Log.e(
"KotlinActivity",
"withContext(Dispatchers.Main) -->>" + Thread.currentThread().name
);
when(mainPageInfo){
is NetworkResponse.NetError -> Log.e("KotlinActivity",
"NetError->$mainPageInfo"
)
is NetworkResponse.Success -> refreshViewWithLaunch(mainPageInfo.body)
is NetworkResponse.UnknownError -> Log.e("KotlinActivity","UnknownError->" + mainPageInfo.error)
}
}
}
或者在 ViewModel 中借助 LiveData 將返回值轉(zhuǎn)化為可觀察對(duì)象
class MainPageInfoViewModel:ViewModel() {
private val _mainPageInfo = MutableLiveData<MainPageInfo>()
val mainPageInfo: LiveData<MainPageInfo> get() = _mainPageInfo
fun getMainPageInfo(){
viewModelScope.launch(Dispatchers.IO){
val result = mainPageApi.getMainPageInfo()
withContext(Dispatchers.Main){
when(result){
is NetworkResponse.NetError -> Log.e("MainPageInfoViewModel",
"NetError->$result"
)
is NetworkResponse.Success -> _mainPageInfo.value = result.body
is NetworkResponse.UnknownError -> Log.e("MainPageInfoViewModel","UnknownError->" + result.error)
}
}
}
}
}
在 Activity 中使用
mainPageInfoModel = ViewModelProvider(this).get(MainPageInfoViewModel::class.java)
mainPageInfoModel.mainPageInfo.observe(this, Observer {
if (it != null) {
Log.e("KotlinActivity", "viewmodel獲取結(jié)果成功")
refreshViewWithViewModelResult(it);
} else {
Log.e("KotlinActivity", "viewmodel獲取結(jié)果為空")
}
})
mainPageInfoModel.getMainPageInfo()
暫時(shí)先這樣吧,基本上夠用了
康康主頁有驚喜~