Kotlin Coroutine(协程): 四、+ Retrofit


前言

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

总结

好吧, 没有总结

上一篇: Kotlin Coroutine(协程): 三、了解协程
下一篇: Activity: 一、生命周期,启动模式,FLAG,Context


版权声明:本文为mfd521原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。