在 Android 应用中使用 Kotlin 协程 - 官方示例详解

一、简介

Kotlin 协程是管理后台线程的推荐方法,可通过减少回调需求来简化代码。
简介参考:https://www.jianshu.com/p/130f3888a433

1. 本示例目标:

熟悉在应用中使用 协程 来 从网络加载数据,能够将协程集成到应用中.
熟悉有关协程的 最佳做法,以及如何针对使用协程的代码 编写测试。
(官方示例: https://developer.android.com/codelabs/kotlin-coroutines#0)

2. 预备知识:

(1) 熟悉 ViewModel、LiveData、Repository 和 Room 架构组件。
(2) 具有使用 Kotlin 语法(包括扩展函数和 lambda)的经验。
(3) 对于在 Android 上使用线程(包括主线程、后台线程和回调)有基本的了解。

3. 示例中应执行的操作

(1) 调用 使用协程编写的代码并获取结果。
(2) 使用 挂起函数 让异步代码 依序调用
(3) 使用 launch 和 runBlocking 控制代码的执行方式。
(4) 了解使用 suspendCoroutine 将现有 API 转换为协程的技巧。
(5) 将协程与架构组件一起使用。
(6) 了解测试协程的最佳做法。

二、示例详细过程

1. 下载代码:

GitHub 代码:https://github.com/googlecodelabs/kotlin-coroutines/tree/master/coroutines-codelab

kotlin-coroutines 代码库包含两个应用模块:
(1) start: 一个使用 Android 架构组件的简单应用,将向此应用添加协程
(2) finished_code : 已添加协程的项目

2. 运行初始示例应用

打开 coroutines-codelab 项目 -> 选择 start 应用模块 -> 点击 Run 运行.

2.1 基础功能

在点按屏幕后,此初始应用会使用线程在经过短暂延迟后增加计数。
它还会从网络中提取新标题并将其显示在屏幕上(模拟读取数据).

目标是将将此应用转换为使用协程.

2.2 项目结构

此应用使用架构组件将 MainActivity 中的界面代码与 MainViewModel 的应用逻辑分隔开.
<项目架构图>

(1)MainActivity 显示界面、注册点击监听器,并且可以显示 Snackbar。
它将事件传递给 MainViewModel,并根据 MainViewModel 中的 LiveData 更新屏幕。
(2)MainViewModel 处理 onMainViewClicked 中的事件,并将使用 LiveData与 MainActivity 通信
(3)Executors 定义 BACKGROUND,,后者可以在后台线程上运行内容。
(4)TitleRepository 从网络提取结果,并将结果保存到数据库。

2.3 向项目添加协程

在 app/build.gradle 添加 协程依赖项:

dependencies {
  ...
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:x.x.x"
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:x.x.x"
}

其中核心库 和 Android拓展库:
kotlinx-coroutines-core - 用于在 Kotlin 中使用协程的主接口
kotlinx-coroutines-android - 在协程中支持 Android 主线程

同时在 Kotlin 协程版本页面上找到协程库的最新版版本号,以替代“xxx”
协程版本:https://github.com/Kotlin/kotlinx.coroutines/releases

2.4 使用协程控制界面

2.4.1 使用 viewModelScope

AndroidX lifecycle-viewmodel-ktx 库将 CoroutineScope,
添加到已配置为启动界面相关协程的 ViewModel 中.

此库将 viewModelScope 添加为 ViewModel 类的扩展函数。
此作用域绑定到 Dispatchers.Main,并会在清除 ViewModel 后自动取消。

2.4.2 从线程切换到协程

在 MainViewModel.kt 中

/**
* Wait one second then update the tap count.
*/
private fun updateTaps() {
   // TODO: Convert updateTaps to use coroutines
   tapCount++
   BACKGROUND.submit {
       Thread.sleep(1_000)
       _taps.postValue("$tapCount taps")
   }
}

此代码使用 BACKGROUND ExecutorService(在 util/Executor.kt 中定义)在后台线程中运行。
由于 sleep 会阻塞当前线程,因此,如果在主线程上调用它,它会导致界面冻结。
在用户点击主视图的一秒钟后,它会请求信息提示控件。

从代码中移除 BACKGROUND 并重新运行代码,就能看到这种情况。
加载旋转图标将不会显示(即使refreshTitle里设置了_spinner=true),并且所有内容都将在一秒钟后“跳到”最终状态。

使用协程 做修改

/**
* Wait one second then display a snackbar.
*/
fun updateTaps() {
   // launch a coroutine in viewModelScope
   viewModelScope.launch {
       tapCount++
       // suspend this coroutine for one second
       delay(1_000)
       // resume in the main dispatcher
       // _snackbar.value can be called directly from main thread
       _taps.postValue("$tapCount taps")
   }
}

此代码执行的操作相同,即等待 1 秒钟后显示信息提示控件(**会显示 加载旋转图标 **)。
不过,它们存在一些重要区别:

(1)viewModelScope.launch 将在 viewModelScope 中启动协程。
这意味着,当我们传递给 viewModelScope 的作业取消时,此作业/作用域内的所有协程都将取消。
如果用户在 delay 返回之前离开了 Activity,
那么在 ViewModel 销毁后系统调用 onCleared 时,此协程将自动取消。
(2)由于 viewModelScope 的默认调度程序为 Dispatchers.Main,因此此协程将在主线程中启动。
稍后,我们将了解如何使用不同的线程。
(3)delay 函数属于 suspend 函数。在 Android Studio 的左侧边线中。
虽然此协程在主线程上运行,但 delay 不会阻塞此线程 1 秒钟。
相反,调度程序将安排协程在一秒钟内在下一个语句中恢复。

注意:原本是的 Thread.sleep(1_000)
现在改成 delay(1_000) 则不会阻塞主线程。(kotlinx.coroutines.delay)
!!!或者指定在IO 线程则会显示 加载旋转图标 : viewModelScope.launch(Dispatchers.IO) {...}

3. 通过行为测试协程

为以上编写的代码编写测试用例.
即如何使用 kotlinx-coroutines-test 库测试在 Dispatchers.Main 上运行的协程。

打开在test 目录下的 MainViewModelTest.kt

已有如下代码:

class MainViewModelTest {
   @get:Rule
   val coroutineScope =  MainCoroutineScopeRule()
   @get:Rule
   val instantTaskExecutorRule = InstantTaskExecutorRule()

   lateinit var subject: MainViewModel

   @Before
   fun setup() {
       subject = MainViewModel(
           TitleRepository(
                   MainNetworkFake("OK"),
                   TitleDaoFake("initial")
           ))
   }
}

其中,
(1) InstantTaskExecutorRule 是一种 JUnit 规则,用于配置 LiveData 以同步执行每项任务
(2) MainCoroutineScopeRule 是此代码库中的自定义规则,用于
将 Dispatchers.Main 配置为使用 kotlinx-coroutines-test 中的 TestCoroutineDispatcher。
这样一来,测试可以将用于测试的虚拟时钟拨快,并让代码可以使用单元测试中的 Dispatchers.Main。
在 setup 方法中,系统使用测试虚构对象创建一个新的 MainViewModel 实例。

以下是 用于控制协程的测试。
确保系统在用户点按主视图的一秒钟后更新点按计数

@Test
fun whenMainClicked_updatesTaps() {
   subject.onMainViewClicked()
   Truth.assertThat(subject.taps.getValueForTest()).isEqualTo("0 taps")
   coroutineScope.advanceTimeBy(1000)
   Truth.assertThat(subject.taps.getValueForTest()).isEqualTo("1 taps")
}

其中,
调用 advanceTimeBy(1_000),这会导致主调度程序立即执行预定在 1 秒钟后恢复的协程。‘

点击 Run ‘MainViewModelTest' , 则会提示 Test pass.

附录: 参考文献

你可能感兴趣的:(在 Android 应用中使用 Kotlin 协程 - 官方示例详解)