RxJava 以及 Android 中的通用线程解决方案、并发与线程安全
关于RxJava如今是熟到发紫了,所以对于它底层的动作机制的了解是迫在眉睫了,费话不多说,直接开始。
这里还是以之前获取个人github仓库列表为例,用retrofit+rxjava,也是实际项目中用得最多的,先来回顾一下当时【https://www.cnblogs.com/webor2006/p/10502230.html】在研究retrofit时所定义的API:
这里新建一个工程,深入之前先来学会RxJava的基本使用,先来声明API,首先得增加retrofit的支持,如下:

然后API定义如下:

具体使用:

运行:

以上不多说,只是回顾一下retrofit的用法,接下来改用RxJava的方式,先增加相关的依赖:

,首先API返回的由Call就得变为Single了,如下:

而在我的公司项目中貌似返回的Observable这个对像,如下:

关于这俩的区别在之后再说,先默认使用Single,修改了API之后,接下来使用一下:

此时编译运行一下:

报错了,貌似是无法从Call转换成Single对像,因为API如今是返回Single了:

所以,此时需要增加一个CallAdapter的配置,用来支持进行转换,如下:

然后再运行:

失败了。。这是为啥呢?我们知道其实RxJava是需要手动来切换线程的,所以先增加它,先不管其内部实现:


成功啦,目前由于在回调中木有操作View,如果在回调中操作View会怎么样呢,试试:


再编译运行:

很显然是由于是非UI线程更新View异常了,所以此时对于回调所在线程是在非UI线程里的,所以需要将其切换到主线程里,这里需要增加另外一个库了:


编译运行:

其中还有一个回调方法:

请求前的回调,所以可以在请求前来在界面中增加一个请求提示,如下:


其中这个回调有一个Disposable参数,它的意义简单说可以理解成取消订阅,具体可以这样用它:

好,对于利用Rxjava来实现网络请求的简单使用就到这,接下来则重点是来理解这个框架,当然不是先来直接分析这个代码,而是将其拆解,从最基础的使用上来理解,如下:
Single和Observable:

运行一下:

顺间就显示了,因为是本地的数据,对于纯小白来说看到这样的写法还是挺难理解的,其订阅关系是:

接下来就彻底来挼清这个简单代码的动作机理,先来看下它:


很明显是来对参数做判空,简单瞅下它的实现,很简单:

继续:

这句涉及到两个陌生的东东:SingleJust、RxJavaPlugins.onAssembly(),下面先来看下RxJavaPlugins.onAssembly()的细节:

看到泛型就晕。。也只能硬着头皮细细揣摩:

其中Function是传一个类型的类,返回另一个类型,熟悉Java8的亲们应该比较熟悉:

如果onSingleAssembly不为空则就执行转换了,而它是用户可以配置的,其实也就是是否要对Single对象进行进一步加工,默认肯定是不需要,所以,对于这句话可以简单理解:

所以此just()方法重点看到它就成了,所以点进去看一下SingleJust,如下:

那再看一下Single,发现是一个3000多行超级大的抽象类:

所以里面的细节就不必现跟了,总之记着SingleJust里面重写了subscribeActual()方法:

这个方法会在调用它时最终被调用的:

所以点击进去看是不是这样:


很显然最终会调用SingleJust中的subscribeActual()方法嘛,所以要理解这个回调是如何调用的:

就需要看SingleJust的subscribeActual()方法的具体实现细节啦,如下:

秒懂不,直接就调用了我们传的回调对象嘛,以上简单的示例虽说简单,但是完整的阐述了RxJava的一个订阅实现细节,也就是其比较核心的原理,很重要,再简单挼下:

此时会在里面生成一个SingleJust对像:


当调用它时,实际会调用SingleJust.subscribeActual()方法,而subscribeActual()方法最终又会回调我们的这个对象的回调方法,如下:

至此,整个这个示例原理就彻底理清楚了,接下来继续深入RxJava,此时就涉及到操作符相关的东东了,关于什么是操作符,对于熟悉Java8的亲们也是不会陌生滴,就不过多解释啦,这里以map转换符为例,如下:

此时运行效果一样,这里的map做了啥事件,就已经牵扯到RxJava的结构啦,下面用图来剖析下:


而在我们调用Single.subscribe()方法时,其调用其实是这样子滴:

好,那此时如果再加上一个map的转换操作符后,具体又是如何运转的呢?

具体是如何转换的呢?此时从图中切回到代码:

等于创建了一个新的SingleMap,也就是表象上看着还是Single对象,其实内部已经发生对象的转换了,当然需要将当前的Single对像给传进来:

而此时又回到图画一下当map()之后的形态变化:

接下来就瞅一下SingleMap干了啥?

其中source则是原Single对像,调用了它的subscribe方法,但是!!此时的SingleObserver对像变化了,变为了MapSingleObserver啦,此时的图片形态就变成了:

接下来则具体看下它里面是如何工作的:

接着执行onSuccess()方法:

当然还可以再多增加其它的操作符,总的来说源和目标肯定只有一个,中间的都是形成了一个链,发送会往源传,得到结果之后则从源往目标传,理解这个理念非常之重要。
说了Single,也谈一下Observable,Rxjava1时还木有Single,其实Single就是Observable的简化版本,它的回调就只观注开始和结束,而对于Observable而言,它的中间过程也会回调,如下:

其中的onNext()可能会被调用多次,了解一下区别既可。
Disposable:
在之前咱们已经稍微提到过它,其实是可以做取消订阅用的,下面用一个例子来实践一下,这里采用Observable来进行每秒间隔发事件,如下:

运行看一下效果:

此时增加一个按钮,在运行期间将其停止掉,这时就可以利用Disposable了,如下:


运行:

接下来很想知道Observable.subscribe()方法到底做了啥导致在onSubscribe()方法中得到了一个什么样的Disposable,才能知道Disposable是怎么工作的,不同的Observable它的Disposable是不一样的,所以咱们先来看一下目前这个Observable是一个什么样的Observable,看它:

点开看一下interval():



下面来细看一下:

此时就得查看一下IntervalObserver了:

也就是说:

调用的也就是IntervalObserver.dispose()方法,所以得研究一下它的具体实现:

然后看一下具体实现:

而对于IntervalObserver中的run()方法就会根据这个状态来做判断,如下:

没有中间操作符的Disposable工作方式都是这样的,没有一个传递的过程,接下来咱们来看一下有map操作的Disposable又是咋样的:

然后点击查看源码:



也就是最终:

调用的是MapObserver.dispose()方法,但是!!貌似在MapObserver中木有找到dispose()方法呀:

这就需要往父类进行查找喽,果真有,如下:

其中的s是?

那不就是源,也就是最终会调用源的dispose()方法,如下:

总结Disposable也就是两种场景:
1、原始的Disposable则是停掉自己的任务。
2、如果带有操作符的,则先停掉自己,然后再停掉原始的Disposable。
线程切换Scheduler:
关于线程切换有几处,咱们一个个来剖析,这里还是回到Single的程序来,首先来瞅下它:

点击进行:


其中核心是:

也就是我们调用subscribeOn所传的参数:

点进去瞅下:

具体它的方法先不分析,可以看到参数是需要一个Runnable,而由于SubscribeOnObserver实现了Runnable方法,所以当然可以将它传进去喽:

而它里面的run方法的实现:

也就是说:


此时我们的调用就会在指定的线程来做了,如下:

用图来说明一下这个subscribeOn()的过程:

那如果写多个subscribeOn()呢?

其实最终会用第一行的切线程,图例表示其形态:

而对于subscribeOn()之后的Disposable则为:


也就是subscribeOn()指定的线程是用来决定subscribe()的订阅操作的,那接下来看一下observeOn(),如下:



此时就看一下它的回调中瞅下是否有切线程的东东:


那如果有多个observeOn会有啥结果呢?由于是管下面的,可能会是这样:

那么就有东东可以利用啦,怎么利用?假如再插入一个操作符:

也就是使用observeOn()多次切是会生效的,而不像subscribeOn()使用多次是没啥效果的,那有了observeOn()的灵活性,subscribeOn()是不是可以不用了?当然不行,因为subscribeOn()是管订阅的,需要配合着使用。总的来说:subscribeOn()是先切线程再进行订阅,而observeOn()是先订阅,而每次回调会切线程处理。
接下来再研究一下它们的原理:

首先来瞅一下Schedulers.io(),不过有一个跟它类似的api需要先看它:

先说一下它们俩的区别:
Schedulers.newThread():每次都会新开一个线程。
Schedulers.io():里面用到了线程池,不是每次都新开线程。
好,先来了解下Schedulers.newThread()是做了啥?

直接看NEW_THREAD:




而切换线程在上面的分析中主要是这句代码:

所以咱们来瞅一下它里面有木有scheduleDirect()这个方法,发现木有。。那就肯定是在它的父类找呗,找找:

确实是有:

下面来简单分析一下:

然后核心代码:

点进去瞅一下:

抽象方法,肯定得看子类:


一层套一层,继续:

其实可以看出Scheduler就是包装Worker的东东,而Worker是包装了executor的东东,咱们再回过头来看:


就会调到子类:


再回过头来瞅一下DisposeTask:


好,了解了Schedulers.newThread()机制之后, 就可以来理解Schedulers.io()了,其实会有一个IoScheduler对像,如下:

然后它里面也有一个createWork()方法,瞅一下:


就不往里跟了,很明显确实io()就是带有缓存的,不是每次都new一个线程。
好接下来就看最后一个主线程的scheduler啦:

点击进来:

然后可以大致看一下切换细节:

至此!!关于Rxjava的核心机制原理都已经剖析完毕,还是挺麻烦!!
RxJava 以及 Android 中的通用线程解决方案、并发与线程安全的更多相关文章
- RxJava在Android中使用场景详解
RxJava 系列文章 <一,RxJava create操作符的用法和源码分析> <二,RxJava map操作符用法详解> <三,RxJava flatMap操作符用法 ...
- Android中使用ListView实现分页刷新(线程休眠模拟)
当要显示的数据过多时,为了更好的提升用户感知,在很多APP中都会使用分页刷新显示,比如浏览新闻,向下滑动到当前ListView的最后一条信息(item)时,会提示刷新加载,然后加载更新后的内容.此过程 ...
- Android中使用ListView实现分页刷新(线程休眠模拟)(滑动加载列表)
当要显示的数据过多时,为了更好的提升用户感知,在很多APP中都会使用分页刷新显示,比如浏览新闻,向下滑动到当前ListView的最后一条信息(item)时,会提示刷新加载,然后加载更新后的内容.此过程 ...
- android中ViewHolder通用简洁写法
public class ViewHolder { // I added a generic return type to reduce the casting noise in client ...
- Android中UI线程与后台线程交互设计的5种方法
我想关于这个话题已经有很多前辈讨论过了.今天算是一次学习总结吧. 在android的设计思想中,为了确保用户顺滑的操作体验.一 些耗时的任务不能够在UI线程中运行,像访问网络就属于这类任务.因此我们必 ...
- Android中线程和线程池
我们知道线程是CPU调度的最小单位.在Android中主线程是不能够做耗时操作的,子线程是不能够更新UI的.在Android中,除了Thread外,扮演线程的角色有很多,如AsyncTask,Inte ...
- Android中关于Handler的若干思考
在之前的博文中,讲过一些和Handler有关的知识,例如: Android 多线程----AsyncTask异步任务详解 Android多线程----异步消息处理机制之Handler详解 今天再把Ha ...
- 在Android中使用并发来提高速度和性能
Android框架提供了很实用的异步处理类.然而它们中的大多数在一个单一的后台线程中排队.当你需要多个线程时你是怎么做的? 众所周知,UI更新发生在UI线程(也称为主线程).在主线程中的任何操作都会阻 ...
- 系统剖析Android中的内存泄漏
[转发]作为Android开发人员,我们或多或少都听说过内存泄漏.那么何为内存泄漏,Android中的内存泄漏又是什么样子的呢,本文将简单概括的进行一些总结. 关于内存泄露的定义,我可以理解成这样 没 ...
随机推荐
- Python扫描器-端口扫描
结合渗透测试最常见就是单个域名扫指纹,自动子域名查找.获取所有子域名的IP,自动C段IP查找相同子域名,利用有效IP扫端口. 常见端口库扫描 service_list = { 21:"FTP ...
- console.log()和alert()的区别
一直都是知道console.log()和alert()是有区别的,但是具体有什么区别就不清楚了,后来在权威指南里注意到了说alert()具有侵入性才来查一查两者的具体区别. 查询到的区别: alert ...
- windows下连接mysql提示1044-access denied for root''@'localhost' to database
ERROR 1044 (42000): Access denied for user ''@'localhost' to database 'mydb'. 原因是因为mysql数据库的user表里,存 ...
- QT qml---- loader使用方法
"简洁是智慧的灵魂,冗长是肤浅的藻饰"------------------<哈姆莱特>莎士比亚 Import Statement: import QtQuick 2.5 ...
- QT 头文件之间相互包含会报错:类名不存在
"希望是一个美好的东西! 希望, 这能自己给自己,否则只有无尽的痛苦和迷茫!"---Frank 假设你写了两个类A和B,如果在A.h中有 #include<B.h>; ...
- labelme2coco问题:TypeError: Object of type 'int64' is not JSON serializable
最近在做MaskRCNN 在自己的数据(labelme)转为COCOjson格式遇到问题:TypeError: Object of type 'int64' is not JSON serializa ...
- Pytorch Tensor 常用操作
https://pytorch.org/docs/stable/tensors.html dtype: tessor的数据类型,总共有8种数据类型,其中默认的类型是torch.FloatTensor, ...
- 分布式 ID
[参考文章] Leaf——美团点评分布式ID生成系统 分布式全局唯一ID生成策略 从一次 Snowflake 异常说起 [雪花算法问题] 微服务架构下 机器码如何生成? 如何处理时钟回调问题?
- LinkedList源码解析(JDK8)
ArrayList的增删效率低,但是改查效率高. 而LinkedList正好相反,增删由于不需要移动底层数组数据,其底层是链表实现的,只需要修改链表节点指针,所以效率较高. 而改和查,都需要先定位到目 ...
- docker学习笔记(一)--介绍和基本组成
Docker基本介绍 1.什么是docker docker本身不是容器,是创建容器的工具,是应用容器引擎,将应用程序自动部署到容器的开源引擎. 2.docker的目标特点 简单轻量,快速开发,具备可移 ...