@
前言
Retrofit 從 2.6.0 版本開始, 內建了對 Kotlin Coroutines 的支援. 我們統一處理異常及響應狀態碼, 使用DSL 讓程式碼更加漂亮整潔
先導包:
//協程
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3'
// Retrofit
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.2.0"
一、準備工作
1.BaseResp
統一的返回實體, 包含 code, msg, data; 這種伺服器響應結構比較常見
data class BaseResp<T>(
// 響應狀態碼
var code: Int = -1,
// 響應資訊
var msg: String = "",
// 資料實體
var data: T? = null
)
2.Api
這裡是一個驗證碼請求介面, 使用的 Json 提交引數
interface Api {
@POST("api/getCode")
suspend fun getCode(@Body body: RequestBody): BaseResp<JsonObject?>
}
3.ApiManager
使用 列舉單例模式; 包括 初始化 Retrofit, OkHttpClient, 新增請求Token, 請求日誌列印.
/**
* Retrofit 管理類;
*/
enum class ApiManager {
INSTANCE;
private val retrofit: Retrofit
val mApi: Api
private val mMediaTypeJson: MediaType?
init {
retrofit = Retrofit.Builder()
.baseUrl("https://... 伺服器地址")
.client(initOkhttpClient())
.addConverterFactory(GsonConverterFactory.create())
.build()
mApi = retrofit.create(Api::class.java)
mMediaTypeJson = "application/json; charset=utf-8".toMediaTypeOrNull()
}
private fun initOkhttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.retryOnConnectionFailure(true) //重試
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.addNetworkInterceptor(initTokenInterceptor())
.addInterceptor(initLogInterceptor())
.build()
}
private fun initTokenInterceptor(): Interceptor = object : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
// 為請求新增 Token
val token: String = SpUtils.getToken()
val originalRequest = chain.request()
return if (token.isNullOrEmpty()) {
chain.proceed(originalRequest)
}else {
token = "Bearer $token"
val updateRequest = originalRequest.newBuilder().header("token", token).build()
chain.proceed(updateRequest)
}
}
}
/*
* 日誌攔截器
* */
private fun initLogInterceptor(): HttpLoggingInterceptor {
if(!BuildConfig.DEBUG){ //生產環境不打日誌
return HttpLoggingInterceptor()
}
val interceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
override fun log(message: String) {
Log.i("Retrofit_network", message)
}
})
interceptor.level = HttpLoggingInterceptor.Level.BODY
return interceptor
}
// 請求資料轉 Json 形式;
open fun getJsonBody(data: Any): RequestBody {
val strEntity = mGson.toJson(data)
return strEntity.toRequestBody(mMediaTypeJson)
}
}
二、開始使用
1.簡單使用
以下程式碼在 Activity 中;
lifecycleScope.launch {
val param = ApiManager.INSTANCE.getJsonBody(mapOf("phone" to "13333333333", "codeType" to "1"))
val data = ApiManager.INSTANCE.mApi.getCode(param).data
binding.tvTitle.text = data.toString()
}
是不是非常簡單, 協程內除了準備引數 就只剩兩行程式碼.
上面程式碼並沒有處理異常, 及請求失敗的情況. 下面我們就開始封裝請求
2.DSL
class RetrofitDSL<T> {
internal lateinit var api: (suspend () -> BaseResp<T?>)
private set
internal var onSuccess: ((BaseResp<T?>) -> Unit)? = null
private set
internal var onFailed: ((msg: String?, code: Int) -> Unit)? = null
private set
internal var onComplete: (() -> Unit)? = null
private set
/**
* 獲取資料
* @param block (T) -> Unit
*/
fun api(block: suspend () -> BaseResp<T?>) {
this.api = block
}
/**
* 獲取資料成功
* @param block (T) -> Unit
*/
fun onSuccess(block: (BaseResp<T?>) -> Unit) {
this.onSuccess = block
}
/**
* 獲取資料失敗
* @param block (msg: String, errorCode: Int) -> Unit
*/
fun onFailed(block: (msg: String?, code: Int) -> Unit) {
this.onFailed = block
}
/**
* 訪問完成
* @param block () -> Unit
*/
fun onComplete(block: () -> Unit) {
this.onComplete = block
}
}
看到 onSuccess, onFailed 是不是有熟悉的感覺!
3.擴充套件函式
我們需要建立一個 .kt 檔案; 為 協程作用域 新增擴充套件函式
fun <T> CoroutineScope.retrofit(
dsl: RetrofitDSL<T>.() -> Unit
) {
launch {
val retrofitDsl = RetrofitDSL<T>()
retrofitDsl.dsl()
try {
val result = retrofitDsl.api()
when(val code = result.code){
200 -> retrofitDsl.onSuccess?.invoke(result)
... // 其他響應碼
else -> retrofitDsl.onFailed?.invoke(result.msg, code)
}
} catch (e: Exception) {
retrofitDsl.onFailed?.invoke(e.message, -10)
}
... // 其他異常型別
finally {
retrofitDsl.onComplete?.invoke()
}
}
}
這裡 只寫了 Exception; 需要判斷多種異常的話, 往 catch 後面加就行了. 同樣, 響應碼也隨便加, 想怎麼處理就怎麼處理.
4.請求發起
以下程式碼在 ViewModel 中;
// 注意: <JsonObject> 與 Api 中配置的請求響應結果型別一致 BaseResp<JsonObject?>
viewModelScope.retrofit<JsonObject> {
api { ApiManager.INSTANCE.mApi.getCode(param) }
onSuccess { // it = BaseResp<JsonObject?>
_number.set(it.data?.getAsJsonPrimitive("code")?.asString)
}
onFailed { msg, _ ->
Toast.makeText(getApplication(), msg, Toast.LENGTH_SHORT).show()
}
onComplete {
Toast.makeText(getApplication(), "完事了", Toast.LENGTH_SHORT).show()
}
}
onSuccess, onFailed, onComplete 都可以不寫, 想寫誰就寫誰;
// 不關心請求結果的話; 一個 api 就夠了
viewModelScope.retrofit<JsonObject> {
api { ApiManager.INSTANCE.mApi.getCode(param) }
}
總結
好吧, 沒有總結