1. 程式人生 > 其它 >Kotlin Coroutine(協程): 四、+ Retrofit

Kotlin Coroutine(協程): 四、+ Retrofit

Coroutine(協程) + Retrofit; 異常及狀態碼 統一處理. 並使用 DSL, 你想要的的全都有

@

目錄

前言

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) }
}

總結

好吧, 沒有總結