Kotlin之协程——在高阶函数中使用协程&将协程与WorkManager结合使用

1、在高阶函数中使用协程

您将重构MainViewModel中的refreshTitle,以使用常规的数据加载函数。

refreshTitle的当前实现运行正常,但我们可以创建一个始终显示旋转图标的常规数据加载协程。对于加载数据以影响多个事件且希望确保加载旋转图标始终显示的代码库,这可能非常有用。

查看当前实现,可以发现,除reppository.refreshTitle()之外的每行代码都是显示旋转图标和错误的样板代码。

// MainViewModel.kt
fun refreshTitle() {
    viewModelScope.launch {
        try {
            _spinner.value = true
            // this is the only part that changes between sources
            repository.refreshTitle()
        } catchh (error: TitleRefreshError) {
            _snackBar.value = error.message
        } finally {
            _spinner.value = false
        }
    }
}

重要提示:虽然我们在此代码中仅适用viewModelScope,但通常可以在任何意义的位置添加作用于。如果不再需要它,记得将其取消。

例如,您可以在RecyclerView Adapter中声明一个作用域,以执行DiffUtil操作。

1.1、在高阶函数中使用协程

将以下代码添加到MainViewModel.kt中 MainViewModel.kt

private fun launchDataLoad(block: suspend () -> Unit): Job {
    return viewModelScope.launch {
        try {
            _spinner.value = true
            block()
        } catch (error: TitleRefreshError) {
            _snackBar.value = error.message
        } finally {
            _spinner.value = false
        }
    }
}

现在重构refreshTitle()以使用此高阶函数。 MainViewModel.kt

fun refreshTitle() {
    launchDataLoad {
        repository.refreshTitle()
    }
}

通过抽象化用于显示加载旋转图标和显示错误的逻辑,我们简化了加载数据所需的实际代码。显示旋转图标或显示错误是易于泛化到任何数据加载的内容,而实际数据源和目标则需要每次都指定。

为了构建此抽象,launchDataLoad接受一个属于挂起lambda的参数block。挂起lambda可让您调用挂起函数。Kotlin就是通过这种方式实现我们在此代码中 使用的launchrunBlocking协程构建器。

// suspend lambda

block: suspend () -> Unit

要创建挂起lambda,应从suspend关键字着手。函数的箭头和返回值类型Unit用于完成声明。

您通常不必声明自己的挂起lambda,但它们可能有助于创建这类封装重复逻辑的抽象!

2、将协程与WorkManager结合使用

本部分,您将学习如何从WorkManager使用基于协程的代码。

2.1、什么是WorkManager

Android有多个选项用于处理可延迟的后台工作。本练习介绍如何将WorkManager与协程集成。WorkManager是一个兼容、灵活且简单的库,用于处理可延迟的后台工作。WorkManager是Android中这些用例的推荐解决方案。

WorkManager属于Android Jetpack的一部分,是一种架构组件,用于处理既需要机会性执行,又需要有保证的执行的后台工作。机会性执行意味着WorkManager会尽快执行您的后台工作。有保证的执行意味着WorkManager会负责通过逻辑保障在各种情况下启动您的工作,即使用户离开您的应用也无妨。

因此,WorkManager适合最终必须完成的任务。

以下是一些适合使用WorkManager的任务的典型示例:

  • 上传日志
  • 对图片应用滤镜并保存图片
  • 定期将本地数据与网络同步

2.2、将协程与WorkManager一起使用

WorkManager为不同用例提供其基本ListenableWorker类的不同实现。

最简单的Worker类可让我们通过WorkManager执行一些同步操作。不过,根据目前为止我们将代码库转换为使用协程和挂起函数的经验,我们发现使用WorkManager的最好方法是通过CoroutineWorker类,此类支持将doWork()函数定义为挂起函数。

首先,请打开refreshMainDataWork。它已扩展了CoroutineWorker,您需要实现doWork

suspend doWork函数中,从代码库中调用refreshTitle()并返回响应的结果。

完成TODO后,代码将如下所示。

override suspend fun doWork(): Result {
    val database = getDatabase(applicationContext)
    val repository = TitleRepository(network, database.titleDao)
    
    return try {
        repository.refreshTitle()
        Result.success()
    } catch (error: TitleRefreshError) {
        Result.failure()
    }
}

请注意,CoroutineWorker.doWork()是一个挂起函数。与更简单的Worker类不同,此代码不会在您的WorkManager配置所指定的执行器上运行,而是使用coroutineContext成员的调度程序(默认为Dispatchers.Default

2.3、测试我们的CoroutineWorker

WorkManager提供了几种不同的Worker类测试方法。

WorkManager v2.1引入了一组新的API来支持更简单的ListenableWorker类测试方法,并最终推出了CoroutineWorker。我们将在代码中使用一个名为TestListenableWorkerBuilder的新API。

为了添加我们的新测试,更新androidTest文件夹下的RefreshMainWorkTest文件。

此文件的内容为:

package com.example.android.kotlincoroutines.main

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.work.ListenableWorker.Result
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.work.ListenableWorker.Result
import androidx.work.testing.TestLiestenableWorkerBuilder
import com.example.android.kotlincoroutines.fakes.MainNetworkFake
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Junit4

@RunWith(JUnit4::class)
class RefreshMainDataWorkTest {

@Test
fun testRefreshMainDataWork() {
    val fakeNetwork = MainNetworkFake("OK")
    
    val context = ApplicationProvider.getApplicationContext<Context>()
    val worker = TestListenableWorkerBuilder<RefreshMainDataWork>(context)
        .setWorkerFactory(RefreshMainDataWork.Factory(fakeNetwork))
        .build()
        
    // Start the work synchronously
    val result = worker.startWork().get()
    
    assertThat(result).isEqualTo(Result.success)
}
}

在执行测试之前,我们会先告知WorkManager关于工厂的信息,以便我们注入虚构网络。

测试本身会使用TestListenableWorkerBuilder创建我们的工作器,然后我们可以运行该工作器来调用startWork()方法。

WorkManger只是用来说明如何使用协程简化API设计的一个示例。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容