Kotlin协程和在Android中的使用总结(三 将回调和RxJava调用改写成挂起函数)

在这里插入图片描述
本文主要介绍以下,对于现有项目中的接口回调逻辑,和Rxjava中的订阅逻辑代码,修改成suspend挂起函数的形式。

1 接口回调的改写

一般接口回调的场景,我们都可以改成挂起函数的形式,比如网络接口的成功与失败的回调,动态权限的申请回调,或者一下其他的异步回调接口等。

(1)使用suspendCoroutine

以OkHttp中网络请求Call为例子,参考Kotlin Coroutines(协程) 完全解析(三),封装异步回调、协程间关系及协程的取消一文,我们可以对Call对象添加一个await()扩展函数,代码如下:

suspend fun <T> Call<T>.await(): T = suspendCoroutine { cont ->
    enqueue(object : Callback<T> {
        override fun onResponse(call: Call<T>, response: Response<T>) { 
            if (response.isSuccessful) {
                cont.Continuation(response.body()!!)
            } else {
                cont.resumeWithException(ErrorResponse(response))
            }
        }
        override fun onFailure(call: Call<T>, t: Throwable) { 
            cont.resumeWithException(t)
        } 
    })
}

即通过suspendCoroutine或者suspendCancellableCoroutine,使用其内部的Continuation或者CancellableContinuationresume方法来正常恢复协程,或者通过resumeWithException来实现原理的异常error回调。

public suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T =
    suspendCoroutineUninterceptedOrReturn { c: Continuation<T> ->
        val safe = SafeContinuation(c.intercepted())
        block(safe)
        safe.getOrThrow()
    }

public suspend inline fun <T> suspendCancellableCoroutine(
    crossinline block: (CancellableContinuation<T>) -> Unit
): T =
    suspendCoroutineUninterceptedOrReturn { uCont ->
        val cancellable = CancellableContinuationImpl(uCont.intercepted(), resumeMode = MODE_CANCELLABLE)
        // 和 suspendCoroutine 的区别就在这里,如果协程已经被取消或者已完成,就会抛出 CancellationException 异常
        cancellable.initCancellability()
        block(cancellable)
        cancellable.getResult()
    }

转化成挂起函数的思路也很简单,就是在原来的接口success和error回调上封装了一层,这样使得调用方可以使用协程的形式来减少接口嵌套的情况。

(2)使用suspendCancellableCoroutine

上面介绍了suspendCoroutine的使用,现在介绍一下suspendCancellableCoroutine的使用,两者的区别,就是suspendCancellableCoroutine可以通过Job.cancel()来取消(会抛出CancellationException),suspendCancellableCoroutine返回的CancellableContinuation对象,我们可以调用其resume、resumeWithException和抛出CancellationException来处理协程逻辑。

MainScope().launch {
  try {
    val user = fetchUser()
    //由于fetchUser中产生来异常,所以下面的updateUser不会被调用到
    updateUser(user)
  } catch (exception: Exception) {
    // Use try-catch or CoroutinesExceptionHandler to handle exceptions.
    Log.d("demo", "$exception") // Prints "java.io.IOException".
  }
  
  // 如果上面使用了try-catch来捕获异常,那么下面的代码仍然可以执行到
  doSomething()
}

// Fetches the user data from server.
private suspend fun fetchUser(): User = suspendCancellableCoroutine { 
cancellableContinuation ->
  fetchUserFromNetwork(object : Callback {
    override fun onSuccess(user: User) {
      cancellableContinuation.resume(user)
    }

    override fun onFailure(exception: Exception) {
      // Invokes this line since the callback onFailure() is invoked.
      cancellableContinuation.resumeWithException(exception)
    }
  })
}

private fun fetchUserFromNetwork(callback: Callback) {
  Thread {
    Thread.sleep(3_000)
    
    //callback.onSuccess(User())
    // Invokes onFailure() callback with "IOException()".
    callback.onFailure(IOException())
  }.start()
}

private fun updateUser(user: User) {
  // Updates UI with [User] data.
}

interface Callback {
  fun onSuccess(user: User)
  fun onFailure(exception: Exception)
}

class User

2 RxJava的订阅回调转换成挂起函数

现在大多数项目都有使用RxJava,虽然很多文字宣传可以使用协程来替换RxJava的使用场景,但是一方面协程API还在不断的发展,另一方面现在的RxJava代码尚能接受,全盘替换成本和风险高,所以两者一起使用不失为一个最佳选择。

为了方便将RxJava的调用转为协程的挂起函数形式,jetbrains官方专门给出了实现,即使用kotlinx-coroutines-rx2

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.3.2"

所有的协程构造器
以使用Single为例子,调用Single.await()可以帮我们将RxJava转换成一个suspendCancellableCoroutine,流程跟我们上面的转换其实是一样的,源码如下:
在这里插入图片描述
使用示例:

MainScope().launch {
  CoroutineScope(Dispatchers.Main).launch {
     try {
    	val user = fetchUserFromServer().await()
    	updateUser(user)
  	} catch (e: Exception) {
    	Log.d("demo", "(4) {$e}, ${Thread.currentThread()}")
  	}
  }
}

private fun fetchUserFromServer(): Single<User> =
  Single.create<User> {
    Log.d("demo", "(1) fetchUserFromServer start, ${Thread.currentThread()}")
    Thread.sleep(3_000)
    it.onSuccess(User())
    Log.d("demo", "(2) fetchUserFromServer onSuccess, ${Thread.currentThread()}")
  }.subscribeOn(Schedulers.io())
      .observeOn(AndroidSchedulers.mainThread())

private fun updateUser(user: User) {
  Log.d("demo", "(3) updateUser, ${Thread.currentThread()}")
}

class User

也就是我们原来的方法不需要做修改,直接在调用处使用kotlinx-coroutines-rx2中定义的await函数就行,它会挂起当前协程的执行,然后在RxJava返回结果后恢复协程的执行。当然别忘了调用的时候要添加异常try-catch。

对于RxJava中的Maybe,Observable,kotlinx-coroutines-rx2都提供了相应的扩展函数来供我们使用,赶紧在你的代码里尝试一下吧!

在这里插入图片描述

总结:

将回调函数或者RxJava的调用转换成协程的挂起函数调用,原理还是比较简单的,关于suspendCoroutinesuspendCancellableCoroutine的原理,
它们的关键实现都是调用suspendCoroutineUninterceptedOrReturn()函数,它的作用是获取当前协程的实例,并且挂起当前协程或者不挂起直接返回结果。

协程中还有两个常见的挂起函数使用到了suspendCoroutineUninterceptedOrReturn()函数,分别是delay()yield()
delay()大家已经很熟悉了,yield()就是让出当前协程的执行权,交给其他协程执行,可以实现类似交替执行的效果,暂时还没想到适用场景。

具体的源码分析可以参考文末的第一篇文章。

在一个Scope中调用其他的挂起函数时,我们还是要加上try-catch(注意应该加在协程体内部,在整个Scope上包try-catch是无法捕获异常的)或者使用CoroutinesExceptionHandler处理逻辑的,虽然kotlin宣称是没有受检异常checked exceptions的,但是为了流程的逻辑处理和代码的正常运行,我们还是要在调用处去捕获处理。

参考:
Kotlin Coroutines(协程) 完全解析(三),封装异步回调、协程间关系及协程的取消
Kotlin Coroutines in Android — Suspending Functions

发布了82 篇原创文章 · 获赞 86 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/unicorn97/article/details/105163021
今日推荐