Android中的异步处理之RxJava与协程(Coroutines)使用案例PK

news2024/12/24 9:18:58

RxJava一直是我长久以来的救星。它提供了丰富的功能,让我在Android编程中更加注重响应式思维。我的代码中到处都是SingleSubjectCompletable

而现在,协程成为了备受赞誉和推崇的选择,许多演讲和会议都推荐使用。于是我开始学习它。

为了展示我目前的学习成果,我将尝试比较RxJava和协程在解决一些常见问题时的差异。我使用的库和设计模式有:

  • MVVM与Android Architecture Component
  • Repository模式
  • Retrofit

加载并显示

打开菜单片段并显示从服务器获取的咖啡列表

RxJava

让我们从ViewModel的角度开始。在MenuViewModel中,我调用menuRepository.getMenu()以获取Single的实例。

class MenuViewModel @Inject constructor(
  private val menuRepository: MenuRepository)
: ViewModel() {
  val coffeeList: LiveData<List<Coffee>>
    get() = _coffeeList
  private val _coffeeList = MutableLiveData<List<Coffee>>()
  private val disposeBag = CompositeDisposable()

  override fun onCleared() {
    super.onCleared()
    disposeBag.dispose()
  }
  
  fun loadMenu() {
    val disposable = menuRepository.getMenu()
      .subscribe { list: List<Coffee> ->
        _coffeeList.value = list
      }
    disposeBag.add(disposable)
  }
}

请注意,忽略了错误处理。

为了避免内存泄漏,在onCleared()期间我们必须记得处理可释放的资源。

repository代码如下:

class MenuRepository @Inject constructor(
  private val menuApi: MenuApi
) {
  
  fun getMenu(): Single<List<Coffee>> {
    return menuApi.getMenu()
      .observeOn(AndroidSchedulers.mainThread())
      .subscribeOn(Schedulers.io())
  }
}

interface MenuApi {
  @GET("menu")
  fun getMenu(): Single<List<Coffee>>
}

我们正在调用subscribeOnobserveOn来确保网络调用不在主线程上执行。

Coroutines

class MenuViewModel @Inject constructor(
  private val menuRepository: MenuRepository)
: ViewModel() {
  val coffeeList: LiveData<List<Coffee>> = liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
    emit(menuRepository.getMenu())
  }
}

class MenuRepository @Inject constructor(
  private val menuApi: MenuApi
) {
  suspend fun getMenu(): List<Coffee> {
    return menuApi.getMenu()
  }
}

interface MenuApi {
  @GET("menu")
  suspend fun getMenu(): List<Coffee>
}

正如您在这里所看到的,借助协程和KTX扩展,代码变得简单得多。生命周期和线程管理只需向liveData块传递一个参数即可:

context = viewModelScope.coroutineContext + Dispatchers.IO
在这里插入图片描述

将视图A从视图B更新

当用户在菜单类型Fragment中选择菜单类型时,更新咖啡菜单。

更具体地说,菜单类型Fragment放置在菜单Fragment上方。当用户在此处更改菜单类型时,我们通过所选的菜单类型更新下方的菜单。

为了分离关注点并解耦组件,这两个片段将不直接相互通信。我们将利用MenuRepository作为中间人,并让两个片段共享相同的实例。这可以通过依赖注入框架如Dagger来实现。这里将忽略其实际实现。

RxJava

class MenuViewModel {
  fun loadMenu() {
    menuRepository.menu
      .subscribe { list: List<Coffee> ->
        _coffeeList.value = list
      }
    menuRepository.refreshMenu(MenuType.DEFAULT).subscribe()
  }
}

class MenuRepository {
  private val menuSubject = BehaviorSubject.create<List<Coffee>>()
  
  val menu: Observable<List<Observable>>
    get() = menuSubject
  
  fun refreshMenu(menuType: MenuType): Completable {
    return menuApi.getMenu(menuType)
      .observeOn(AndroidSchedulers.mainThread())
      .subscribeOn(Schedulers.io())
      .doOnSuccess { menuSubject.onNext(it) }
      .ignoreElement()
  }
}

MenuRepository中,getMenu()被替换为一个菜单属性和一个refreshMenu()方法。我使用副作用运算符doOnSuccess()将接收到的数据传递给Subject,它是一个BehaviorSubject,用于保存最新的菜单数据。在MenuFragment中,getMethod().subscribe {}被拆分为两个调用,menu.subscribe {}refreshMenu().subscribe()

通过这个变化,我们可以在MenuTypeFragment中简单地这样做。

class MenuTypeViewModel @Inject constructor(
  private val menuRepo: MenuRepository
): ViewModel() {
  
  fun onMenuTypeChanged() {
    menuRepo.refreshMenu(MenuType.TODAY_SPECIAL).subscribe()
  }
}

请注意,我忽略了一次性处理。为了避免任何内存泄漏,您仍需执行前一个用例中展示的相同处理。此外,如果API调用失败,每次调用menuRepo.refreshMenu()都必须进行错误处理。

Coroutines

在这种情况下,协程并没有像示例3中那样简化。接下来您将看到原因所在。

class MenuViewModel {
  val coffeeList: LiveData<List<Coffee>> = liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
    // if the data needs to be processed before displaying,
    // this is where I usually do it
    menuRepo.menu.collect(::emit)
  }
}

class MenuRepository {
  private val menuChannel = BroadcastChannel<List<Coffee>>(CONFLATED)
  
  val menu: Flow<List<Coffee>>
    get() = channel.asFlow()

  suspend fun refreshMenu(menuType: MenuType) {
    menuChannel.send(menuApi.getMenu(menuType))
  }
}

interface MenuApi {
  @GET("menu")
  suspend fun getMenu(@Query("type") menuType: MenuType): List<Coffee>
}

MenuRepository中,我们仍然需要创建与BehaviorSubject类似的东西。在协程世界中,它被称为BroadcastChannel(CONFLATED)。这是一个工厂方法,它创建了ConflatedBroadcastChannel的实例。就像在RxJava中有其他类型的Subject一样,这里也有其他类型的BroadcastChannel

为了能够观察到热数据流,我们使用asFlowBroadcastChannel转换为Flow。然后我们可以在ViewModel中调用collect()来持续接收新数据。

这个概念更接近于RxJava世界而不是协程世界。collect()类似于subscribe()Flow类似于Observable。如果你看一下Flow,会发现有很多内置的操作符,比如retryWhendebounce,这些在RxJava中已经存在并且我们都喜欢。

然后在MenuTypeFragment中代码如下:

class MenuTypeViewModel @Inject constructor(
  private val menuRepo: MenuRepository
): ViewModel() {
  
  fun onMenuTypeChanged() {
    viewModelScope.launch {
      // try-catch-finally is how we usually handle 
      // error in Coroutines world
      try {
        menuRepo.refreshMenu(MenuType.TODAY_SPECIAL)
      } catch (e: Exception) {
        // e.g. 
        // notify user
        // record exception to your exception monitoring service
      } finally {
        // e.g.
        // dismiss loading indicator
        // enable views
      }
    }
  }
}

由于我们使用了ConflatedBroadcastChannel,因此refreshMenu()内部的send()将永远不会挂起。这意味着如果有很多地方使用refreshMenu(),将不会有很多挂起的协程持有您的资源。

随着Kotlin协程1.4.0的发布,ConflatedBroadcastChannel已被弃用。StateFlowShareFlow被引入。您可以在官方公告中了解更多信息。

使用哪个调度器?

在代码片段6中,我们直接在collect中调用emit

menuRepo.menu.collect(::emit)

但是要设置值给LiveData使用了哪个调度器?为了找到答案,我打印了一些日志。

menuRepo.menu.collect {
    Log.d("MenuViewModel", Thread.currentThread().name)
    emit(it)
}

//打印结果: D/MenuViewModel: DefaultDispatcher-worker-1
这看起来不对劲,在我没有在主线程中设置LiveData值的情况下,为什么这不会导致应用崩溃?

原来,KTX库已经为我们解决了这个问题。在liveData { }的emit()内部,它执行了以下操作:

override suspend fun emit(value: T) = withContext(coroutineContext) {
    // target: LiveData
    target.clearSource()
    target.value = value
}

而你可能猜到了,这就是coroutineContext:

private val coroutineContext = context + Dispatchers.Main.immediate

这里的Dispatchers.Main表示我们Android的主线程。

订阅的取消处理?

那么menuRepo.menu.collect()的取消处理呢?当LiveData变为非活跃状态时,订阅会自动取消吗?还是我们仍然需要在RxJava中执行所有的步骤?

答案是是的,它会自动取消。在KTX库内部,liveData { }生成的LiveDataLiveData变为非活跃状态时会取消正在运行的协程。

结论

当您有一个需要等待其结果的阻塞耗时任务时,协程本身可能是一个很不错的选择。如果您使用Android KTX库,您将发现自己编写了最高效的代码。

然而,当您想使用协程来实现观察者设计模式时,与RxJava相比,它并没有提升多少生产力。最终,协程本身并不是专门为此而构建的。

RxJava仍然是我工具箱中最强大的工具。我不建议在所有情况下都迁移到协程。协程中仍然存在很多实验性API,包括我示例中的BroadcastChannel。协程真正发挥作用还需要时间。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/648809.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用Vision Transformers实现高效语义分割的内容感知共享Token

文章目录 Content-aware Token Sharing for Efficient Semantic Segmentation with Vision Transformers摘要本文方法Content-aware token sharing frameworkContent-aware token sharing policy 实验结果 Content-aware Token Sharing for Efficient Semantic Segmentation wi…

Vue中如何进行滚动吸顶与侧边栏固定

Vue中如何进行滚动吸顶与侧边栏固定 在Vue应用程序中&#xff0c;当需要实现滚动吸顶和侧边栏固定效果时&#xff0c;我们可以使用一些技术来实现。这些技术包括CSS和JavaScript&#xff0c;可以帮助我们实现各种各样的滚动效果。 如何实现滚动吸顶&#xff1f; 滚动吸顶是指…

Ubuntu 系统如何使用 root 用户登录实例

Ubuntu 系统的默认用户名是 ubuntu&#xff0c;并在安装过程中默认不设置 root 帐户和密码。您如有需要&#xff0c;可在设置中开启允许 root 用户登录。具体操作步骤如下&#xff1a; 1. 使用 ubuntu 帐户登录轻量应用服务器。 2. 执行以下命令&#xff0c;设置 root 密码。…

Java判断一个字符串是否包含某个字符串

开发过程中&#xff0c;有时会判断一个字符串是否包含某个字符串的操作&#xff0c;这里总结判断方法。 方式一&#xff1a;contains()方法 理解&#xff1a;contains() 方法用于判断字符串中是否包含指定的字符或字符串。&#xff08;判断一个字符串是否包含某个字符串&#…

网上书店 Vue+Spring boot+H5+Uniapp

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 基于VueSpring boot的网上书城 目录一、项目模块二、项目模块三 技术选型四、运行环境五 .PC登录页面代码六 .H5登录页面代码 运行效果源码 目录 网上书店 VueSpring bootH5U…

Cos 文件上传下载

目录 方法一&#xff1a; maven依赖&#xff1a; UploadServlet upload.jsp 方法二&#xff1a; maven依赖 UploadServlet upload.jsp success.jsp error.jsp 运行结果&#xff1a; 百度文件上传插件&#xff1a; Web Uploader 本文通过JSPServlet的架构&#xff0c…

华为OD机试真题 JavaScript 实现【字符串变换最小字符串】【2022Q4 100分】

一、题目描述 给定一个字符串s&#xff0c;最多只能进行一次变换&#xff0c;返回变换后能得到的最小字符串&#xff08;按照字典序进行比较&#xff09;。 变换规则&#xff1a;交换字符串中任意两个不同位置的字符。 二、输入描述 一串小写字母组成的字符串s。 三、输出…

文件系统整体流程介绍

一、什么是文件系统 计算机的文件系统是一种存储和组织计算机数据的方法&#xff0c;它使得对其访问和查找变得容易&#xff0c;文件系统使用文件和树形目录的抽象逻辑概念&#xff0c;用户使用文件系统来保存数据不必关心数据实际保存在硬盘的地址为多少的数据块上&#xff0…

拨云见日:Redis和数据库之间的一致性如何保证?

概 述 Redis在使用过程中&#xff0c;有四个异常问题&#xff1a;缓存穿透、缓存击穿、缓存雪崩、以及缓存和数据库&#xff08;MySQL&#xff09;双写一致性问题。 前三个问题可能会因为业务体量的不同而有所不同&#xff0c;但是最后一个问题是无法避免的。就算你的电商业…

OPNET出现错误的解决办法汇总

文章目录 Packet pointer references unowned packet(<pk_id>) 错误Standard function stack imbalance 错误Invalid Memory Access 错误 在使用 OPNET Modeler 软件时&#xff0c;会遇到很多奇奇怪怪的报错&#xff0c;这篇文章收集的是自己在使用该软件时遇到的一些错误…

易基因:组学研究揭示不同牛品种的DNA甲基化、染色质和基因表达互作机制|科研进展

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 在全球范围内&#xff0c;牛为60多亿人提供了重要的营养来源。传染病是养牛生产的主要限制因素&#xff0c;且许多疾病人畜共患&#xff0c;因此与人类健康直接相关。近年来牛的许多复杂…

CBTC信号系统ATP子系统接口

ATP/ATO车载设备与车辆接口要求 ATP/ATO车载设备应实现与车辆制动装置的可靠接口&#xff0c;保证安全和对列车实施连续有效的控制。 ATP/ATO车载设备与车辆的接口分为开关量、模拟量、通信接口三种。 涉及行车安全的电气接口应采用安全输入&#xff0f;输出接口方式。 ATP…

electron 快速创建一个本地应用

参考官方文档流程 快速入门 | Electron 建议先全局安装electron&#xff0c;npm install -g electron 开发过程中可以在本地开发安装 使用electron快速创建一个web页面 &#xff0c;参考官方demo 实例 electron-quick-start 第一步&#xff1a; mkdir my-electron-app &am…

银行信用卡流失预测模型_基于ANN神经网络_金融培训_论文科研_毕业设计

业务背景 根据央行公布的数据显示&#xff0c;全国性银行信用卡和借贷合一卡的发卡量增速从2017年同比增速26.35%的高点逐年下降&#xff0c;截至2020年同比增速降至4.26%。银行信用卡发卡增速明显放缓的背景下&#xff0c;预防老客户流失的问题变得愈发重要。 假设一家消费信…

前端开发中有哪些鲜为人知的技巧?

下面分享一些前端开发鲜为人知的HTML/CSS/JS技巧&#xff0c;希望大家可以有所收获。 一、Datalist元素 不知道为什么&#xff0c;这个元素不太被人所使用。<datalist>标签被用于为<input>元素提供一个“自动补全”的功能。 例如&#xff1a; <input list&qu…

MMPose安装记录

参考&#xff1a;GitHub - open-mmlab/mmpose: OpenMMLab Pose Estimation Toolbox and Benchmark. 一、依赖环境 MMPose 适用于 Linux、Windows 和 macOS。它需要 Python 3.7、CUDA 9.2 和 PyTorch 1.6。我的环境&#xff1a; Windows 11 Python 3.9 CUDA 11.6 PyTorch 1.13 …

影响客户管理系统的因素有哪些?能买断吗?

客户管理系统是企业数字化转型的必由之路&#xff0c;对于没有部署CRM客户管理系统的企业来说除了关注软件功能还要关注价格&#xff0c;客户管理软件系统多少钱&#xff1f;是否需要买断&#xff0c;今天我们就来说一说。 一、什么是客户管理系统 客户管理系统是帮助企业建立…

经典基于外观的SLAM框架-RTABMAP(RGBD视觉输入方案)

经典基于外观的SLAM框架-RTABMAP 文章目录 经典基于外观的SLAM框架-RTABMAP1. RTABMAP整体框架2.RTABMAP的内存管理机制3. 视觉里程计4. 局部地图5. 回环检测与图优化6. 代码工程实践 1. RTABMAP整体框架 RTABMAP是采用优化算法的方式求解SLAM问题的SLAM框架&#xff0c;本赛题…

Linux---虚拟机配置固定IP

1. IP地址 每一台联网的电脑都会有一个地址&#xff0c;用于和其它计算机进行通讯 IP地址主要有2个版本&#xff0c;V4版本和V6版本&#xff08;V6很少用&#xff0c;课程暂不涉及&#xff09; IPv4版本的地址格式是&#xff1a;a.b.c.d&#xff0c;其中abcd表示0~255的数字…

深度学习应用篇-元学习[15]:基于度量的元学习:SNAIL、RN、PN、MN

【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化算法、卷积模型、序列模型、预训练模型、对抗神经网络等 专栏详细介绍&#xff1a;【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化…