资讯专栏INFORMATION COLUMN

[译] 在 Android 使用协程(part III) - 在实际工作中使用

番茄西红柿 / 1526人阅读

摘要:通过实现一次请求来解释使用协程中的实际问题是这篇文章的重点。当接收一个新事件时,启动一个新的协程来对列表进行排序,并在响应时更新。在中启动协程作为一般模式。因此,在默认情况下,在存储库中启动的任何协程都会泄露。


原文链接:Coroutines On Android (part III): Real work

原文作者:Sean McQuillan

这是一篇关于在 Android 上使用协程的系列文章之一。通过实现一次请求来解释使用协程中的实际问题是这篇文章的重点。

本系列的其他文章:

[译] 在 Android 使用协程(part I) - 协程的背景知识

[译] 在 Android 使用协程(part II) - 入门指南

使用协程解决实际问题

本系列的第 1 部分和第 2 部分重点介绍了如何使用协程来简化代码、在 Android 上提供主线程安全调用以及避免协程泄露。有了这个背景,协程看起来是一个既可以用于后台处理,又可以简化 Callback 的很好解决方案。

到目前为止,我们主要关注的是「什么是协程」以及「如何管理它们」。在这篇文章中,我们将看看如何使用它们来完成一些真正的任务。协程是一种通用的编程语言特性,与函数处于同一级别,因此,你可以使用它们来实现任何使用函数和对象实现的功能。然而,有两种类型的任务总是出现在实际代码中,协程是一种很好的解决方案:

    一次性请求 它们总是在得到响应时认为请求完成了,所以每次调用时都会重新运行的请求

    流请求 它们不会在得到第一个响应时就认为请求完成了,还会继续观察改变并将其报告给调用者

协程是这两个任务的一个很好的解决方案。在这篇文章中,我们将深入研究一次性请求,并探索如何在 Android 上用协程实现它们。

一次性请求

每次调用一个一次性请求都会执行一次,并在响应时完成。此模式与常规函数调用相同——被调用,执行一些操作,然后返回。由于与函数调用的相似性,它们往往比流请求更容易理解。

每次调用一个一次性请求时都会执行一次。一旦得到响应,就停止执行。

对于一次性请求的示例,请考虑浏览器如何加载此页面。当你点击到这篇文章的链接时,浏览器向服务器发送了一个网络请求来加载页面。一旦页面被传输到你的浏览器,它就停止与后端通信——它已经获取到需要的所有数据。如果服务器修改了这篇文章,除非你刷新页面否则新的修改将不会显示在浏览器中。

因此,虽然它们缺乏流请求的实时推送功能,但一次性请求仍旧非常强大。在 Android 应用中,有很多事情可以通过一次性请求来解决,比如获取、存储或更新数据。对于排序列表之类的事情,它也是一种很好的模式。

问题:显示已排序的列表

让我们通过查看如何显示排序列表来研究一次性请求。为了让示例更加具体,我们构建了一个「存货清单」的应用,供商店员工使用。它将用于根据产品最后一次进货的时间查找产品——他们希望能够对列表进行升序和降序排序。因为有很多产品,排序产品可能需要一秒钟,所以我们将使用协程来避免阻塞主线程!

在这个应用中,所有的产品都存储在一个 Room 数据库中。这是一个很好的用例,因为它不需要涉及网络请求,所以我们可以关注模式。尽管这个示例比较简单,因为它不使用网络,但是它展示了实现一次性请求所需的模式。

要使用协程实现这个请求,你将把协程引入到 ViewModel、Repository 和 Dao。让我们逐个浏览一下,看看如何将它们与协程集成在一起。

class ProductsViewModel(val productsRepository: ProductsRepository): ViewModel() {
   val _sortedProducts = MutableLiveData>()
   val sortedProducts: LiveData> = _sortedProducts

   /**
    * 当用户点击 sort 按钮时调用,由 UI 层调用
    */
   fun onSortAscending() = sortPricesBy(ascending = true)
   fun onSortDescending() = sortPricesBy(ascending = false)

   private fun sortPricesBy(ascending: Boolean) {
       viewModelScope.launch {
           // 挂起和恢复使这个数据库请求确保主线程安全
           // 所以我们的 ViewModel 不需要担心线程
           _sortedProducts.value =
                   productsRepository.loadSortedProducts(ascending)
       }
   }
}

ProductsViewModel 负责从 UI 层接收事件,然后向存储库请求更新后的数据。它使用 LiveData 保存当前已排序的列表,以便让 UI 显示。当 sortProductsBy 接收一个新事件时,启动一个新的协程来对列表进行排序,并在响应时更新 LiveData。ViewModel 通常是这个体系结构中启动大多数协程的正确位置,因为它可以在 onCleared中取消协程。如果用户离开界面,它们通常不再需要工作。

如果你还不经常使用 LiveData,请查看 @CeruleanOtter发布的这篇很棒的文章,它介绍了如何为 UI 存储数据.

一个简单的 ViewModel 示例 (ViewModels : A Simple Example)

这是 Android 上协程的一般模式。由于 Android 框架不能调用挂起函数,因此你需要配合一个协程来响应 UI 事件。最简单的办法是事件发生时启动一个新的协程,而在 ViewModel 做这件事比较合适。

在 ViewModel 中启动协程作为一般模式。

ViewModel 使用 ProductsRepository 来实际获取数据。来看它是这样做的:

class ProductsRepository(val productsDao: ProductsDao) {

  /**
    * 这是一个"常规"挂起函数,这意味着调用者必须处于一个协程中。存储层不负责启动或
    * 停止协程,因为它没有一个合适的生命周期来取消不必要的工作。
    * 这可以是从 Dispatchers.Main 调用的,而且是主线程安全的,因为 Room 将为我们负责
    * 主线程安全。
    */
   suspend fun loadSortedProducts(ascending: Boolean): List {
       return if (ascending) {
           productsDao.loadProductsByDateStockedAscending()
       } else {
           productsDao.loadProductsByDateStockedDescending()
       }
   }
}

ProductsRepository 为产品交互提供了一个合理的接口。在这个应用程序中,由于所有内容都在本地的 Room 数据库中,所以他只是为 @Dao 提供了一个很好的接口,对于不同的排序,@Dao 有两个不同的函数。

存储层是 Android 架构体系中一个可选部分——但如果你的应用有它或类似的层,它应该更愿意暴露常规挂起函数。因为存储层没有一个天然的生命周期——它只是一个对象——它没有办法清理工作。因此,在默认情况下,在存储库中启动的任何协程都会泄露。

除了避免泄露之外,通过暴露常规挂起函数,还可以很容易地在不同的上下文中复用存储库。任何知道如何创造协程的东西都可以调用 loadSortedProducts 。例如,Workmanager 调度的后台 Job 可以直接调用它。

存储库应该暴露出主线程安全的常规挂起函数。

注意:一些后台保存操作可能会希望用户离开界面后继续执行——在没有生命周期的情况下运行这些保存是有意义的。在大多数其他情况下,viewModelScope 是一个合理的选择。

继续看 ProductsDao,它看起来是这样的:

@Dao
interface ProductsDao {
   // 因为这是挂起的,Room 将使用它自己的调度器以主线程安全的方式运行这个查询
   @Query("select * from ProductListing ORDER BY dateStocked ASC")
   suspend fun loadProductsByDateStockedAscending(): List

   // 因为这是挂起的,Room 将使用它自己的调度器以主线程安全的方式运行这个查询
   @Query("select * from ProductListing ORDER BY dateStocked DESC")
   suspend fun loadProductsByDateStockedDescending(): List
}  

ProductsDao 是一个 Room @Dao ,它公开了两个挂起函数。由于函数被suspend 标记,Room 确保它们是主线程安全的。这意味着你可以直接从 Dispatchers.Main 来调用它们。

如果你还没有在 Room 中看到协程,请查看 @FMuntenescu的这篇很棒的文章

Room

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/7055.html

相关文章

  • [] Android 使用协程(part III) - 实际工作使用

    摘要:通过实现一次请求来解释使用协程中的实际问题是这篇文章的重点。当接收一个新事件时,启动一个新的协程来对列表进行排序,并在响应时更新。在中启动协程作为一般模式。因此,在默认情况下,在存储库中启动的任何协程都会泄露。 原文链接:Coroutines On Android (part III): Real work 原文作者:Sean McQuillan 这是一篇关于在 Android 上使用...

    newtrek 评论0 收藏0
  • [] WorkManager 基础入门

    摘要:让我们探讨一下如何确保你的工作脱离主线程运行并保证执行。这确保在默认情况下,你的工作是同步运行的,并且在主线程之外运行。这是应该脱离主线程运行的工作,但是,因为它与直接相关,所以如果关闭应用程序则不需要继续。 原文地址:WorkManager Basics 原文作者:Lyla Fujiwara 译文出自:掘金翻译计划 本文永久链接:github.com/xitu/gold-m… 译者:Ri...

    番茄西红柿 评论0 收藏0
  • [] WorkManager 基础入门

    摘要:让我们探讨一下如何确保你的工作脱离主线程运行并保证执行。这确保在默认情况下,你的工作是同步运行的,并且在主线程之外运行。这是应该脱离主线程运行的工作,但是,因为它与直接相关,所以如果关闭应用程序则不需要继续。 原文地址:WorkManager Basics 原文作者:Lyla Fujiwara 译文出自:掘金翻译计划 本文永久链接:github.com/xitu/gold-m… 译者:Ri...

    oliverhuang 评论0 收藏0
  • []教程:如何使用Rollup打包样式文件并添加LiveReload

    摘要:通过这个教程学习如何使用打包工具配合来取代或处理样式文件。使用这个命令安装插件更新。如果你没有项目的副本,你可以通过这条命令克隆在结束这个状态下的项目为添加监听插件。在代码块内,添加如下内容简单起见我省略了文件的大部分内容 通过这个教程学习如何使用JavaScript打包工具Rollup配合PostCSS来取代Grunt或Gulp处理样式文件。 上一篇文章中,我们完成了使用Rollup...

    garfileo 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<