Android开发:协程Coroutines&LifecycleCoroutineScop&Retrofit网络请求

1,984 阅读6分钟

Retrofit支持Coroutines

Retrofit 自从更新到了 2.6.0 版本,内置了对 Kotlin Coroutines 的支持,进一步简化了使用 Retrofit 和协程来进行网络请求的过程,这已经是2019年的事情。
现在2020年已经到了一半了,目前版本已经到了2.9.0了,顺带一提Okhttp3也到了4.7.2

一般的网络请求需要什么

在Word文档里,大致画一下图:

从图片可以看出我们其实不仅需要除妖处理数据返回正确与否,其实还需各种状态去判断,当前请求的情况,所以我们需要引入状态这个因素进入网络请求框架。

Lifecycle中的LifecycleCoroutineScop

这个类需要引入:androidx.lifecycle:lifecycle-runtime-ktx: lastest version 截至本文章发布,我找到的最新版本lastest version: 2.3.0-alpha03
源码:

/**
 * [CoroutineScope] tied to this [LifecycleOwner]'s [Lifecycle].
 *
 * This scope will be cancelled when the [Lifecycle] is destroyed.
 *
 * This scope is bound to
 * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
 */
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
    get() = lifecycle.coroutineScope
    
    
/**
 * [CoroutineScope] tied to this [Lifecycle].
 *
 * This scope will be cancelled when the [Lifecycle] is destroyed.
 *
 * This scope is bound to
 * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
 */
val Lifecycle.coroutineScope: LifecycleCoroutineScope
    get() {
        while (true) {
            val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?
            if (existing != null) {
                return existing
            }
            val newScope = LifecycleCoroutineScopeImpl(
                this,
                SupervisorJob() + Dispatchers.Main.immediate
            )
            if (mInternalScopeRef.compareAndSet(null, newScope)) {
                newScope.register()
                return newScope
            }
        }
    }

先不看代码,我们看一下那个注释,看到熟悉的CoroutineScope 对~!协程 ViewModel那边也有一个,有兴趣的同学可以去看看。再看看LifecycleCoroutineScope这个类:

/**
 * [CoroutineScope] tied to a [Lifecycle] and
 * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
 *
 * This scope will be cancelled when the [Lifecycle] is destroyed.
 *
 * This scope provides specialised versions of `launch`: [launchWhenCreated], [launchWhenStarted],
 * [launchWhenResumed]
 */
abstract class LifecycleCoroutineScope internal constructor() : CoroutineScope {
    internal abstract val lifecycle: Lifecycle

    /**
     * Launches and runs the given block when the [Lifecycle] controlling this
     * [LifecycleCoroutineScope] is at least in [Lifecycle.State.CREATED] state.
     *
     * The returned [Job] will be cancelled when the [Lifecycle] is destroyed.
     * @see Lifecycle.whenCreated
     * @see Lifecycle.coroutineScope
     */
    fun launchWhenCreated(block: suspend CoroutineScope.() -> Unit): Job = launch {
        lifecycle.whenCreated(block)
    }

    /**
     * Launches and runs the given block when the [Lifecycle] controlling this
     * [LifecycleCoroutineScope] is at least in [Lifecycle.State.STARTED] state.
     *
     * The returned [Job] will be cancelled when the [Lifecycle] is destroyed.
     * @see Lifecycle.whenStarted
     * @see Lifecycle.coroutineScope
     */

    fun launchWhenStarted(block: suspend CoroutineScope.() -> Unit): Job = launch {
        lifecycle.whenStarted(block)
    }

    /**
     * Launches and runs the given block when the [Lifecycle] controlling this
     * [LifecycleCoroutineScope] is at least in [Lifecycle.State.RESUMED] state.
     *
     * The returned [Job] will be cancelled when the [Lifecycle] is destroyed.
     * @see Lifecycle.whenResumed
     * @see Lifecycle.coroutineScope
     */
    fun launchWhenResumed(block: suspend CoroutineScope.() -> Unit): Job = launch {
        lifecycle.whenResumed(block)
    }
}

然后我们关注到一点,我们都希望网络请求能在Activity or Fragmentdestroy方法中自己结束,看看注释The returned [Job] will be cancelled when the [Lifecycle] is destroyed.,帮我们完成了。其中launchWhenCreatedlaunchWhenStarted是我们关注的方法,这个state在这里:

  /**
         * Created state for a LifecycleOwner. For an {@link android.app.Activity}, this state
         * is reached in two cases:
         * <ul>
         *     <li>after {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} call;
         *     <li><b>right before</b> {@link android.app.Activity#onStop() onStop} call.
         * </ul>
         */
        CREATED,

        /**
         * Started state for a LifecycleOwner. For an {@link android.app.Activity}, this state
         * is reached in two cases:
         * <ul>
         *     <li>after {@link android.app.Activity#onStart() onStart} call;
         *     <li><b>right before</b> {@link android.app.Activity#onPause() onPause} call.
         * </ul>
         */
        STARTED,

可以根据个人需求选择,我的选择的方案是launchWhenCreated进行后续开发。

封装网络请求

在开展封装前,我们先定义一个接口

interface JsonResult {
    fun isLegal() : Boolean
}

isLegal()用于判断网络接口返回是否为正确的返回内容,这么做的好处是方便适配多变的接口返回的数据结构。
以下开展以高德地图的天气接口作为参考: 新建一个基础类继承这接口

open class JsonData : JsonResult {

    var status = ""

    override fun isLegal(): Boolean = status == "1"

}

然后我们再新建一个类去继承这个基础类

data class Weather(@SerializedName("lives")
                   val lives: ArrayList<LivesItem>,
                   @SerializedName("count")
                   val count: String = "",
                   @SerializedName("infocode")
                   val infocode: String = "",
                   @SerializedName("info")
                   val info: String = "") : JsonData()

这个类基础类和后面这个解析类需要根据你接口进行调整,高德地图的接口返回是这样的:

{"status":"1","count":"1","info":"OK","infocode":"10000","lives":[{"province":"广东","city":"白云区","adcode":"440111","weather":"雨","temperature":"23","winddirection":"西","windpower":"≤3","humidity":"98","reporttime":"2020-06-09 22:52:20"}]}

基本的信息都知道了,由于开发现在普遍都开始利用了LiveData进行分发的,那我们需要进行请求过程中各个状态进行处理,先看代码:

class KResult<T>(var status:String, var response: T? = null){

    companion object{

        const val Loading = "Loading"
        const val Success = "Success"
        const val Failure = "Failure"
        const val OutTime = "OutTime"

    }


    private var loading:(()->Unit)? = null
    fun whenLoading(loading:()->Unit){
        this.loading = loading
    }

    private var success:(T?.()->Unit)? = null
    fun whenSuccess(success:(T?.()->Unit)){
        this.success = success
    }

    private var failed:(T?.()->Unit)? = null
    fun whenFailed(failed:(T?.()->Unit)){
        this.failed = failed
    }

   private var outTime:(()->Unit)? = null
    fun whenOutTime(outTime:()->Unit){
        this.outTime = outTime
    }


    fun  whenStatus(result: KResult<T>.()->Unit){
        result.invoke(this)
        when(status){
            Loading ->{
                loading?.invoke()
            }
           Success ->{
                success?.invoke(response)
            }
            Failure ->{
                failed?.invoke(response)
            }
            OutTime ->{
                outTime?.invoke()
            }
        }
    }

}

所以结合以上这些,我们就能把网络封装写成如下:

class CallResult<T: JsonResult> constructor(private var owner: LifecycleOwner?, callResult: CallResult<T>.()->Unit) {

    init {
        callResult()
    }
    private var data : MutableLiveData<KResult<T>>? = null
    var repeatWhenOutTime = 0
    private var tryCount = 0
    fun post(data : MutableLiveData<KResult<T>>){
        this.data = data
    }

    fun hold(result: suspend () -> Response<T>){
        var response: Response<T>?
        var netJob: Job? = null
        var call : KResult<T>
        owner?.apply {
            netJob = lifecycleScope.launchWhenCreated {

                call = KResult(KResult.Loading)
                makeCall(call)
                onLoading?.invoke()

                response = tryRepeat(result)

                if (response != null) {
                    response?.run {
                        if(code() != 200){
                            call = KResult(KResult.OutTime)
                            makeCall(call)
                            loadingOutTime?.invoke(call)
                            netJob?.cancel()
                        }else{
                            build(response)
                        }
                    }
                } else {
                    call = KResult(KResult.OutTime)
                    makeCall(call)
                    loadingOutTime?.invoke(call)
                    netJob?.cancel()
                }
            }
        }
    }

    private suspend fun tryRepeat(result: suspend () -> Response<T>) : Response<T>?{
        return try {
            val output = withTimeoutOrNull(10000){
                withContext(Dispatchers.IO){
                    result.invoke()
                }
            }
            Logger.eLog("CallResult","output : $output  tryCount : $tryCount")
            return if(output == null && tryCount < repeatWhenOutTime){
                tryCount ++
                tryRepeat(result)
            }else{
                output
            }
        }catch (e:java.lang.Exception){
            e.printStackTrace()
            null
        }

    }

    private fun build(response: Response<T>?) {
        response?.apply {
            val call : KResult<T>
            try {
                if (body() == null) {
                    call = KResult(KResult.Failure, null)
                    makeCall(call)
                    onError?.invoke(call)
                } else {
                    if (body()!!.isLegal()) { // 正确返回成功
                        call  = KResult(KResult.Success, body())
                        makeCall(call)
                        onSuccess?.invoke(call)
                    } else {
                        call = KResult(KResult.Failure, body())
                        makeCall(call)
                        onError?.invoke(call)
                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

    private fun makeCall(call: KResult<T>) : KResult<T> {
        data?.postValue(call)
        return call
    }

    private var onLoading: (() -> Unit)? = null

    fun loading(onLoading: (() -> Unit)): CallResult<T> {
        this.onLoading = onLoading
        return this
    }

    private var loadingOutTime: ((result: KResult<T>) -> Unit)? = null
    fun outTime(loadingOutTime: ((result: KResult<T>) -> Unit)) : CallResult<T> {
        this.loadingOutTime = loadingOutTime
        return this
    }


    private var onSuccess: ((result: KResult<T>) -> Unit)? = null

    private var onError: ((result: KResult<T>) -> Unit)? = null

    fun success(onSuccess: ((result: KResult<T>) -> Unit)): CallResult<T> {
        this.onSuccess = onSuccess
        return this
    }


    fun error(onError: ((result: KResult<T>) -> Unit)): CallResult<T> {
        this.onError = onError
        return this
    }


}

其中利用到DSL语法,待会举例子,首先关注点: lifecycleScope.launchWhenCreated 中调用这个withContext(Dispatchers.Main) 根据源码:

suspend fun <T> Lifecycle.whenStateAtLeast(
    minState: Lifecycle.State,
    block: suspend CoroutineScope.() -> T
) = withContext(Dispatchers.Main.immediate) {
    val job = coroutineContext[Job] ?: error("when[State] methods should have a parent job")
    val dispatcher = PausingDispatcher()
    val controller =
        LifecycleController(this@whenStateAtLeast, minState, dispatcher.dispatchQueue, job)
    try {
        withContext(dispatcher, block)
    } finally {
        controller.finish()
    }
}

一、这里使用了withContext(Dispatchers.Main.immediate),所以我这里withContext(Dispatchers.Main)有没有必要这么写呢?两个区别在哪里?留个问题给大家思考
二、第二个是withTimeoutOrNull这个方法,源码注释

Runs a given suspending block of code inside 
a coroutine with a specified [timeout][timeMillis] 
and returns `null` if this timeout was exceeded.

简单理解就是超时时候返回结果null,方便我们判断是否超时。

如何使用

Retrofit对应的接口,加上suspend

 @GET("/v3/weather/weatherInfo")
   suspend fun getWeather(@Query("key") key:String,@Query("city") city:String) : Response<Weather>
   

在执行网络请求的地方,如下:

fun getWeather(code:String,call:MutableLiveData<KResult<Weather>>){
        CallResult<Weather>(owner){
            post(call)
            hold {  main.getWeather("XXXXXXXXXXXXX",code) }
        }

    }

在接受Weather对应LiveData的地方如下:

weather.observe(this, Observer {
            it?.apply {
                whenStatus {
                    whenLoading { 
                        //处理加载
                    }
                    whenSuccess {
                        //处理成功 这里是this不是it 对应 Weather?
                    }
                    whenFailed { 
                        //处理失败
                    }
                    whenOutTime { 
                       //处理超时
                    }
                }
            }
        })

总结

Kotlin的协程Coroutines,并非很深的内容,属于对线程池的进一步封装实现调度等等 可以看到很明显的三个优点:
一、很容易离开主线程,参考RxJava,对比下协程可以让开发者只需要更少的代码就完成这个工作,而且没有累人的回调处理。 二、样板代码最少。协程完全活用了 Kotlin 语言的能力,包括 suspend 方法。让编写线程任务过程就和编写普通的代码块差不多。 三、当一个操作不再需要被执行时或者超时或者受LifeOwner的不同状态影响,协程会自动取消它。