前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Architecting Android with RxJava

Architecting Android with RxJava

作者头像
小鄧子
发布于 2018-08-20 07:11:55
发布于 2018-08-20 07:11:55
52100
代码可运行
举报
运行总次数:0
代码可运行

最近,我抽出了几个晚上的时间,把咖啡和啤酒变成了代码与文字。

引子

三个月以来,我翻译了一些关于RxJava的文章,说实话这些翻译,真的搞得我很头疼,那么现在是时候回来写点什么了。

最近,我在看两本书,《Learning Reactive Programming with Java 8》《RxJava Essentials》,不过,没关系,我已经买到了电子版,我会在文章结尾附上网盘链接和密码,但我还是希望你将文章继续读下去,因为那是文章结尾的事。

其实关于RxJava的文章和消息远不止我们能了解到的,但又拜英语所赐,所以它看起来又没那么多。好在,国内有许多优秀的开发专家hi大头鬼hi

BlackSwift程序亦非猿Drakeet扔物线流火枫林等等在为之做着贡献,以及简直不能更优秀的文章《给 Android 开发者的 RxJava 详解》

但是,现在,我不得不再次做啰嗦一下,RxJava究竟会改变我们什么。

响应式编程Reactive Programming

什么是响应式编程呢?在Java程序中:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int a = 4;
int b = 5;
int c = a + b;
System.out.println(c); // 9
a = 6;
System.out.println(c);
// 9 again, but if 'c' was tracking the changes of 'a' and 'b',
// it would've been 6 + 5 = 11

当我们改变“a”和“b”的值时,“c”并没有改变。换句话说,“a”和“b”的改变并没有响应到“c”。这就是响应式:程序以流的形式,传递数据的改变

那我,我们又为什么需要响应式呢?

以下翻译自《Learning Reactive Programming with Java 8》

10-15年前,对于网站开发来说,最平常的日常工作就是进行维护和缩短响应时间,那么今天,一切程序都应该保证七天二十四小时不间断运行,并且能够极快的做出响应;如果你的网站响应慢或者宕机,那么用户将会对你们真爱一秒变备胎,转而选择其他网站服务。当今的慢意味着不可用甚至是有故障的。如今的互联网是在和大数据打交道,所以我们需要快速的处理数据。 过去的几年中HTTP错误已经不是什么新鲜事了,但是现在,我们不得不进行容错机制,还要提供用户易读以及合理的消息更新。 在过去,我们写简单的桌面应用,但如今我们写能够做出快速响应的Web应用。多数情况下,这些应用要与大量的远程服务器进行数据传递。 如果我们想让自己的软件保持竞争性,就不得不实现这些新需求,所以,换言之就是我们应该这样做:

  • 模块的/动态的:用这种方式,我们就能够拥有一个七天二十四小时的系统了,因为这些模块能够在不停止整个系统的情况下进行脱机和联机。另外,随着系统的不断庞大,还能帮助我们更好地组织应用结构,同时还能管理底层代码。
  • 可扩展的:用这种方式,我们就能够处理大量的数据和用户请求了。
  • 容错性:用这种方式,能够为用户提供稳定的系统。
  • 响应式:这不仅意味着快速,还意味着可用性强。

让我们思考如何实现它:

  • 如果我们的系统是事件驱动型的,那就把它模块化。我们可以将系统分成多个彼此之间通过通知进行交互的微服务/组件/模块。这样,我们就能够以通知为代表,响应系统的数据流了。
  • 可扩展意味着能够应对日益增长的数据,在负载的情况下不会崩溃。
  • 对故障/错误做出及时的响应,能够提高系统的容错性。
  • 响应意味着对能够对用户操作及时的做出反应。

如果应用是事件驱动型的,那么,它就能够解耦成多个自包含组件。这能够帮我们更好的实现扩展性,因为我们总是可以在不停掉或者打断系统的情况下添加新组建或者移除旧组件。如果错误和故障传递给正确的组件,把它们当做通知来处理并作出响应,那么应用能变得更具有容错性和弹性。所以,如果把系统构建成事件驱动型的。我们可以更容易的实现扩展性和容错性,而且一个具有扩展性,低耦合和防错的应用能够快速的响应用户操作。

Reactive Manifesto文档定义了我们刚刚提到的四点响应式准则。每一个响应式系统都应该是消息驱动型(事件驱动型)的。这样它不仅能变得低耦合,而且扩展性和容错性将更高,这就意味着它可靠和具有响应式。 要注意的是,Reactive Manifesto只是描述了一个响应式系统,并不是对响应式编程的定义。当然,你也可以不使用任何响应式类库或者语言,打造一款弹性可扩展,具有消息驱动的响应式应用。 应用程序中数据的变化,以通知的方式传递给正确的Handler。所以,使用响应式构造应用是符遵循Manifesto最简单的方式。

回调地狱

如果你是一个能够时刻保持头脑清醒,逻辑清晰和思维缜密的人,是个Callback高手,善用并且能够用好FutureTask

那么在Android中你的代码可能会频繁的使用async+callbacks,或者service composition+ error handing

那么关于异步回调的逻辑,你会写成这样getData(Callback<T>)、这样Future<T> getData(),还是这样Future<List<T>> getData(),甚至这样Future<List<Future<T>>> getData(),嗷!拜托,我简直不能再举例下去了,这简直就是Callback Hell,这样的程序或许写起来很舒服,但是如何测试和维护呢。

如果哪天你的程序出了问题而必须马上修复,但你却不能马上赶来或者需要别人协助(这在很多公司是很常见的),或者当他人在review你的代码时,那么,是时候拿出这张图了。

然而使用RxJava的操作符,我们可以避免这些烦人甚至糟糕的回调,让结构和思路看起来更清晰,通过组合API,只需要约定最终的结果Observable<T>就行了。

并且scheduler的出现,不仅解放了线程的切换,让UI线程与工作线程间的跳转变得简单,而且,它的API很丰,也提供了很多使用场景的建议,比如,适用计算任务的Schedulers.computation( );处理密集IO任务的Schedulers.io( );以及Schedulers.trampoline( )能够有效避免StackOverflowError,所以非常适合函数的递归调用。好了,我不再举例了,因为官方文档已经给出了很详细的解释了,但是值得一提的是,如果使用Schedulers的工厂方法创建的Worker,一旦任务执行完毕,都应该调用worker.unsubscribe( )方法,然后转向之前定义的Scheduler实例上来。

当然RxJava的出现并不仅仅是为了解决回调地狱的。

这是我通过学习和不断地练习,一路走来很辛苦,总结的一些经验,分享给大家:

1 . error handling

2 . lifecycle changes

3 . caching (roation)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
   @Override public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setRetainInstance(true);

      /*.cache()操作符:
        当第一个subscribe订阅的时候,才会连接原始Observable,缓存事件,重发给后续订阅的subscribe
        值得注意的事,它和使用了.replay()操作符的ConnectableObservable的不同。
        另外,为了避免内存开销,不建议缓存大量事件*/
      cacheObservable = weatherManager.getWeather().cache();
    }

    @Override public void onViewCreated(View view, Bundle savedInstanceState) {
      super.onViewCreated(view, savedInstanceState);
      cacheObservable.subscribe(/*your subscribe*/);
    }

4 . composing multiple calls

5 . more robust interface than asyncTask

6 . easy to do complex threading

7 . functional nature is more expressive

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    /*一个数组,每个元素乘以2,然后筛选小于10的元素,放入集合中*/
    Integer[] integers = { 0, 1, 2, 3, 4, 5 };

    /*一般写法,看上去并不是那么的“函数”*/
    Integer[] doubles = new Integer[integers.length];
    for (int i = 0; i < integers.length; i++) {
      doubles[i] = integers[i] * 2;
    }
    List<Integer> integerList = new ArrayList<>(doubles.length);
    for (Integer integer : doubles) {
      if (integer < 10) integerList.add(integer);
    }


    /*Observable写法,一切都好多了*/
    List<Integer> funactionalList = Observable.from(integers).map(new Func1<Integer, Integer>() {
      @Override public Integer call(Integer integer) {
        return integer * 2;
      }
    }).filter(new Func1<Integer, Boolean>() {
      @Override public Boolean call(Integer integer) {
        return integer < 10;
      }
    }).toList().toBlocking().first();

8 . async unit testing

9 . fluent API

10 . easy debugging

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//值得一提的是,关于@RxLogSubscriber要放在继承自Subscriber的类上
@RxLogSubscriber class MySubscriber extends Subscriber<Void> {
        @Override public void onCompleted() {
        }
        @Override public void onError(Throwable e) {
        }

        @Override public void onNext(Void aVoid) {
        }
      }

//而不是实现Observer接口的类上
@RxLogSubscriber class MySubscriber implements Observer<Void> {
        @Override public void onCompleted() {
        }
        @Override public void onError(Throwable e) {
        }

        @Override public void onNext(Void aVoid) {
        }
      }

当然,随着学习的深入,你会发现,收益不止如此。

在响应式编程中,应该牢记以下两点:

  • everything is a stream(一切皆流)
  • don't break the chain(不要打断链式结构)

谈谈Backpressure

Android这种嵌入式系统,尤其是生产者-消费者(producer-consumer)模式中,一定要小心Backpressure(背压,反压)的出现。一个宽泛的解释就是:事件产生的速度比消费快。一旦发生overproducing,当你的链式结构不能承受数据压力的时候,就会抛出MissingBackpressureException异常。

在Android中最容易出现的Backpressure就是连续快速点击跳转界面、数据库查询、文件扫面、键盘输入,甚至联网等操作都有可能造成Backpressure,可能有些情况并不会导致程序崩溃,但是会造成一些我们不想见到的小麻烦。那么一起来看看如何用RxJava解决Backpressure,OK,让我们的程序变得健壮起来吧。

groupBy操作符

在写这篇文章的时候,刚好看到一段代码,看来有必要说一说这个操作符了。

.groupBy( ),分组操作符,虽然目前这个项目中没有用到,但是我还是蛮喜欢它的,而且我看到很多人在使用,将原始Observable根据不同的key分组成多个GroupedObservable,由原始Observable发射(原始Observable的泛型将变成这样Observable<GroupedObservable<K, T>>),每一个GroupedObservable既是事件本身也是一个独立的Observable,每一个GroupedObservable发射一组原始Observable的事件子集。

引用自:GroupBy中文翻译

注意:groupBy将原始Observable分解为一个发射多个GroupedObservable的Observable,一旦有订阅,每个GroupedObservable就开始缓存数据。因此,如果你忽略这些GroupedObservable中的任何一个,这个缓存可能形成一个潜在的内存泄露。因此,如果你不想观察,也不要忽略GroupedObservable。你应该使用像take(0)这样会丢弃自己的缓存的操作符。 如果你取消订阅一个GroupedObservable,那个Observable将会终止。如果之后原始的Observable又发射了一个与这个Observable的Key匹配的数据,groupBy将会为这个Key创建一个新的GroupedObservable。

那么问题恰恰出在.take(n)操作符上。

只返回前面指定的n项数据,然后发送完成通知,忽略后面的事件。

那么看一下这个例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Observable.just(0, 1, 2, 3, 4, 5).groupBy(new Func1<Integer, Boolean>() {
          @Override public Boolean call(Integer integer) {
            return integer % 2 == 0;
          }
        }).flatMap(new Func1<GroupedObservable<Boolean, Integer>, Observable<Integer>>() {
          @Override
          public Observable<Integer> call(GroupedObservable<Boolean, Integer> groupedObservable) {

            return groupedObservable.getKey() ? groupedObservable.take(1) : groupedObservable;
          }
        }).subscribe(new Action1<Integer>() {
          @Override public void call(Integer i) {
            System.out.println(i);
          }
        });

输出结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
0
1
2
3
4
5

然而在1.0.0-RC5之前的版本中,在GroupedObservable上使用.take(n)操作符将会在发送完n个事件后,对GroupedObservable进行unsubscribe。并且GroupedObservable内部将会记录这个unsubscribed状态,然后忽略后面的事件。所以输出结果将是这样的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
0
1
3
5

而在这之后的版本,使用.take(n)操作符,虽然也会发生unsubscribe,但是当原始Observable再次发送一个满足key的事件后,将会重新创建一个GroupedObservable,然后发送这个GroupedObservable,不会发生之前那样的,忽略后续事件的现象。

当然,不要忘记,对不感兴趣的GroupedObservable使用.take(0),来避免泄露。

所以,我的建议是,在使用RxJava之前看看官方文档或者change log。

关于RxWeather

我尽量减少对这个工程的文字描述。因为代码才是最好的老师。

通过对Android技术栈,1#架构译文)和Android架构演化之路译文)的解读和学习,按照架构和思路进行了实现,并且加入了RxBus。

关于REST API,我选择了和风天气,而放弃了Openweathermap的理由如下:

  1. Openweathermap免费用户所在的服务器不稳定。
  2. 付费方面,和风天气更经济实惠。

但是和风天气目前并不支持同时查询多个地区的天气预报,也不支持根据经纬度查询天气预报。但是以后的事情谁又能说的准呢?

由于应用并不支持动态的上拉加载。所以,所有的列表展示结果,取决于city.txt文件。

我从Openweathermap给出的资源(下载city.list.json)中,整理需要的城市Json字符串,整合了经纬度,以备不时之需。

找到了一个通过Location查询所在地的API。

就这样基本实现了列表展示页ListActivity的功能:

  1. 根据Loaction查询所在地城市名称,然后查询当地天气。
  2. 读取domain->assets->city.txt,然后依次查询每个城市的天气,所以,这个文件不建议放入太多json。
  3. 整合1和2的并发请求结果,显示界面。

详情页DetailActivity通过RxBus发送黏性事件接收列表页传递过来的数据,然后进行展示。这里会有七天内的天气以及穿衣建议。由于我么并没有找到一个正确的算法,所以当进入详情页后,旋转屏幕之后的退出动画会有所不同。这个类涉及的代码大部分都是动画(注意Hardware Layer的使用)以及对屏幕旋转的处理,所以代码看起有点多。ForkView使用了一个简单的自定义Behavior

搜索界面SearchActivity,输入的关键字请不要以市、区结尾,例如,北京而不是北京市,因为API不支持,我也没办法 :( 。

启动页

我认为,出彩的引导页是对细节的重视,但是我实在不能忍受,在启动页等太久。注意:不要混淆这两种场景。

所以,我在看了正确使用启动页之后,决定采取这种方式实现SplashActivity。而且不建议使用大图,一个icon足以。

Code

所有代码都可以从Github上获得。

片尾Tips:

文章开头提到的资料,需要pdf或者kindle版本的请自行选择下载,不得用于商业用途。

Learning Reactive Programming with Java 8 - pdf版,提取密码:2d88。

Learning Reactive Programming with Java 8 - kindle版,提取密码:5nec。

RxJava Essentials - pdf版,提取密码:z3r8。

RxJava Essentials - kindle版,提取密码:l67e。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2015.11.09 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
5分钟快速了解MySQL索引的各种类型
可以用新华字典做类比:如果新华字典中对每个字的详细解释是数据库中表的记录,那么按部首或拼音等排序的目录就是索引,使用它可以让我们快速查找的某一个字详细解释的位置。
万猫学社
2022/04/22
4020
B-Tree索引案例分析
如果将数据放入磁盘中,由于指令的执行速度远远超过磁盘的读写速度,因此控制运行时间的几乎都是磁盘访问次数。那么写一个复杂的程序来将磁盘访问次数降低到一个很小的常数是很有意义的。 B-Tree:所有的数据项都存储在树叶上,每一个叶子节点都包含指向下一个叶子节点的指针,从而方便叶子节点的范围遍历。B-Tree通常意味着所有的值都是按顺序存储的,并且每一个叶子页(每个叶子页包含多个树叶)到根的距离相同,很适合查找范围数据。( InnoDB使用的是B+Tree)
用户7705674
2021/10/07
4430
Mysql实战面试题
B Tree 指的是 Balance Tree,也就是平衡树。平衡树是一颗查找树,并且所有叶子节点位于同一层。
爱撸猫的杰
2019/03/28
1.1K0
Mysql实战面试题
MySQL 的 hash 索引
最常用的索引也就是B-tree索引和Hash索引,且只有Memory,NDB两种引擎支持Hash索引。
JavaEdge
2021/01/08
5.3K0
MySQL索引原理解析及用法
数据库索引是数据库管理系统中一个非常重要的概念,它类似于书籍的目录,可以帮助我们快速定位到数据的位置,从而提高查询效率。MySQL作为最流行的关系型数据库之一,其索引机制和用法尤为重要。
炒香菇的书呆子
2024/12/29
1300
图解MySQL索引--B-Tree(B+Tree)
看了很多关于索引的博客,讲的大同小异。但是始终没有让我明白关于索引的一些概念,如B-Tree索引,Hash索引,唯一索引…或许有很多人和我一样,没搞清楚概念就开始研究B-Tree,B+Tree等结构,导致在面试的时候答非所问!本文中有关存储引擎请查看MySQL存储引擎-InnoDB和MyISAM
DannyHoo
2019/07/02
1.2K0
图解MySQL索引--B-Tree(B+Tree)
面试又给我问到MySQL索引【索引的实现原理】
MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BTree索引,B+Tree索引,哈希索引,全文索引等等,
码农编程进阶笔记
2021/07/20
1.7K0
面试又给我问到MySQL索引【索引的实现原理】
MySQL知识体系(DS整理)
存储引擎(Storage Engine)是数据库管理系统中负责数据存储、检索和管理的核心组件,它决定了:
Him
2025/05/04
2970
B-Tree和B+Tree的比较
我们都知道在 Mysql 中,索引是非常重要的内容,因为他对我们的查询会有非常大的帮助,所以,我们今天就来看看这个 Mysql 的索引。
Java极客技术
2024/03/02
1720
B-Tree和B+Tree的比较
MySQL 哈希索引、空间数据索引、全文索引
哈希索引基于哈希表实现,仅支持精确匹配索引所有列的查询。对于每行数据,存储引擎都会对所有的索引列计算出一个哈希码。哈希索引将所有的哈希码存储在索引中,同时保存指向每个数据行的指针。
很酷的站长
2023/03/08
1.4K0
MySQL 哈希索引、空间数据索引、全文索引
MySQL 索引的类型
索引有很多种类型,为不同的场景提供更好的性能。在MySQL中,索引是在存储引擎层而不是服务器层实现。不同存储引擎的索引其工作方式并不一样。也不是所有存储引擎都支持所有类型的索引。即使多个存储引擎支持同一种类型的索引,其底层实现也可能不同。
Java架构师必看
2021/05/14
1.5K0
MySQL 索引的类型
MySQL 索引及其分类
mysql 索引能够轻易将查询性能提高几个数量级,而一个“最优”索引有时比一个“好的”索引性能要高两个数量级。
用户3147702
2022/06/27
8150
MySQL 索引及其分类
MYSQL基础知识和案例分享
· Mysql 5.1之前默认的存储引擎,支持包括全文索引、压缩、空间函数(GIS)等,不支持事务和行级锁。最大的缺陷是崩溃后无法安全恢复。
曲水流觞
2019/10/27
9580
相关推荐
5分钟快速了解MySQL索引的各种类型
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验