【Rxjava详解】(四)线程切换

lift()变换原理

这些变换虽然功能各有不同,但实质上都是针对事件序列的处理和再发送。而在RxJava的内部,它们是基于同一个基础的变换方法:lift()。

首先看一下lift() 的内部实现(仅显示了部分主要逻辑代码):

public <R> Observable<R> lift(Operator<? extends R, ? super T> operator) {return Observable.create(new OnSubscribe<R>() {@Overridepublic void call(Subscriber subscriber) {Subscriber newSubscriber = operator.call(subscriber);newSubscriber.onStart();onSubscribe.call(newSubscriber);}});
}

方法用于将当前的 Observable 对象转换成另一种类型的 Observable 对象。它接受一个 Operator 参数,用于定义转换的规则。返回的是一个新的 Observable 对象。

它创建了一个新的 Observable 对象,并且将 operator 对象作用于当前 Observable 对象的订阅过程中。

在 Observable.create 方法中,通过创建一个匿名内部类实现了 OnSubscribe 接口的 call 方法。在 call 方法中,首先通过调用 operator.call(subscriber),将原始的 Subscriber 对象转换成一个新的 Subscriber 对象 newSubscriber。然后调用 newSubscriber.onStart() 方法进行一些初始化操作。最后调用 onSubscribe.call(newSubscriber),将转换后的 newSubscriber 对象传递给原始的 onSubscribe 对象进行订阅操作。

类似于这个图(别的地方扒下来的)

https://s2.loli.net/2023/11/23/iTWHCOKE791j8mA.gif

RxJava不建议开发者自定义Operator来直接使用lift(),而是建议尽量使用已有的lift()包装方法(如map()、flatMap()等)进行组合来实现需求,因为直接使用lift()非常容易发生一些难以发现的错误。

线程控制Scheduler

在不指定线程的情况下,RxJava遵循的是线程不变的原则,即在哪个线程调用subscribe()方法就在哪个线程生产事件;在哪个线程生产事件,就在哪个线程消费事件。也就是说事件的发出和消费都是在同一个线程的。观察者模式本身的目的就是『后台处理,前台回调』的异步机制,因此异步对于RxJava是至关重要的。而要实现异步,则需要用到RxJava的另一个概念:Scheduler。

Scheduler简介

在RxJava中,Scheduler相当于线程控制器,通过使用 Scheduler 可以实现事件的异步处理和线程切换。Scheduler 可以指定事件发送和处理所在的线程,从而实现异步的操作,RxJava 提供了多种类型的 Scheduler:

  • Schedulers.immediate(): 直接在当前线程运行,相当于不指定线程。这是默认的Scheduler。
  • Schedulers.newThread(): 总是启用新线程,并在新线程执行操作。
  • Schedulers.io(): I/O 操作(读写文件、读写数据库、网络信息交互等)所使用的Scheduler。行为模式和newThread()差不多,区别在于io() 的内部实现是是用一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下io()比newThread()更有效率。不要把计算工作放在io()中,可以避免创建不必要的线程。
  • Schedulers.computation(): 计算所使用的Scheduler。这个计算指的是CPU密集型计算,即不会被I/O等操作限制性能的操作,例如图形的计算。这个Scheduler 使用的固定的线程池,大小为CPU核数。不要把I/O操作放在computation()中,否则I/O操作的等待时间会浪费CPU。
  • 另外,Android还有一个专用的AndroidSchedulers.mainThread(),它指定的操作将在Android主线程运行。

有了这几个Scheduler,就可以使用subscribeOn()和observeOn()两个方法来对线程进行控制了。subscribeOn()指定subscribe()所发生的线程,即Observable.OnSubscribe()被激活时所处的线程或者叫做事件产生的线程。observeOn()指定Subscriber所运行在的线程或者叫做事件消费的线程。

Observable.just("Hello").subscribeOn(Schedulers.io()) // 在 IO 线程发送事件.map(str -> str + " World").observeOn(AndroidSchedulers.mainThread()) // 在主线程中处理事件.subscribe(str -> {// 更新 UItextView.setText(str);}, throwable -> {// 处理错误Log.e(TAG, "Error: " + throwable.getMessage());});

上面这段代码中,subscribeOn(Schedulers.io())的指定会让创建的事件的内容Hello 、World !将会在IO线程发出;而由于observeOn(AndroidScheculers.mainThread()) 的指定,因此subscriber()方法设置后的回调中内容的打印将发生在主线程中。事实上,这种在subscribe()之前写上两句subscribeOn(Scheduler.io())和observeOn(AndroidSchedulers.mainThread())的使用方式非常常见,它适用于多数的***后台线程取数据,主线程显示***的程序策略。

Scheduler的原理

我们可以多切换几次线程,因为observeOn()指定的是Subscriber的线程,而这个Subscriber并不是subscribe() 参数中的Subscriber,而是observeOn()执行时的当前Observable所对应的Subscriber,即它直接对应的Subscriber。换句话说observeOn() 指定的是它之后的操作所在的线程。所以想要多次切换线程,只要在每个想要切换线程的位置调用一次observeOn()即可。

Observable.just("Hello").subscribeOn(Schedulers.io()) // 在 IO 线程执行.observeOn(Schedulers.computation()) // 切换到计算线程执行.map(s -> s + " World").observeOn(AndroidSchedulers.mainThread()) // 切换到主线程执行.subscribe(s -> {// 更新 UItextView.setText(s);});

如上,通过observeOn()的多次调用,程序实现了线程的多次切换。 不过,不同于observeOn(),subscribeOn()的位置放在哪里都可以,但它是只能调用一次的。

subscribeOn()和observeOn()的内部实现,也是用的lift()。

具体看图(不同颜色的箭头表示不同的线程,subscribeOn()原理图:

https://s2.loli.net/2023/11/23/BW1uFAZn4rGHOe6.jpg

observeOn()原理图:

https://s2.loli.net/2023/11/23/3rfzvsEFhUDTn1Y.jpg

从图中可以看出,subscribeOn()和observeOn()都做了线程切换的工作(图中的schedule...部位)。不同的是,subscribeOn()的线程切换发生在OnSubscribe中,即在它通知上一级 OnSubscribe时,这时事件还没有开始发送,因此subscribeOn()的线程控制可以从事件发出的开端就造成影响;而observeOn()的线程切换则发生在它内建的Subscriber中,即发生在它即将给下一级Subscriber发送事件时,因此observeOn()控制的是它后面的线程。

用一张图来(扒的)解释当多个subscribeOn()和observeOn()混合使用时,线程调度是怎么发生的

https://s2.loli.net/2023/11/23/Q8JYfSKCa3hjgcr.jpg

图中共有5处含有对事件的操作。由图中可以看出,①和②两处受第一个subscribeOn()影响,运行在红色线程;③和④处受第一个observeOn()的影响,运行在绿色线程;⑤处受第二个 onserveOn()影响,运行在紫色线程;而第二个subscribeOn(),由于在通知过程中线程就被第一个subscribeOn() 截断,因此对整个流程并没有任何影响。这里也就回答了前面的问题:当使用了多个subscribeOn()的时候,只有第一个subscribeOn()起作用。

在前面讲Subscriber的时候,提到过Subscriber的onStart()可以用作流程开始前的初始化。然而onStart()由于在subscribe()发生时就被调用了,因此不能指定线程,而是只能执行在subscribe()被调用时的线程。这就导致如果onStart()中含有对线程有要求的代码(例如在界面上显示一个ProgressBar,这必须在主线程执行),将会有线程非法的风险,因为有时你无法预测subscribe()将会在什么线程执行。

而与Subscriber.onStart()相对应的,有一个方法Observable.doOnSubscribe()。它和Subscriber.onStart()同样是在subscribe()调用后而且在事件发送前执行,但区别在于它可以指定线程。默认情况下,doOnSubscribe()执行在subscribe()发生的线程;而如果在doOnSubscribe()之后有subscribeOn()的话,它将执行在离它最近的subscribeOn()所指定的线程。

示例代码:

Observable.create(onSubscribe).subscribeOn(Schedulers.io()).doOnSubscribe(new Action0() {@Overridepublic void call() {progressBar.setVisibility(View.VISIBLE); // 需要在主线程执行}}).subscribeOn(AndroidSchedulers.mainThread()) // 指定主线程.observeOn(AndroidSchedulers.mainThread()).subscribe(subscriber);

Agera

之前Google发布agera,它在Github上的介绍是:Reactive Programming for Android,可以进行了解。它为 Android 应用程序提供了一种简单且灵活的方式来处理数据流和事件驱动的编程模型。很轻量化,很适合安卓。

但是缺点也很明显:与 RxJava 相比,Agera 的功能相对较为有限,操作符和功能较少。对于一些复杂的数据流操作和并发处理,可能需要额外的工作量来实现

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

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

相关文章

Kubernetes之kubeadm集群部署篇—系统初始化

文章目录 操作系统初始化配置1.1 设置主机名1.2 设置hosts解析主机名1.3 关闭SELinux和防火墙1.4 关闭swap1.5 设置系统参数1.6 设置节点间的SSH免密码登录1.7 配置docker 操作系统初始化配置 1.1 设置主机名 根据规划设置主机名 &#xff08;所有节点&#xff09; hostname…

盘点43个Android项目源码安卓爱好者不容错过

盘点43个Android项目源码安卓爱好者不容错过 学习知识费力气&#xff0c;收集整理更不易。 知识付费甚欢喜&#xff0c;为咱码农谋福利。 链接&#xff1a;https://pan.baidu.com/s/1yHmkUeX4vxVag9Yr0yeQRg?pwd8888 提取码&#xff1a;8888 项目名称 Android NDK直播项…

Java(七)(Lambda表达式,正则表达式,集合(Collection,Collection的遍历方式))

目录 Lambda表达式 省略写法(要看懂) 正则表达式 语法 案例 正则表达式的搜索替换和分割内容 集合进阶 集合体系结构 Collection Collection的遍历方式 迭代器 增强for循环 Lambda表达式遍历Collection List集合 ArrayList LinkedList 哈希值 HashSet底层原理 …

(附源码)springboot电影售票系统小程序 计算机毕设36991

目 录 摘要 1 绪论 1.1课题目的与意义 1.2研究背景 1.3论文结构与章节安排 1.4小程序框架以及目录结构介绍 2 springboot电影售票系统小程序系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 操作可行性分析 2.2 系统流程分析 2.2.1 数据…

Elasticsearch集群部署

服务器 安装软件主机名IP地址系统版本配置ElasticsearchElk10.3.145.14centos7.5.18042核4GElasticsearchEs110.3.145.57centos7.5.18042核3GElasticsearchEs210.3.145.57centos7.5.18042核3G 软件版本&#xff1a;elasticsearch-7.13.2.tar.gz 示例节点&#xff1a;10.3.145…

Redis分布式锁实现Redisson 15问

在一个分布式系统中&#xff0c;由于涉及到多个实例同时对同一个资源加锁的问题&#xff0c;像传统的synchronized、ReentrantLock等单进程情况加锁的api就不再适用&#xff0c;需要使用分布式锁来保证多服务实例之间加锁的安全性。常见的分布式锁的实现方式有zookeeper和redis…

文本编辑 UTF-8 BOM 中的BOM释义

参考资料 UTF8のBOM無しとBOM付きの違いBOMなしUTF-8によってWindowsでもたらされる困惑文字コードをUTF-8 BOMなし(UTF-8N)でファイル保存をする方法 目录 一. 前提二. BOM三. CSV文件中的表现 一. 前提 在使用Windows自带的记事本编辑.csv文件的时候&#xff0c;准备保存为…

Java 基础学习(一)Java环境搭建和基本数据类型

1 Java 开发环境搭建 1.1 Java 编程语言 1.1.1 什么是Java编程语言 语言是人类进行沟通交流的各种表达符号&#xff0c;方便人与人之间进行沟通与信息交换&#xff1b;而计算机编程语言则是人与计算机之间进行信息交流沟通的一种特殊语言&#xff0c;也有语法规则、字符、符…

kali linux英文改中文

如果英语基础较好的同学可以不用调整 反之则需要 找到终端&#xff08;就是输入命令的那个地方 如下&#xff09;点击它出现命令终端 切换为root用户&#xff0c;命令为&#xff1a; sudo dpkg-reconfigure locales 然后回车 找到这个zh_CN 然后回车 鼠标下键选中并且回车 输…

自动驾驶学习笔记(十一)——高精地图

#Apollo开发者# 学习课程的传送门如下&#xff0c;当您也准备学习自动驾驶时&#xff0c;可以和我一同前往&#xff1a; 《自动驾驶新人之旅》免费课程—> 传送门 《Apollo Beta宣讲和线下沙龙》免费报名—>传送门 文章目录 前言 高精地图 地图采集 底图制作 地图…

HCIA-H12-811题目解析(1)

1、【多选题】关于动态 MAC 地址表说法正确的是&#xff1f; A、通过报文中的源MAC地址学习获得的动态MAC表项会老化 B、通过查看指定动态MAC地址表项的个数&#xff0c;可以获取接口下通信的用户数 C、在设备重启后&#xff0c;之前的动态表项会丢失 D、在设备重启后&…

Vue打包错误UnhandledPromiseRejectionWarning: CssSyntaxError

错误详情如下&#xff1a; building for production...Error processing file: static/css/app.3d5caae7aaba719754d7d5c30b864551.css (node:33011) UnhandledPromiseRejectionWarning: CssSyntaxError: /Users/yt/Documents/BM/sims-plus/sims-website/static/css/app.3d5caa…

Docker笔记-Docker搭建最新版zabbix服务端(2023-07-31)

前言 一开始问chartgpt上&#xff0c;搭建的思路是对的&#xff0c;但命令和细节有问题&#xff0c;最后还是依靠StackOverflow解决的。一开始在amd的linux上搭建好docker版的zabbix&#xff0c;但放到arm的机器上就报错了&#xff0c;原因是指令集不匹配&#xff0c;最后跑到…

C#每天复习一个重要小知识day9:常量和静态变量

常量&#xff08;Const&#xff09;是指在程序中定义的固定不变的数值或者对象&#xff0c;其值在程序执行过程中不能被修改。常量在程序中起到了固定数值或者对象的作用&#xff0c;使得程序的逻辑更加清晰和可维护。 Const&#xff08;常量&#xff09;可以理解为特殊的stat…

网关知识总结

网关&#xff08;Gateway&#xff09;是一种网络设备或软件&#xff0c;用于连接两个不同的网络或协议&#xff0c;并能够在这两个网络或协议之间进行数据交换。网关是网络体系结构中的重要组成部分&#xff0c;它可以使不同的网络或协议相互通信&#xff0c;实现数据的传输和处…

基于PLC的果园灌溉系统设计(论文+源码)

1.系统设计 系统示意图如图2-1所示。某一果园 共有3个灌溉区域&#xff0c;分别为灌溉1#区&#xff0c;灌溉2#区&#xff0c;灌溉3#区&#xff0c;分别使用不同湿度传感器检测湿度&#xff0c;用于各区域控制湿度&#xff0c;进行灌溉&#xff0c;使用相应的灌溉阀进行灌溉。这…

Go语言的学习笔记3——Go语言项目布局

Go 1.11 版本开始引入 go.mod 和 go.sum 以支持Go Module构建机制&#xff0c;而这种机制成为官方的依赖包管理方式。 现在Go可执行程序项目的典型布局如下所示&#xff1a; exe-layout ├── cmd/ │ ├── app1/ │ │ └── main.go │ └── app2/ │ └…

使用python提取出身份证的出生日期(18位和15位)

有的时候我们需要在身份证中提取出出生日期&#xff0c;只是我们的身份证有两种&#xff0c;一种是15位&#xff0c;一种是18位&#xff0c;提取的时候就有点麻烦&#xff0c;记录一下&#xff0c;直接上代码&#xff0c;用的是python&#xff0c;其他语言也可以用这个逻辑。 …

CCC联盟数字车钥匙(三)——UWB MAC时间网格同步及Hopping

本文继续上一篇UWB MAC时间网格继续介绍UWB MAC中关于时间同步相关内容。 3、MAC时间网格同步 每个测距会话的定义都基于相对的指定时钟参考 U W B t i m e 0 k UWB^k_{time0} UWBtime0k​&#xff0c;相对于发起者的内部时钟定义。 时钟参考 U W B t i m e 0 k UWB^k_{time0} …

关于营销的一些总结

多用图&#xff0c;少用点。(要关联其他文章、平台&#xff0c;做流量引导) 持续更新&#xff0c;欢迎关注。