在Android中用Kotlin协程将callback转换为同步调用

lifecycleScope
viewModelScope

applicationScope

coroutineScope

GlobalScope

的区别

使用一个和应用生命周期一样长的自定义的CoroutineScope解决这个问题,自定义CoroutineScope的优势在于可以使用自定义的CoroutineExceptionHandler和自定义的thread pool(可以定制一个完全符合自己需求的CoroutineContext)。

我们使用applicationScope来命名这个自定义的CoroutineScope,并且它必须包含一个SupervisorJob(),这样任何子coroutine的执行失败不会影响到该scope下其他的子coroutines,也不会导致applicationScope本身的退出。

class MyApplication : Application() {
  // No need to cancel this scope as it'll be torn down with the process
  val applicationScope = CoroutineScope(SupervisorJob() + otherConfig)
}


我们不需要处理applicationScope的退出,因为它的生命周期和我们的应用一样长,所以我们也不需要持有SupervisorJob()的句柄。我们可以使用applicationScope来启动那些需要和我们App生命周期一样长的coroutines。
对于那些我们不希望被取消执行的coroutines,我们都可以使用applicationScope。
当你创建一个新的Repository实例的时候,就可以传入applicationScope。

suspend
withContext
Dispatchers.IO

调度程序:调度程序帮助协程决定必须在哪个线程上完成工作。
调度程序主要分为三种类型:IO、Default 和 Main。
IO调度程序用于完成网络和磁盘相关的工作。
Default用于执行 CPU 密集型工作。
Main是Android的UI线程。

还有一个在Android上可能不常用
Dispatchers.Unconfined
不限于任何特定线程的协程调度程序。它在当前调用帧中执行协程的初始延续,并让协程在相应挂起函数使用的任何线程中恢复,而无需强制执行任何特定的线程策略。
它不会改变线程。当它启动时,它在启动它的线程上运行。如果恢复,它将在恢复它的线程上运行。

假设有一个使用回调的库

Library.doSomething(object : Listener {
    override fun onSuccess(result: Result) {
    }
    override fun onError(throwable: Throwable) {
    }
})

用协程将它转换为同步函数

suspend fun doSomething(): Result {

    return suspendCoroutine { continuation ->
        Library.doSomething(object : Listener {
            override fun onSuccess(result: Result) {
                continuation.resume(result)
            }
            override fun onError(throwable: Throwable) {
                continuation.resumeWithException(throwable)
            }
        })
    }
}

步骤:
1. 创建一个suspend函数来返回 onSucess的 Result
2. 用 suspendCoroutine返回块 来返回
3. 在onSucess回调中,用 continuation.resume(result) 来恢复
4. 用 continuation.resumeWithException(throwable) 抛出错误

在launch块里使用 创建的suspend函数

launch {
    val result = doSomething()
}

有时候需要用到 suspendCancellableCoroutine, 而不是 suspendCoroutine
假设该库还支持取消任务

val id = Library.doSomething(object : Listener {
    override fun onSuccess(result: Result) {
    }
    override fun onError(throwable: Throwable) {
    }
})
可以用id来取消任务
Library.cancel(id)

转换成

suspend fun doSomething(): Result {
    return suspendCancellableCoroutine { continuation ->
        val id = Library.doSomething(object : Listener {
            override fun onSuccess(result: Result) {
                continuation.resume(result)
            }
            override fun onError(throwable: Throwable) {
                continuation.resumeWithException(throwable)
            }
        })
        continuation.invokeOnCancellation {
            Library.cancel(id)
        }
    }
}

withContext是一个suspend函数

withContext(Dispatchers.Default) {
    delay(2000)
    return@withContext 10
}

withContext不会创建新的协程,它只会改变现有协程的上下文,它是一个suspend函数,
而launch和async创建一个新的协程,它们不是suspend函数。

用async 启动协程,然后用await得到返回结果。

launch{}返回 一个Job并且 不携带任何结果值,
async{}返回一个实例Deferred t,该实例 有一个await()函数, 用来返回协程结果。

launch和async之间的另一个区别在于异常处理。
如果launch块内出现任何异常,如果我们没有处理它,应用程序就会崩溃。
但是,如果async块内出现任何异常,它会存储在结果中Deferred并且不会传递到其他任何地方,除非我们处理它,否则它将被悄悄丢弃。

将Camera API由回调改协程

suspend fun CameraManager.openCamera(cameraId: String): CameraDevice? =
    suspendCoroutine { cont ->
        val callback = object : CameraDevice.StateCallback() {
            override fun onOpened(camera: CameraDevice) {
                cont.resume(camera)
            }

            override fun onDisconnected(camera: CameraDevice) {
                cont.resume(null)
            }

            override fun onError(camera: CameraDevice, error: Int) {
                // assuming that we don't care about the error in this example
                cont.resume(null) 
            }
        }
        openCamera(cameraId, callback, null)
    }

生产者/消费者

val channel = Channel<ByteArray>
send()
receive()

挂起函数应该能够安全地从主线程调用

挂起函数应该是主线程安全的,这意味着,您可以安全地从主线程调用挂起函数。如果某个类在协程中执行长期运行的阻塞操作,那么该类负责使用 withContext 将执行操作移出主线程。这适用于应用中的所有类,无论其属于架构的哪个部分都不例外。

withContext(Dispatchers.IO) {
    ...
}

返回协程中产生的值

val result = runBlocking(CommonPool) {
         suspendingFunctionThatReturnsMyValue()
}

将会一直阻塞,直到结果可用

runBlocking 将运行一个新的协程,并阻塞当前线程,知道协程运行完成。
这个函数,不应该从 协程中调用。
它设计成 桥接 普通的 代码块 到 suspend风格的代码库(也就是协程写的代码库)。
一般用在 main函数, 或者 测试代码中

最好是同 withContext(Dispachedr.IO) 一起用。

因为阻塞主线程不是一个好注意。并且当有多个协程 同 runBlocking唤起 的协程,运行在同一个线程时,会将其他协程也阻塞住。

Kotlin has special ways to deal with it, such as the IO Dispatcher, which will run the code on its own, isolated thread so that it doesn’t disrupt your other coroutines.

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注