Kotlin之协程——从回调转向协程

前言

在此步骤中,您将开始将一个代码库转换为使用协程。为此,我们将向ViewModelRepositoryRoomRetrofit添加协程。

在将架构的各个部分转换为使用协程之前,最好先了解每个部分的作用。

  1. MainDatabase使用Room实现一个数据库,以保存和加载Title
  2. MainNetwork实现一个网络API,用于提取新标题。它使用Retrofit提取标题。Retrofir配置为随即返回错误和模拟数据,但除此之外其行为就像是在发出实际网络请求一样。
  3. TitleRepository实现了一个API,用于通过结合来自网络和数据库的数据来提取火树安心标题。
  4. MainViewModel表示屏幕的状态,并负责处理事件。它会只是代码库在用户点按屏幕时刷新标题。

由于网络请求由界面事件驱动,并且我们希望根据这个事件启动协程,那么自然而然应在ViewModel中开始使用协程。

1、回调版本

打开MainViewModel.kt可查看refreshTitle的声明。 .MainViewModel.kt

/**
* Update title text via this LiveData
*/
val title = repository.title

// ... other code ...

/**
* Refresh the title, showing a loading spinner while it refreshes and errors via snackbar
*/
fun refreshTitle() {
    // TODO: Convert refreshTitle to use coroutines
    _spinner.value = true
    repository.refreshTitleWithCallbacks(object: TitleRefreshCallback {
        override fun onCompleted() {
            _spinner.postValue(false)
        }
        
        override fun onError(cause: Throwable) {
            _snackBar.postValue(cause.message)
            _spinner.postValue(false)
        }
    })
}

每次用户点击屏幕时,系统都会调用此函数,这会导致代码库刷新标题,然后将标题写入数据库。

此实现使用回调来执行几项操作:

  • 在开始查询之前,它使用_spinner.value = true显示一个加载旋转图标
  • 当获得结果时,它使用_spinner.value = false清楚加载旋转图标
  • 如果出现错误,它会指示系统显示信息提示控件并清除旋转图标

请注意,系统不会向onCompleted回调函数传递title。由于我们将所有标题写入Room数据库,因此界面通过观察由Room更新的LiveData来更新为最新标题。

在协程的更新中,我们将保持完全相同的行为。使用Room数据库等可观察的数据源自动让界面保持最新状态,不失为一种好模式。

object: TitleRefreshCallback是什么?
这是在Kotlin中构建匿名类的方法。它会创建一个实现TitleRefreshCallback的新对象。

2、协程版本

我们来使用协程重写refreshTitle!

由于我们需要立即获得结果,因此,我们在代码库(TitleRepository.kt)中创建空的挂起函数。定义一个使用suspend运算符的新函数,以告知Kotlin它可与协程配合使用。

TitleRepository.kt

suspend fun refreshTitle() {
    // TODO: Refresh from network and write to database
    delay(500)
}

完成瓷片博客后,您将更新此代码,以使用Retrofit和Room提取新标题,并使用协程将标题写入数据库。现在,它会等待500毫秒来假装在执行操作,然后再继续。

MainViewModel中,将refreshTitle的回调版本替换为启动新协程的版本: MainViewModel.kt

/**
* Refresh the title, showing a loading spinner while it refreshes and errors via snackbar.
*/
fun refreshTitle() {
    viewModelScope.launch {
        try {
            _spinner.value = true
            repository.refreshTitle()
        } catch (error: TitleRefreshErrpr) {
            _snackBar.value = error.message
        } finally {
            _spinner.value = false
        }
    }
}

我们来了解一下这个函数:

viewModelScope.launch {

就像更新点按技术的协程一样,首先在viewModelScope中启动一个新协程。这将使用Dispatchers.Main,也就是OK。尽管refreshTitle会发出网络请求和数据库查询,但它可以使用协程公开主线程安全接口。这意味着,您可以安全地从主线程调用它。

由于我们使用了viewModelScope,因此,当用户离开此屏幕时,此协程启动的操作将自动取消。这意味着它不会发出其他网络请求或数据库查询。

从非协程创建协程时,应通过launch启动。

这样一来,如果它们抛出未捕获异常,这会自动传播到未捕获异常处理程序(默认会导致应用崩溃)。在宁调用await之前,通过async启动的协程不会向其调用方抛出异常。不过,您只能从协程内调用await,因为它是挂起函数。

进入协程后,您便可以使用launch或async启动子协程。没有要返回的结果时使用launch,反之使用async。

接下来的几行代码实际上会调用repository中的refreshTitle

try {
    _spinner.value = true
    repository.refreshTitle()
}

在此协程执行任何操作之前,它会启动加载旋转图标,然后就像常规函数一样调用refreshTitle。不过,由于refreshTitle是挂起函数,因此其执行方式与常规函数不同。

我们不必传递回调。协程将挂起,直到refreshTitle恢复它为止。协程看起来就像常规的阻塞函数调用一样,但它会自动等待网络和数据库查询完成后,然后才会恢复,不会阻塞主线程。

}  catch (error: TitleRefreshError) {
    _snackBar.value = error.message
} finally {
    _spinner.value = false
}

挂起函数中的异常的作用类似于常规函数中的错误。如果您在挂起函数中抛出错误,则错误会抛给调用方。因此,尽管它们的执行方式截然不同,您可以使用常规try/catch块来处理它们。这非常有用,因为这让您可以依靠针对错误处理的内置语言支持,而不是为每个回调构建自定义错误处理。

此外,如果您从协程丢出异常,则此协程将默认取消父级。也就是说,同时取消多项相关人物非常容易。

然后,在一个finally块中,我们可以确保旋转图标始终在查询运行后关闭。

未捕获异常会发生什么情况

协程中的未捕获异常与非协程代码中的未捕获异常类似。默认情况下,这些异常会取消协程的Job,并通知父级协程自行取消。如果没有协程处理异常,异常最终会传递给CoroutineScope上的未捕获异常处理程序。

默认情况下,未捕获异常会发送到JVM上线程的未捕获异常处理程序。您可以通过CoroutineExceptionHandler来自定义此行为。

通过依次选择 start 配置并点按 execute.png 再次运行应用,您应该会在点按任意位置时看到加载旋转图标。由于我们尚未连接网络或数据库,标题不会发生变化。

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题

图片

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值