深入理解Kotlin协程如何将回调改写成挂起函数

Posted 川峰

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解Kotlin协程如何将回调改写成挂起函数相关的知识,希望对你有一定的参考价值。




最简单的复写挂起函数的回调:

suspend fun suspendFunc() = suspendCoroutine<Int> 
    it.resumeWith(Result.success(1))

只不过真正的挂起需要真正的切换线程,如果直接调用的话相当于没有挂起。

suspend fun getUserSuspend(name: String) = suspendCoroutine<User>  continuation ->
    githubApi.getUserCallback(name).enqueue(object: Callback<User>
        override fun onFailure(call: Call<User>, t: Throwable) =
            continuation.resumeWithException(t)
        override fun onResponse(call: Call<User>, response: Response<User>) =
            response.takeIf  it.isSuccessful ?.body()?.let(continuation::resume)
                ?: continuation.resumeWithException(HttpException(response))
    )

suspend fun main()
    val user = getUserSuspend("bennyhuo")
    showUser(user)

支持取消的回调转协程的完整写法:

import com.bennyhuo.kotlin.coroutines.advanced.common.gitHubServiceApi
import kotlinx.coroutines.suspendCancellableCoroutine
import retrofit2.Call
import retrofit2.Callback
import retrofit2.HttpException
import retrofit2.Response
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

suspend fun <T> Call<T>.await(): T = suspendCancellableCoroutine  //可取消
    continuation ->
    continuation.invokeOnCancellation 
        cancel() //调用retrofit的取消方法
    

    enqueue(object: Callback<T> 
        override fun onFailure(call: Call<T>, t: Throwable) 
            continuation.resumeWithException(t)
        

        override fun onResponse(call: Call<T>, response: Response<T>) 
            response.takeIf  it.isSuccessful ?.body()?.also continuation.resume(it) 
                ?: continuation.resumeWithException(HttpException(response))
        
    )


suspend fun main() 
    val user = gitHubServiceApi.getUserCallback("flycumt").await()
    println(user)

当然实际开发中不用自己写,Retrofit的api中本身有实现await()方法,awaitResponse()方法等。

CompletableFuture 添加回调的写法:

import com.bennyhuo.kotlin.coroutines.advanced.utils.log
import kotlinx.coroutines.suspendCancellableCoroutine
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutionException
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

suspend fun main() 
    val result = CompletableFuture.supplyAsync 
        3
    .await()

    log(result)


suspend fun <T> CompletableFuture<T>.await(): T 
    if(isDone)
        try 
            return get()
         catch (e: ExecutionException) 
            throw e.cause ?: e
        
    
    return suspendCancellableCoroutine  //可取消
        cancellableContinuation ->
        cancellableContinuation.invokeOnCancellation 
            cancel(true) //取消
        

        whenComplete  value, throwable ->
            if(throwable == null)
                cancellableContinuation.resume(value)
             else 
                cancellableContinuation.resumeWithException(throwable.cause ?: throwable)
            
        
    

CompletableFuture本身也有实现await()方法。

模仿给Handler扩展添加可取消的支持:

suspend fun <T> Handler.run(block: () -> T) = suspendCoroutine<T>  continuation ->
    post 
        try 
            continuation.resume(block())
         catch (e: Exception) 
            continuation.resumeWithException(e)
        
    


suspend fun <T> Handler.runDelay(delay: Long, block: () -> T) = suspendCancellableCoroutine<T>  continuation ->

    val message = Message.obtain(this)  //Message obtain(Handler h, Runnable callback)
        try 
            continuation.resume(block())
         catch (e: Exception)
            continuation.resumeWithException(e)
        
    .also 
        it.obj = continuation //message.obj
    

    continuation.invokeOnCancellation 
        removeCallbacksAndMessages(continuation) //通过Handler的removeCallbacksAndMessages方法来取消回调, 参数就是前面设置的message.obj的值
    

    sendMessageDelayed(message, delay)


suspend fun main() 
    Looper.prepareMainLooper()

    GlobalScope.launch 
        val handler = Handler(Looper.getMainLooper())
        val result = handler.run  "Hello" 
        val delayedResult = handler.runDelay(5000) "World" 
        log(result, delayedResult)
        Looper.getMainLooper().quit()
    

    Looper.loop()

这个例子的主要意图是,Hanlder可以通过定义扩展函数的方式来延时获取一些东西,比如Activity刚创建的时候,拿不到view的宽和高,就可以使用这种方法。

上面三个例子主要是针对可取消的写法,如果实际用,不用自己写,直接导库就行。

参考:[Kotlin 协程] 常规操作:怎么把回调转成挂起函数?

以上是关于深入理解Kotlin协程如何将回调改写成挂起函数的主要内容,如果未能解决你的问题,请参考以下文章

如何在可组合函数回调中调用 Kotlin 协程?

深入理解Koltin协程:为什么学习 Kotlin 协程?

深入理解Kotlin协程协程作用域启动模式调度器异常和取消使用篇

深入理解Kotlin协程协程的分类与线程的区别

android kotlin 协程 理解挂起,恢复以及job

Kotlin挂起函数整理-基础