此 Codelab 是“Android Kotlin 基础知识”课程的一部分。如果您按顺序学习这些 Codelab,您将会充分发掘此课程的价值。“Android Kotlin 基础知识”Codelab 着陆页列出了所有课程 Codelab。
简介
打造完美应用用户体验的首要任务之一是确保界面总是能够迅速响应并顺畅运行。提高界面性能的一种方法是将数据库操作等长时间运行的任务移至后台。
在此 Codelab 中,您将实现 TrackMySleepQuality 应用中面向用户的部分,使用 Kotlin 协程在主线程之外执行数据库操作。
您应当已掌握的内容
您应熟悉以下内容:
- 使用 activity、fragment、视图和点击处理程序构建基本界面。
- 在 fragment 之间导航,并使用
safeArgs
在 fragment 之间传递简单的数据。 - 视图模型、视图模型工厂、转换和
LiveData
。 - 如何创建
Room
数据库、创建 DAO 以及定义实体。 - 如果您熟悉线程和多进程概念,这将很有帮助。
学习内容
- 线程在 Android 中的工作原理。
- 如何使用 Kotlin 协程将数据库操作移出主线程。
- 如何在
TextView
中显示设置了格式的数据。
您将执行的操作
- 扩展 TrackMySleepQuality 应用以收集、存储和显示数据库中的数据。
- 使用协程在后台运行长时间运行的数据库操作。
- 使用
LiveData
触发导航和信息提示控件的显示。 - 使用
LiveData
启用和停用按钮。
在此 Codelab 中,您将构建 TrackMySleepQuality 应用的视图模型、协程和数据显示部分。
该应用有两个屏幕,以 fragment 表示,如下图所示。
左侧所示的第一个屏幕包含用于开始和停止跟踪的按钮。这个屏幕会显示用户的所有睡眠数据。CLEAR 按钮用于永久删除应用针对用户收集的所有数据。
右侧所示的第二个屏幕用于选择睡眠质量评分。在该应用中,评分用数字表示。出于开发目的,该应用同时显示人脸图标及其对应的数字。
用户的流程如下所示:
- 用户打开该应用,并看到睡眠跟踪界面。
- 用户点按 START 按钮。系统会记录开始时间并显示该时间。START 按钮会停用,而 STOP 按钮会启用。
- 用户点按 STOP 按钮。系统会记录结束时间,并打开睡眠质量界面。
- 用户选择一个睡眠质量图标。这个屏幕会关闭,跟踪屏幕会显示睡眠结束时间和睡眠质量。STOP 按钮会停用,而 START 按钮会启用。该应用已为下一晚运行做好准备。
- 只要数据库中有数据,CLEAR 按钮就会处于启用状态。如果用户点按 CLEAR 按钮,系统会清空其所有数据,并且不予追偿,也就是说,系统不会显示“您确定吗?”这类消息。
该应用在完整架构的基础上采用简化的架构,如下所示。该应用仅使用以下组件:
- 界面控制器
- 视图模型和
LiveData
- Room 数据库
在此任务中,您将使用 TextView
显示设置了格式的睡眠跟踪数据。(这不是最终界面。您将在另一个 Codelab 中学习更好的方法。)
您可以继续使用在上一个 Codelab 中构建的 TrackMySleepQuality 应用,也可以下载此 Codelab 的起始应用。
第 1 步:下载并运行起始应用
- 从 GitHub 下载 TrackMySleepQuality-Coroutines-Starter 应用。
- 构建并运行应用。该应用会显示
SleepTrackerFragment
fragment 的界面,但不会显示数据。按钮不会响应点按操作。
第 2 步:检查代码
此 Codelab 的起始代码与“6.1 创建 Room 数据库”Codelab 的解决方案代码相同。
- 打开 res/layout/activity_main.xml。此布局包含
nav_host_fragment
fragment。此外,请注意<merge>
标记。
当包含布局时,merge
标记可用于消除冗余布局,使用该标记是一种不错的做法。例如,ConstraintLayout > LinearLayout > TextView 就是冗余布局,其中系统或许能够消除 LinearLayout。这种优化可以简化视图层次结构并提高应用性能。 - 在 navigation 文件夹中,打开 navigation.xml。您可以看到两个 fragment 以及连接它们的导航操作。
- 在 layout 文件夹中,双击睡眠跟踪器 fragment 以查看其 XML 布局。请注意以下几点:
- 布局数据封装在
<layout>
元素中以启用数据绑定。 ConstraintLayout
及其他视图被安排在<layout>
元素内。- 该文件具有占位符
<data>
标记。
起始应用还提供了界面的尺寸、颜色和样式设置。该应用包含 Room
数据库、DAO 和 SleepNight
实体。如果您未完成前面的 Codelab,请务必自行了解代码的这些方面。
现在您已经有了数据库和界面,接下来需要收集数据、将数据添加到数据库,并显示数据。所有这些工作都在视图模型中完成。您的睡眠跟踪器视图模型将处理按钮点击、通过 DAO 与数据库进行交互,并通过 LiveData
向界面提供数据。所有数据库操作都必须在主界面线程之外运行,您将使用协程做到这一点。
第 1 步:添加 SleepTrackerViewModel
- 在 sleeptracker 软件包中,打开 SleepTrackerViewModel.kt。
- 检查
SleepTrackerViewModel
类,我们在起始应用中为您提供了该类,下面也显示了该类。请注意,该类扩展了AndroidViewModel()
。该类与ViewModel
相同,但它将应用上下文作为参数,并使其可用作属性。您稍后会需要它。
class SleepTrackerViewModel(
val database: SleepDatabaseDao,
application: Application) : AndroidViewModel(application) {
}
第 2 步:添加 SleepTrackerViewModelFactory
- 在 sleeptracker 软件包中,打开 SleepTrackerViewModelFactory.kt。
- 检查针对工厂为您提供的代码,如下所示:
class SleepTrackerViewModelFactory(
private val dataSource: SleepDatabaseDao,
private val application: Application) : ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(SleepTrackerViewModel::class.java)) {
return SleepTrackerViewModel(dataSource, application) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
请注意以下几点:
- 提供的
SleepTrackerViewModelFactory
采用的参数与ViewModel
相同,并扩展了ViewModelProvider.Factory
。 - 在工厂内,代码替换了
create()
,该方法会将任何类类型作为参数并返回ViewModel
。 - 在
create()
的主体中,代码会检查是否有可用的SleepTrackerViewModel
类,如果有,会返回它的实例。否则,代码会抛出异常。
第 3 步:更新 SleepTrackerFragment
- 在
SleepTrackerFragment
中,获取对应用上下文的引用。将该引用放在onCreateView()
中的binding
下面。您需要引用此 fragment 连接到的应用,以传入视图模型工厂提供程序。
如果值为null
,requireNotNull
Kotlin 函数会抛出IllegalArgumentException
。
val application = requireNotNull(this.activity).application
- 您需要通过引用 DAO 来引用数据源。在
onCreateView()
中的return
前面,定义dataSource
。如需获取对数据库的 DAO 的引用,请使用SleepDatabase.getInstance(application).sleepDatabaseDao
。
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
- 在
onCreateView()
中的return
前面,创建viewModelFactory
的实例。您需要向其传递dataSource
和application
。
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
- 现在您已经有了工厂,接下来获取对
SleepTrackerViewModel
的引用。SleepTrackerViewModel::class.java
参数引用此对象的运行时 Java 类。
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
- 完成后的代码应如下所示:
// Create an instance of the ViewModel Factory.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
// Get a reference to the ViewModel associated with this fragment.
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
下面是到目前为止的 onCreateView()
方法:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Get a reference to the binding object and inflate the fragment views.
val binding: FragmentSleepTrackerBinding = DataBindingUtil.inflate(
inflater, R.layout.fragment_sleep_tracker, container, false)
val application = requireNotNull(this.activity).application
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
return binding.root
}
第 4 步:为视图模型添加数据绑定
在基本的 ViewModel
就绪之后,您需要在 SleepTrackerFragment
中完成数据绑定的设置,以将 ViewModel
与界面连接起来。
在 fragment_sleep_tracker.xml
布局文件中:
- 在
<data>
代码块内,创建一个引用SleepTrackerViewModel
类的<variable>
。
<data>
<variable
name="sleepTrackerViewModel"
type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>
在 SleepTrackerFragment
中:
- 将当前的 activity 设置为绑定的生命周期所有者。在
onCreateView()
方法内添加以下代码,在return
语句前面:
binding.setLifecycleOwner(this)
- 将
sleepTrackerViewModel
绑定变量分配给sleepTrackerViewModel
。将以下代码放在onCreateView()
内,在用于创建SleepTrackerViewModel
的代码下面:
binding.sleepTrackerViewModel = sleepTrackerViewModel
- 您可能会看到一个错误,因为您必须重新创建绑定对象。请清除并重建项目以消除该错误。
- 最后,像往常一样,确保代码的构建和运行没有错误。
在 Kotlin 中,使用协程来顺畅且高效地处理长时间运行的任务。Kotlin 协程使您能够将基于回调的代码转换为顺序代码。顺序编写的代码通常更易于阅读,甚至可以使用异常等语言功能。最后,协程和回调执行的是相同的操作:等待长时间运行的任务获得结果,然后继续执行任务。
协程具有以下属性:
- 协程是异步且非阻塞的。
- 协程使用挂起函数来让异步代码依序调用。
协程是异步的。
协程独立于程序的主执行步骤运行。这可以是并行运行,也可以是在单独的处理器上运行。还有可能是当应用的其余部分正在等待输入时,您暗中执行少量的处理。异步编程的一个重要方面是,您不能期望结果立即可用,除非您明确等待结果。
例如,假设您有一个需要研究的问题,您请一位同事帮您找到答案。他们会离开并开始处理,这就像他们“异步”且“在单独的线程上”完成工作一样。您可以继续做其他不依赖于该答案的工作,直到您的同事回来并告诉您答案。
协程是非阻塞的。
非阻塞 意味着协程不会阻塞主线程或界面线程。因此使用协程,用户可以获得尽可能流畅的体验,因为界面交互总是得到优先处理。
协程使用挂起函数来让异步代码依序调用。
关键字 suspend
是 Kotlin 将函数(或函数类型)标记为可供协程使用的一种方式。当协程调用标有 suspend
的函数时,它不会像常规函数调用一样在函数返回结果之前进行阻塞,而是挂起执行,直到结果就绪为止。然后,协程会从上次停止的位置恢复并使用返回的结果。
当协程挂起并等待结果时,它会取消阻塞正在运行它的线程。这样,其他函数或协程就可以运行了。
suspend
关键字未指定运行代码的线程。挂起函数可以在后台线程上运行,也可以在主线程上运行。
如需在 Kotlin 中使用协程,您需要以下三项:
- 作业
- 调度程序
- 作用域
作业:基本上,作业是可以取消的任何内容。每个协程都有一项作业,您可以使用该作业来取消协程。您可以将作业安排到父子层次结构中。如果取消一项父作业,会立即取消该作业的所有子级,这样比手动取消每个协程要方便得多。
调度程序: 调度程序发出协程以在各种线程上运行。例如,Dispatcher.Main
在主线程上运行任务,而 Dispatcher.IO
将阻塞的 I/O 任务分流到共享线程池。
作用域: 协程的作用域定义协程在什么上下文中运行。作用域将有关协程的作业和调度程序的信息组合在一起。作用域会跟踪协程。当您启动一个协程时,它就会“在一个作用域中”,这意味着,您已指明哪个作用域将跟踪该协程。
您希望用户能够通过以下方式与睡眠数据进行交互:
- 当用户点按 START 按钮时,应用会创建一个新的睡眠之夜,并将该睡眠之夜存储在数据库中。
- 当用户点按 STOP 按钮时,应用会使用结束时间更新睡眠之夜。
- 当用户点按 CLEAR 按钮时,应用会清空数据库中的数据。
这些数据库操作可能需要很长时间才能完成,因此应在单独的线程上运行。
第 1 步:为数据库操作设置协程
当用户点按睡眠跟踪器应用中的 START 按钮时,您希望调用 SleepTrackerViewModel
中的函数来创建一个新的 SleepNight
实例,并将该实例存储在数据库中。
点按任意按钮都会触发数据库操作,如创建或更新 SleepNight
。出于此原因和其他原因,您使用协程来为应用的按钮实现点击处理程序。
- 打开应用级
build.gradle
文件,然后找到协程的依赖项。如需使用协程,您需要这些已为您添加的依赖项。$coroutine_version
在项目build.gradle
文件中定义为coroutine_version =
'1.0.0'
。
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
- 打开
SleepTrackerViewModel
文件。 - 在类的正文中,定义
viewModelJob
并为其分配Job
的实例。借助此viewModelJob
,您可以在不再使用并销毁视图模型时取消该视图模型启动的所有协程。这样,您就不会遇到协程无处可返回的情况。
private var viewModelJob = Job()
- 在类正文的末尾,替换
onCleared()
并取消所有协程。当ViewModel
被销毁时,系统会调用onCleared()
。
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
- 在
viewModelJob
的定义下方,为协程定义一个uiScope
。作用域决定了协程将在哪个线程上运行,并且作用域还需要了解作业。如需获取范围,请请求CoroutineScope
的实例,并传入调度程序和作业。
使用 Dispatchers.Main
意味着在 uiScope
中启动的协程将在主线程上运行。对于由 ViewModel
启动的许多协程来说,这是合理的,因为这些协程在执行一些处理后,会导致界面更新。
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
- 在
uiScope
的定义下方,定义一个名为tonight
的变量,用于保存当晚的数据。将该变量设为MutableLiveData
,因为您需要能够观察数据并更改数据。
private var tonight = MutableLiveData<SleepNight?>()
- 如需尽快初始化
tonight
变量,请在tonight
的定义下面创建一个init
代码块,并调用initializeTonight()
。您将在下一步中定义initializeTonight()
。
init {
initializeTonight()
}
- 在
init
代码块下面,实现initializeTonight()
。在uiScope
中,启动一个协程。在其中,通过调用getTonightFromDatabase()
从数据库中获取tonight
的值,并将该值分配给tonight.value
。您将在下一步中定义getTonightFromDatabase()
。
private fun initializeTonight() {
uiScope.launch {
tonight.value = getTonightFromDatabase()
}
}
- 实现
getTonightFromDatabase()
。将其定义为一个private suspend
函数,如果当前没有启动的SleepNight
,该函数将会返回可为 null 的SleepNight
。这样会产生错误,因为该函数必须返回一些内容。
private suspend fun getTonightFromDatabase(): SleepNight? { }
- 在
getTonightFromDatabase()
的函数正文内,返回在Dispatchers.IO
上下文中运行的协程的结果。使用 I/O 调度程序,因为从数据库获取数据属于 I/O 操作,与界面无关。
return withContext(Dispatchers.IO) {}
- 在返回代码块内,让协程从数据库中获取“今晚”(最新的睡眠之夜)。如果开始时间和结束时间不同,表示该睡眠之夜已完成,返回
null
。否则,返回该睡眠之夜。
var night = database.getTonight()
if (night?.endTimeMilli != night?.startTimeMilli) {
night = null
}
night
完成后的 getTonightFromDatabase()
suspend 函数应如下所示。应该不会再有错误了。
private suspend fun getTonightFromDatabase(): SleepNight? {
return withContext(Dispatchers.IO) {
var night = database.getTonight()
if (night?.endTimeMilli != night?.startTimeMilli) {
night = null
}
night
}
}
第 2 步:添加“开始”按钮的点击处理程序
现在,您可以实现 onStartTracking()
,它是 START 按钮的点击处理程序。您需要创建一个新的 SleepNight
,将其插入数据库,并将其分配给 tonight
。onStartTracking()
的结构将与 initializeTonight()
非常相似。
- 从
onStartTracking()
的函数定义开始。您可以将点击处理程序放在SleepTrackerViewModel
文件中的onCleared()
上面。
fun onStartTracking() {}
- 在
onStartTracking()
内,在uiScope
中启动一个协程,因为您需要此结果才能继续操作并更新界面。
uiScope.launch {}
- 在用于启动协程的代码内,创建一个新的
SleepNight
,它将捕获当前时间作为开始时间。
val newNight = SleepNight()
- 仍然在用于启动协程的代码内,调用
insert()
以将newNight
插入数据库。您将会看到一个错误,因为您还没有定义此insert()
挂起函数。(这与同名的 DAO 函数不同。)
insert(newNight)
- 还是在用于启动协程的代码内,更新
tonight
。
tonight.value = getTonightFromDatabase()
- 在
onStartTracking()
下面,将insert()
定义为一个private suspend
函数,该函数将SleepNight
作为其参数。
private suspend fun insert(night: SleepNight) {}
- 对于
insert()
的正文,请在 I/O 上下文中启动协程,并通过从 DAO 调用insert()
将睡眠之夜插入数据库。
withContext(Dispatchers.IO) {
database.insert(night)
}
- 在
fragment_sleep_tracker.xml
布局文件中,使用您之前设置的数据绑定的强大功能将onStartTracking()
的点击处理程序添加到start_button
。@{() ->
函数符号会创建一个 lambda 函数,该函数不采用任何参数,并在sleepTrackerViewModel
中调用点击处理程序。
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
- 构建并运行您的应用。点按 START 按钮。此操作会创建数据,但您还看不到任何内容。您接下来就会解决该问题。
fun someWorkNeedsToBeDone { uiScope.launch { suspendFunction() } } suspend fun suspendFunction() { withContext(Dispatchers.IO) { longrunningWork() } }
第 3 步:显示数据
在 SleepTrackerViewModel
中,nights
变量引用 LiveData
,因为 DAO 中的 getAllNights()
会返回 LiveData
。
每当数据库中的数据发生变化时,LiveData
nights
都会更新以显示最新数据,这是一项 Room
功能。您从不需要明确设置 LiveData
或对其进行更新。Room
会更新数据以与数据库匹配。
不过,如果在文本视图中显示 nights
,将显示对象引用。如需查看对象的内容,请将数据转换为设置了格式的字符串。使用每当 nights
从数据库接收新数据时都会执行的 Transformation
映射。
- 打开
Util.kt
文件,然后取消备注formatNights()
的定义和关联的import
语句的代码。如需在 Android Studio 中取消备注代码,请选择标有//
的所有代码,然后按Cmd+/
或Control+/
。 - 请注意,
formatNights()
返回一个类型Spanned
,它是 HTML 格式的字符串。 - 打开 strings.xml。请注意,使用了
CDATA
来设置字符串资源的格式以显示睡眠数据。 - 打开 SleepTrackerViewModel。在
SleepTrackerViewModel
类中,在uiScope
的定义下方,定义一个名为nights
的变量。从数据库中获取所有睡眠之夜,并将其分配给nights
变量。
private val nights = database.getAllNights()
- 就在
nights
的定义下面,添加相应的代码以将nights
转换为nightsString
。使用Util.kt
中的formatNights()
函数。
将nights
传入Transformations
类中的map()
函数。如需访问字符串资源,请将映射函数定义为发起调用的formatNights()
。提供nights
和Resources
对象。
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}
- 打开
fragment_sleep_tracker.xml
布局文件。在TextView
内的android:text
属性中,您现在可以将资源字符串替换为对nightsString
的引用。
"@{sleepTrackerViewModel.nightsString}"
- 重建代码并运行应用。现在应显示您的所有睡眠数据与开始时间。
- 再点按几次 START 按钮,您会看到更多数据。
在下一步中,您将启用 STOP 按钮的功能。
第 4 步:添加“STOP”按钮的点击处理程序
使用与上一步中相同的模式,在 SleepTrackerViewModel.
中实现 STOP 按钮的点击处理程序。
- 将
onStopTracking()
添加到ViewModel
。在uiScope
中启动一个协程。如果尚未设置结束时间,请将endTimeMilli
设置为当前系统时间,并使用睡眠之夜数据调用update()
。
在 Kotlin 中,return@
label
语法指定此语句从几个嵌套函数中的哪个函数返回结果。
fun onStopTracking() {
uiScope.launch {
val oldNight = tonight.value ?: return@launch
oldNight.endTimeMilli = System.currentTimeMillis()
update(oldNight)
}
}
- 实现
update()
,使用的模式与用于实现insert()
的模式相同。
private suspend fun update(night: SleepNight) {
withContext(Dispatchers.IO) {
database.update(night)
}
}
- 如需将点击处理程序连接到界面,请打开
fragment_sleep_tracker.xml
布局文件,然后向stop_button
添加点击处理程序。
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
- 构建并运行您的应用。
- 点按 START,然后点按 STOP。您会看到开始时间、结束时间、没有值的睡眠质量和睡眠时长。
第 5 步:添加“CLEAR”按钮的点击处理程序
- 同样,实现
onClear()
和clear()
。
fun onClear() {
uiScope.launch {
clear()
tonight.value = null
}
}
suspend fun clear() {
withContext(Dispatchers.IO) {
database.clear()
}
}
- 如需将点击处理程序连接到界面,请打开
fragment_sleep_tracker.xml
,然后向clear_button
添加点击处理程序。
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
- 构建并运行您的应用。
- 点按 CLEAR 以清除所有数据。然后,点按 START 和 STOP 以生成新数据。
Android Studio 项目:TrackMySleepQualityCoroutines
- 使用
ViewModel
、ViewModelFactory
和数据绑定来设置应用的界面架构。 - 为使界面顺畅运行,使用协程来执行长时间运行的任务,如所有数据库操作。
- 协程是异步且非阻塞的。它们使用
suspend
函数来让异步代码依序调用。 - 当协程调用标有
suspend
的函数时,它不会像常规函数调用一样在该函数返回结果之前进行阻塞,而是挂起执行,直到结果就绪为止。然后,它会从上次停止的位置恢复并使用返回的结果。 - 阻塞与挂起的区别在于,如果线程被阻塞,不会进行其他工作。如果线程被挂起,会进行其他工作,直到结果可用为止。
如需启动协程,您需要作业、调度程序和作用域:
- 基本上,作业是可以取消的任何内容。每个协程都有一项作业,您可以使用作业来取消协程。
- 调度程序发出协程以在各种线程上运行。
Dispatcher.Main
在主线程上运行任务,而Dispartcher.IO
用于将阻塞的 I/O 任务分流到共享线程池。 - 作用域会组合信息(包括作业和调度程序),以定义协程的运行上下文。作用域会跟踪协程。
如需实现触发数据库操作的点击处理程序,请遵循以下模式:
- 启动一个在主线程或界面线程上运行的协程,因为结果会影响界面。
- 调用一个挂起函数来完成长时间运行的工作,这样在等待结果时就不会阻塞界面线程。
- 长时间运行的工作与界面无关,因此切换到 I/O 上下文。这样,工作就可以在针对这些类型的操作进行了优化并为其预留的线程池中运行。
- 然后,调用数据库函数来完成工作。
每当 LiveData
对象发生变化时,都使用 Transformations
映射从该对象创建一个字符串。
Udacity 课程:
Android 开发者文档:
其他文档和文章:
- 工厂模式
- 协程 Codelab
- 协程和官方文档
- 协程上下文和调度程序
Dispatchers
- 超过 Android 速度限制
Job
launch
- Kotlin 中的返回和跳转
- CDATA 代表字符数据。CDATA 表示这些字符串之间的数据包括可以(但不应)被解释为 XML 标记的数据。
此部分列出了在由讲师主导的课程中,学生学习此 Codelab 后可能需要完成的家庭作业。讲师自行决定是否执行以下操作:
- 根据需要布置作业。
- 告知学生如何提交家庭作业。
- 给家庭作业评分。
讲师可以酌情采纳这些建议,并且可以自由布置自己认为合适的任何其他家庭作业。
如果您是在自学此 Codelab,可随时通过这些家庭作业来检测您的知识掌握情况。
回答以下问题
问题 1
以下哪些是协程的优势:
- 它们是非阻塞的
- 它们异步运行。
- 它们可以在除主线程以外的线程上运行。
- 它们总是能够让应用更快地运行。
- 它们可以使用异常。
- 它们都可以作为线性代码进行写入和读取。
问题 2
什么是挂起函数?
- 使用
suspend
关键字进行注解的普通函数。 - 可以在协程内调用的函数。
- 当挂起函数正在运行时,发起调用的线程会挂起。
- 挂起函数必须始终在后台运行。
问题 3
阻塞线程和挂起线程之间有什么区别?请标记所有正确选项。
- 执行阻塞后,无法在阻塞的线程上执行任何其他工作。
- 执行挂起后,线程可以在等待分流的工作完成的同时执行其他工作。
- 挂起更高效,因为线程可能不会等待,什么也不做。
- 无论执行是阻塞还是挂起,都会等待返回协程结果,然后再继续。
开始学习下一课:
如需本课程中其他 Codelab 的链接,请参阅“Android Kotlin 基础知识”Codelab 着陆页。