站在巨人的肩膀上:https://medium.com/swlh/kotlin-coroutines-in-android-suspending-functions-8a2f980811f8

前言:

① 使用 suspendCancellableCoroutine 和 suspendCoroutine 可以将回调函数转换为协程
② SuspendCancellableCoroutine 返回一个 CancellableContinuation, 它可以用 resume、resumeWithException 来处理回调 和抛出 CancellationException 异常。它与 suspendCoroutine的唯一区别就是 SuspendCancellableCoroutine 可以通过 cancel() 方法手动取消协程的执行,而 suspendCoroutine 没有该方法。
③ 尽可能使用 suspendCancellableCoroutine 而不是 suspendCoroutine ,因为协程的取消是可控的

举个例子:使用网络请求数据时,如果请求时间过长,用户可以手动取消掉协程的执行。这时会抛出一个 CancellationException 异常,但是将该异常try{}catch{}捕获后就不会影响后续代码的执行。而使用 suspendCoroutine 只能干等着被 resume 或者 resumeWithException ,因为它没有该功能。

注:
① Kotlin没有检查异常,但是我们仍然需要在try-catch中处理所有可能的异常。否则,该应用程序将崩溃。但是 CancellationException 是个意外,它也会抛出异常,但是并不会导致程序崩溃。具体原因可参考 https://medium.com/swlh/kotlin-coroutines-in-android-basics-9904c98d4714
② SuspendCancellableCoroutine 的 cancel() 方法理解:调用 cancel() 后协程不再往下执行,抛出 CancellationException 异常,但是程序不会崩溃。而suspendCoroutine没有该方法,因此只能傻等…直到被通知 resume 或 resumeWithException。

在很多情况下,对于耗时操作,我们只需要将其声明为suspend函数并使用withContext(Dispatchers.IO)将其切换到IO线程即可。这样,耗时操作就会在IO线程下执行并在任务完成后,从挂起点继续往下执行。这是协程的基本写法,举个例子:

Kotlin获取系统时间 android kotlin suspendcoroutine_主线程

但是假设我们已经在线上有一个Android项目。许多异步任务用于等待读取数据库或从服务器获取数据。使用回调函数可能是一种处理主线程上数据的方法。我们都知道,回调写多了自己都能看晕,而且无法保持协程(用同步的方式写异步代码)这一要求。
这时候,suspendCancellableCoroutine就派上用场了。它就是专门用来将回调函数转换成协程的作用域

将回调函数转换为协程:suspendCancellableCoroutine

cancellableContinuation.resume

让我们来分析一下,这里使用 suspendCancellableCoroutine 声明了作用域,并在里面执行耗时操作(fetchUserFromNetwork)。该耗时操作接收一个 Callback,这其实就是我们熟悉的回调写法。

创建匿名内部类(object : Callback),如果成功返回,即回调 cancellableContinuation.resume(user) 方法,如果出现异常,就回调 cancellableContinuation.resumeWithException(exception) 方法。

Kotlin获取系统时间 android kotlin suspendcoroutine_抛出异常_02

其实说到底很简单,就是将回调交给 CancellableContinuation 去处理。而内部是怎么处理的,用户并不需要关心。用到的还是之前的回调逻辑,成功就是 onSuccess() 方法,失败就是 onFailure() 方法。只是我们在这里将回调的写法转换成了协程,即 onSuccess -> resume , onFailure -> resumeWithException。
当resume处理完之后,就会将处理完的值赋值给 fetchUser() 这个挂起函数,接着主线程就可以继续往下执行 updateUser(user)。即从挂起点继续往下执行。

cancellableContinuation. resumeWithException

注:为了演示 resumeWithException 是如何处理异常的,我们手动抛出了一个异常。此时 resumeWithException(exception) 就会执行,但是由于我们在主线程中 try{}catch{}捕获了异常,所以程序能够正常往下执行。

Kotlin获取系统时间 android kotlin suspendcoroutine_kotlin_03

cancellableContinuation.cancel

还是上面那个例子,做一下改动,我们在 fetchUser 方法末尾手动 cancel 掉任务,看看会发生什么

Kotlin获取系统时间 android kotlin suspendcoroutine_主线程_04

还记得我们前面说过的 cancel() 方法的作用吗?这里再回顾一下,Kotlin没有检查异常,但是我们仍然需要在try-catch中处理所有可能的异常。否则,该应用程序将崩溃。但是 CancellationException 是个意外,它也会抛出异常,但是并不会导致程序崩溃。如果我们对该异常进行捕获,则协程能继续往下执行。如果不捕获异常,则终止该协程的后续操作。