Kotlin之协程(第五趴)——从阻塞代码创建主线程安全函数&Room和Retrofit中的协程

1、从阻塞代码创建主线程安全函数

通过这一章节,您将学习如何切换运行协程,以实现TitleRepository的工作版本。

1.1、查看refreshTitle中的现有回调代码

打开TitleRepository.kt并查看现有的基于回调的实现。 TitleRepository.kt

kotlin 复制代码
// TitleRepository.kt

fun refreshTitleWithCallbacks(titleRefreshCallback: TitleRefreshCallback) {
    // This request will be run on a background thread by retrofit
    BACKGROUND.submit {
        try {
            // Make network request using a blocking call
            val result = network.fetchNextTitle().execute()
            if (result.isSuccessful) {
                // Save it to database
                titleDao.insertTitle(Title(result.body()!!))
                // Inform the caller the refresh is completed
                titleRefreshCallback.onCompleted()
            } else {
                // If it's not successful, inform the callback of the error
                titleRefreshCallback.onError(TitleRefreshError("Unable to refresh title", null))
            }
        } catch (cause: Throwable) {
            // If anything throws an exception, inform the caller
            titleRefreshCallback.onError(TitleRefreshError("Unable to refresh title", cause))
        }
    }
}

TitleRepository.kt中,refreshTitleWithCallbacks方法通过回调来实现,以便将加载和错误状态传达给调用放。

为了实现刷新,此函数会执行多项操作。

  1. 切换到包含BACKGROUND``ExecutorService的另一个线程。
  2. 使用阻塞execute()方法运行fetchNextTitle网络请求。这将在当前线程中运行网络请求,在本例中为BACKGROUND中的一个线程。
  3. 如果结果成功,则使用insertTitle将其保存到数据库,并调用onCompleted()方法。
  4. 如果结果不成功或者出现异常,则调用onError方法,以告知调用方刷新失败。

这种基于回调的时间是主线程安全 的,因为它不会阻塞主线程。但是,它必须在工作完成后使用回调来通知调用方。此外,它还会再它也已切换的BACKGROUND线程上调用回调。

1.2、从协程调用阻塞调用

在不向网络或数据库引入协程的情况下,我们可以使用协程让次代码具有主线程安全性。这样,我们就可以移除回调,并将结果传回醉蛛调用回调的线程。

如果您需要再协程内执行阻塞或CPU密集型工作,例如排序和过滤大型列表或从磁盘读取数据,则可以使用此模式。

此模式应该用于与您代码中的阻塞API集成或执行CPU密集型工作。最好尽可能使用Room或Retrofit等库中的常规挂起函数。

在任何调度程序之间切换时,协程会使用withContext。调用withContext会切换到仅适用于lambda的另一个调度程序,然后返回到使用该lambda的结果调用它的调度程序。

Kotlin协程默认提供三个调度程序:MainIODefault。IO调用程序针对IO工作进行了优化,例如从网络或磁盘读取内容,而Default调度程序则针对CPU密集型任务进行了优化。

TitleRepository.kt

kotlin 复制代码
suspend fun refreshTitle() {
    // interact with *blocking* network and IO calls from a coroutine
    withContext(Dispatchers.IO) {
    val result = try {
        // Make network request using a blocking call
        network.fetchNextTitle().execute()
    } catch (cause: Throwable) {
        // If the network throw an exception, inform the caller
        throw TitleRefreshError("Unable to refresh title", cause)
    }
    
    if (result.isSuccessful) {
        // Save it to database
        titleDao.insertTitle(Title(result.body()!!))
    } else {
        // If it's not successful, inform the callback of the error
        throw TitleRefreshError("Unable to refresh title", null)
    }
}

此实现为网络和数据库使用阻塞调用,但它仍然比回调版本简单一点。

此代码仍使用阻塞调用。调用execute()insertTitle(...)都会阻塞正在运行此协程的线程。不过,通过使用withContext切换到Dispatchers.IO,我们将阻塞IO调用程序中的某个线程。调用此函数的协程(可能在Dispatchers.Main上运行)会挂起,直到withContextlambda完成为止。

与回调版本相比,有一下两个主要区别:

  1. withContext将其结果返回给调用它的调度程序,在本例中调度程序为Dispatchers.Main。回调版本在BACKGROUND执行程序服务中的线程上调用回调。
  2. 调用方不必将回调传递给此函数。它们可以依赖挂起和恢复来获取结果或错误。

2、Room和Retrofit中的协程

为了继续实现协程集成,我们将利用对稳定版Room和Retrofit中的挂起函数的支持,然后使用挂起函数大幅简化我们刚刚编写的代码。

2.1、Room中的协程

首先,打开MainDatabase.kt并将insertTitle设置为挂起函数: MainDatabase.kt

kotlin 复制代码
// add the suspend modifier to the existing insertTitle

@Insert(onConflict = OneConflictStrategy.REPLACE)
suspend fun insertTItle(title: Title)

执行此操作后,Room会让您的检查具有主线程安全性,并自动在后台线程上执行此查询。不过,这也意味着您只能从协程内调用此查询。

以上就是在Room中使用协程所需执行的全部操作。

2.2、Refrefit中的协程

接下来,我们来看看如何将协程与Refrofit集成。打开MainNetwork.kt并将fetchNextTitle更改为挂起函数。此外,将返回值类型从Call<String>更改为String

挂起函数支持需要Retrofit 2.6.0或更高版本

MainNetWork.kt

kotlin 复制代码
// add suspend modifier to the existing fetchNextTitle
// change return type from Call<String> to String

interface MainNetwork {
    @Get("next_title.json")
    suspend fun fetchNextTitle(): String
}

要将挂起函数与Retrofit一起使用,您必须执行以下两项操作:

  1. 为函数添加挂起修饰符
  2. 从返回值类型中移除Call封装容器。这里我们会返回String,但您可以可返回json支持的复杂类型。如果您仍希望提供对Retrofit的完整Result的访问权限,您可以从挂起函数返回Result<String>而不是String

Retrofit将会自动使挂起函数具有主线程安全性 ,以便您可以直接从Dispatchers.Main调用它们。

sql 复制代码
Room和Retrofit均会让挂起函数具有主线程安全性。

尽管这些挂起函数从网络中提取数据并将数据写入数据库,您可以安全地从Dispatchers.Main调用这些函数。

Room和Retrofit都使用自定义调度程序,而不使用Dispatchers.IO

Room会使用已配置的默认查询和事务Executor运行协程。

Retrofit将在后台创建新的Call对象,并将其调用队列以一步发送请求。

2.3、使用Room和Retrofit

现在,Room和Retrofit支持挂起函数,因此我们可以从代码库中使用它们。打开TitleRepository.kt,并观察使用挂起函数如何大大简化逻辑,甚至与苏泽版本相比也不例外:

TitleRepository.kt

kotlin 复制代码
suspend fun refreshTitle() {
    try {
        // Make network request using a blocking call
        val result = network.fetchNextTitle()
        titleDao.insertTitle(Title(result))
    } catch (cause: Throwable) {
        // If anything throws an exception, inform the caller
        throw TitleRefreshError("Unable to refresh title", cause)
    }
}

哇,这样要短得多。可这是怎么回事?事实证明,依赖挂起和恢复操作会使代码大幅缩短。借助Retrofit,我们可以在此处使用StringUser对象等返回值类型,而不是Call。这样做石泉县的,因为在挂起函数内,Retrofit能够在后台线程上运行网络请求,并在调用完成时恢复协程。

更棒的是,我们去掉了withContext。由于Room和Retrofit都提供主线程安全 挂起函数,因此可以安全地通过Dispatchers.Main安排此异步工作。

bash 复制代码
您需要使用withContext来调用主线程安全挂起函数。

按照惯例,您应确保在应用中编写的suspend函数具有主线程安全性。这样一来,您便可安全地从任意调度程序(甚至是Dispacthers.Main)调用这些函数。

2.4、修正编译器错误

专用协程确实设计更改函数的签名,因为您无法通过常规函数调用挂起函数。如果您在此步骤中添加了suspend修饰符,系统会生成一些编译器错误,从中您会明白在实际项目中奖函数更改为挂起函数时会发生的情况。

检查醒目,并修正将函数更改为挂起函数时所产生的编辑器错误。以下是快速解决各类问题的方法:

2.4.1、TestingFakes.kt

更新测试虚假对象,以支持新的挂起修饰符。

TitleDaoFake

  1. 按Alt + Enter(在Mac上按Option + Enter),将挂起修饰符添加到层次结果中的所有函数。

MainNetworkFake

  1. 按Alt + Enter(在Mac上按Option + Enter),将挂起修饰符添加到层级结构中的所有函数。
  2. fetchNextTitle替换为此函数
kotlin 复制代码
override suspend fun fetchNextTitle() = result

MainNetworkCompletableFake

  1. 按Alt + Enter(在Mac上按Option + Enter),将挂起修饰符添加到层级结构中的所有函数。
  2. fetchNextTitle替换为此函数
kotlin 复制代码
override suspend fun fetchNextTiel() = completable.await()

TitleRepository.kt

  • 删除refreshTitleWithCallbacks函数,因为系统已不再使用它。

2.5、运行应用

再次运行应用,编译完成后,您会发现它使用协程将数据从ViewModel一直加载到Room和Retrofit!

相关推荐
2401_8979078618 分钟前
10天学会flutter DAY2 玩转dart 类
android·flutter
m0_748233641 小时前
【PHP】部署和发布PHP网站到IIS服务器
android·服务器·php
Yeats_Liao2 小时前
Spring 定时任务:@Scheduled 注解四大参数解析
android·java·spring
xidianjiapei0013 小时前
为何应将微服务从Java迁移到Kotlin:经验与见解【来自DZone】
java·微服务·kotlin
雾里看山4 小时前
【MySQL】 库的操作
android·数据库·笔记·mysql
水瓶丫头站住12 小时前
安卓APP如何适配不同的手机分辨率
android·智能手机
xvch13 小时前
Kotlin 2.1.0 入门教程(五)
android·kotlin
xvch16 小时前
Kotlin 2.1.0 入门教程(七)
android·kotlin
望风的懒蜗牛17 小时前
编译Android平台使用的FFmpeg库
android
浩宇软件开发17 小时前
Android开发,待办事项提醒App的设计与实现(个人中心页)
android·android studio·android开发