Rxjava实践: 把混乱的WORKFLOW撸成串吧

上个月做的事情比较多:改改iOS bug,学python,把项目重构成MVP,深入使用Rxjava。

这次来说说Rxjava,通过还原一个真实的开发过程,来感受下rxjava的便利之处。

巨坑从来都是由小坑慢慢塌陷的

先来看下一段最普通的代码

rx01.png

在没有特殊需求的情况下,代码就这么简单。你可以理解为,获取一个目录下的所有文件,将它们一个个传到服务器上去。

看起来好像是没什么问题,一个for循环搞定。一个task失败了不影响另一个task。每个task run在一个单独的子线程。

之前rxjava使用场景只局限于和Retrofit一起用。没过多的使用操作符。因此在uploadFile(path)方法中就是最简单的Retrofit+Rxjava上传文件。rxjava就切换了下线程。

对于写惯java的人,这么写是没什么问题的。但如果深入使用过rxjava之后,这么写就非常别扭了。看到for loop了,你不想将它改成Observable.from()嘛?

把能看见的都改成stream吧

getFileList()方法是获取sd卡中data包下所有以loc为后缀的files。

workflow分三步:

  1. locate to data dir
  2. list files under data dir
  3. filter files with .loc suffix

换成rxjava非常容易

  1. 先发射一个data目录路径
  2. 需求是多次上传文件,得用flatMap将data映射成一个Observable<File[]>
    2.1 当然你可以选择直接listFile(filter),但这样回调又套回调,不是很好看。
    2.2 用filter操作符将发射来的File[]过滤

比如像2.1这样写

rx02.png

或者像2.2这样写


rx03.png

注意,在flatMap中又用from()操作符将File[]变换成一个OnSubscribeFromArray类型的Observable在内部通过for循环一个个发射。(感谢一位朋友指正,之前理解错误,以为是发射多个Observable。不看源码真是稀里糊涂的)

假如你的API接口可以接收多个文件,其实也不用这样写。直接在flatMap中拼接RequestBody,调用API请求就可以了。比如像下图这样写:


rx04.png

无奈需求是上传loc文件同时还会再带上一个sensor文件,所以就不能像上述这样写。

产品说:需求变了~

接下来的workflow就很有趣了。现在有了多个Observable<File>,一个个上传就是了。

如果不考虑队列,不考虑无网或上传失败情况。完全再来一个flatMap将Observable<File>变换为Observable<Response<HashMap>>就可以了。比如:


rx05.png

但现在的需求是,队列上传文件,也就是说,必须一个任务完成(成功|失败)后才能进行下一个任务。这样用flatMap就不可以了。(其实后来我考虑过这个问题,线程的调度本质还是由我声明出来的线程池来决定的,如果用Schedulers.newThread(),那就会创建多个子线程。但如果用Schedulers.from(Executors.newSingleThreadExecutor())呢?)

需求总是多变的,好在有rxjava可以随意变换。来吧,我们看看不用单个线程池,如何实现队列。

不能随意套路,坑的是自己

之前学习rxjava时,看过很多在android中高度使用rxjava的文章。有一个操作符很有意思-> concat()

The Concat operator concatenates the output of multiple Observables so that they act like a single Observable, with all of the items emitted by the first Observable being emitted before any of the items emitted by the second Observable (and so forth, if there are more than two).

即将多个Observables串起成一个Observable,直到一个执行完毕后再执行下一个。

我们可以将这个concat()应用在读取缓存还是请求服务器, 如果缓存有数据,那就不用请求服务器了。

Observable<Data> cache;
Observable<Data> server;
Observable.concat(cache, server)
            .first()

这个也可以用在队列上传文件场景上咯。but,concat()是创建型操作符,再次变换就不能使用了。不过可以用concatMap(),

Returns a new Observable that emits items resulting from applying a function that you supply to each item emitted by the source Observable, where that function returns an Observable, and then emitting the items that result from concatenating those resulting Observables.

直接看代码吧

rx06.png

这段写的特别扭,为什么又要在一个Observable里又创建一个retrofit相关的Observable?当时想的是,因为要在upload成功后得删除文件啊。如果把subscribe放到外层去,那接收到的全是服务器response,不知道当前的response属于哪一个file upload。所以我就又写了次变换。(这里肯定可以优化的,写的太挫)

在concatMap中接收到from()发射来的一个Observable,变换成Retrofit请求,当Subscriber标记为onCompleted后再去执行下一个Observable。

到这里还没完,假如无网络又或者服务器异常。在第一个Observable就会失败,此时还需要继续请求吗?很有可能后面的Observable也都不成功。那加个判断吧。concat()可以和first()一起用。concatMap()也是可以的。

rx07.png

If you are only interested in the first item emitted by an Observable, or the first item that meets some criteria, you can filter the Observable with the First operator.

如果first() -> return true; 这样只取到目前的这个Observable,后续的不执行了。

也就是说,只有在上传成功时return false,继续执行下一个Observable。否则就return true停止。

觉醒分割线

我想之前肯定是被concat(cache, db, server).first()整懵逼了,一心去套,才写了上面这么二的代码的。等等,容我换个姿势。

rx10.png

看,对请求结果map变换一次就可以啦,如果成功删除相关文件,不成功就是个异常了。Observable.error()。这样就跳出了concatMap,也就是说,当异常发生时会停止后续的文件上传。这样first()也不需要啦。除非还有其他额外的停止flag要判断。

到这里整个workflow就被rxjava梳理完毕了。是不是很有趣?我们来看下代码全过程。

rx08.png

还剩最后一个问题:线程调度

之前一直都没写线程调度的地方。subscribeOn放在哪里比较好?

需求是:在主线程listFile拿到目录下的所有文件,然后在子线程一个个队列上传文件,执行完毕后再切换到主线程弹dialog告知结果。

这样来说,每个文件上传时不需要切换线程,所以调用retrofit的地方是不需要subscribeOn。如果执意要在uploadTrip()后加上subscribeOn(io),也不是不可以。只是每个上传task都在一个新的线程里执行的。但实际上,我们的文件上传是个队列,完全可以一直在同一个线程里执行。所以我在了flatMap后observeOn(io)。最终执行的log如下图

rx09.png

之前乱尝试,写了两个subscribeOn(),虽然逻辑是对的,但思想上来说是observeOn多次,subscribeOn一次,幸好有位朋友提醒,才改成了上图。

好了,混乱的workflow总算撸成串了。平时看相关文章总觉得很简单,无非就是几个操作符拼接在一起,做了线程切换。不好理解的就是链式思维的转换还有一些操作符:compose transformer等。等到真正应用到项目场景中,着实折腾了不少。比如不用flatMap,改为concatMap。比如线程调度。比如放弃使用retrofit+rxjava套路,重新认识reactive等。

总得来说,当理解了rxjava的链式思维并对一些复杂的逻辑重构之后,还是会爱上的。

参考阅读

  1. 理解操作符,还是看官网最佳ReactiveX
  2. 感谢小鄧子帮忙梳理流程。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,050评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,538评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,673评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,622评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,047评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,974评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,129评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,893评论 0 209
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,654评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,828评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,297评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,619评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,326评论 3 243
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,176评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,975评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,118评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,909评论 2 278

推荐阅读更多精彩内容