android kotlin架构,将 Kotlin 协程与架构组件一起使用

Kotlin 协程提供了一个可供您编写异步代码的 API。通过 Kotlin 协程,您可以定义 CoroutineScope,以帮助您管理何时应运行协程。每个异步操作都在特定范围内运行。

架构组件针对应用中的逻辑范围以及与 LiveData 的互操作层为协程提供了一流的支持。本主题介绍了如何有效地结合使用协程与架构组件。

添加 KTX 依赖项

本主题中介绍的内置协程范围包含在每个相应架构组件的 KTX 扩展程序中。请务必在使用这些范围时添加相应的依赖项。

对于 ViewModelScope,请使用 androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0 或更高版本。

对于 LifecycleScope,请使用 androidx.lifecycle:lifecycle-runtime-ktx:2.2.0 或更高版本。

对于 liveData,请使用 androidx.lifecycle:lifecycle-livedata-ktx:2.2.0 或更高版本。

生命周期感知型协程范围

架构组件定义了以下内置范围供您在应用中使用。

ViewModelScope

为应用中的每个 ViewModel 定义了 ViewModelScope。如果 ViewModel 已清除,则在此范围内启动的协程都会自动取消。如果您具有仅在 ViewModel 处于活动状态时才需要完成的工作,此时协程非常有用。例如,如果要为布局计算某些数据,则应将工作范围限定至 ViewModel,以便在 ViewModel 清除后,系统会自动取消工作以避免消耗资源。

您可以通过 ViewModel 的 viewModelScope 属性访问 ViewModel 的 CoroutineScope,如以下示例所示:

class MyViewModel: ViewModel() {

init {

viewModelScope.launch {

// Coroutine that will be canceled when the ViewModel is cleared.

}

}

}

LifecycleScope

为每个 Lifecycle 对象定义了 LifecycleScope。在此范围内启动的协程会在 Lifecycle 被销毁时取消。您可以通过 lifecycle.coroutineScope 或 lifecycleOwner.lifecycleScope 属性访问 Lifecycle 的 CoroutineScope。

以下示例演示了如何使用 lifecycleOwner.lifecycleScope 异步创建预计算文本:

class MyFragment: Fragment() {

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

super.onViewCreated(view, savedInstanceState)

viewLifecycleOwner.lifecycleScope.launch {

val params = TextViewCompat.getTextMetricsParams(textView)

val precomputedText = withContext(Dispatchers.Default) {

PrecomputedTextCompat.create(longTextContent, params)

}

TextViewCompat.setPrecomputedText(textView, precomputedText)

}

}

}

暂停生命周期感知型协程

即使 CoroutineScope 提供了适当的方法来自动取消长时间运行的操作,在某些情况下,您可能需要暂停执行代码块(除非 Lifecycle 处于特定状态)。例如,如需运行 FragmentTransaction,您必须等到 Lifecycle 至少为 STARTED。对于这些情况,Lifecycle 提供了其他方法:lifecycle.whenCreated、lifecycle.whenStarted 和 lifecycle.whenResumed。如果 Lifecycle 未至少处于所需的最低状态,则会暂停在这些块内运行的任何协程。

以下示例包含仅当关联的 Lifecycle 至少处于 STARTED 状态时才会运行的代码块:

class MyFragment: Fragment {

init { // Notice that we can safely launch in the constructor of the Fragment.

lifecycleScope.launch {

whenStarted {

// The block inside will run only when Lifecycle is at least STARTED.

// It will start executing when fragment is started and

// can call other suspend methods.

loadingView.visibility = View.VISIBLE

val canAccess = withContext(Dispatchers.IO) {

checkUserAccess()

}

// When checkUserAccess returns, the next line is automatically

// suspended if the Lifecycle is not *at least* STARTED.

// We could safely run fragment transactions because we know the

// code won't run unless the lifecycle is at least STARTED.

loadingView.visibility = View.GONE

if (canAccess == false) {

findNavController().popBackStack()

} else {

showContent()

}

}

// This line runs only after the whenStarted block above has completed.

}

}

}

如果在协程处于活动状态时通过某种 when 方法销毁了 Lifecycle,协程会自动取消。在以下示例中,一旦 Lifecycle 状态变为 DESTROYED,finally 块即会运行:

class MyFragment: Fragment {

init {

lifecycleScope.launchWhenStarted {

try {

// Call some suspend functions.

} finally {

// This line might execute after Lifecycle is DESTROYED.

if (lifecycle.state >= STARTED) {

// Here, since we've checked, it is safe to run any

// Fragment transactions.

}

}

}

}

}注意:尽管这些方法为使用 Lifecycle 提供了便利,但只有当信息在 Lifecycle 的范围(例如预计算文本)内有效时才应使用它们。请记住,协程不会随着 Activity 重启而重启。

将协程与 LiveData 一起使用

使用 LiveData 时,您可能需要异步计算值。例如,您可能需要检索用户的偏好设置并将其传送给界面。在这些情况下,您可以使用 liveData 构建器函数调用 suspend 函数,并将结果作为 LiveData 对象传送。

在以下示例中,loadUser() 是在其他位置声明的暂停函数。使用 liveData 构建器函数异步调用 loadUser(),然后使用 emit() 发出结果:

val user: LiveData = liveData {

val data = database.loadUser() // loadUser is a suspend function.

emit(data)

}

liveData 构建块用作协程和 LiveData 之间的结构化并发基元。当 LiveData 变为活动状态时,代码块开始执行;当 LiveData 变为非活动状态时,代码块会在可配置的超时过后自动取消。如果代码块在完成前取消,则会在 LiveData 再次变为活动状态后重启;如果在上次运行中成功完成,则不会重启。请注意,代码块只有在自动取消的情况下才会重启。如果代码块由于任何其他原因(例如,抛出 CancellationException)而取消,则不会重启。

您还可以从代码块中发出多个值。每次 emit() 调用都会暂停执行代码块,直到在主线程上设置 LiveData 值。

val user: LiveData = liveData {

emit(Result.loading())

try {

emit(Result.success(fetchUser()))

} catch(ioException: Exception) {

emit(Result.error(ioException))

}

}

您也可以将 liveData 与 Transformations 结合使用,如以下示例所示:

class MyViewModel: ViewModel() {

private val userId: LiveData = MutableLiveData()

val user = userId.switchMap { id ->

liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {

emit(database.loadUserById(id))

}

}

}

您可以从 LiveData 中发出多个值,方法是在每次想要发出新值时调用 emitSource() 函数。请注意,每次调用 emit() 或 emitSource() 都会移除之前添加的来源。

class UserDao: Dao {

@Query("SELECT * FROM User WHERE id = :id")

fun getUser(id: String): LiveData

}

class MyRepository {

fun getUser(id: String) = liveData {

val disposable = emitSource(

userDao.getUser(id).map {

Result.loading(it)

}

)

try {

val user = webservice.fetchUser(id)

// Stop the previous emission to avoid dispatching the updated user

// as `loading`.

disposable.dispose()

// Update the database.

userDao.insert(user)

// Re-establish the emission with success type.

emitSource(

userDao.getUser(id).map {

Result.success(it)

}

)

} catch(exception: IOException) {

// Any call to `emit` disposes the previous one automatically so we don't

// need to dispose it here as we didn't get an updated value.

emitSource(

userDao.getUser(id).map {

Result.error(exception, it)

}

)

}

}

}

如需更多与协程相关的信息,请参阅以下链接:

其他资源

要详细了解如何将协程与架构组件一起使用,请参阅下面列出的其他资源。

示例

博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值