├── .DS_Store ├── .gitignore ├── README.md ├── SUMMARY.md ├── book.json ├── chapter1 ├── chapter1.md ├── landing_in_the_java_world_-_netflix_rxjava.md ├── microsoft_reactive_extensions.md ├── summary1.md └── what_is_different_in_rxjava.md ├── chapter2 ├── .DS_Store ├── observable.md ├── rxjava_observer_pattern_toolkit.md ├── subject_observable_observer.md ├── summary2.md ├── the_observer_pattern.md ├── when_do_you_use_the_Observer_pattern.md └── why_observables.md ├── chapter3 ├── a_few_more_examples.md ├── creating_an_observable_from_list.md ├── hello_reactive_world.md ├── our_first_observable.md ├── start_the_engine.md ├── summary3.md └── utils.md ├── chapter4 ├── debounce.md ├── elementat.md ├── filtering_a_sequence.md ├── filtering_observables.md ├── first_and_last.md ├── let's_take_what_we_need.md ├── once_and_only_once.md ├── sampling.md ├── skip_and_skiplast.md ├── summary4.md └── timeout.md ├── chapter5 ├── buffer.md ├── cast.md ├── groupby.md ├── summary5.md ├── the_map_family.md ├── transforming_observables.md └── window.md ├── chapter6 ├── and_then_when.md ├── combinelatest.md ├── combining_observables.md ├── join.md ├── merge.md ├── startwith.md ├── summary6.md ├── switch.md └── zip.md ├── chapter7 ├── avoiding_blocking_io_operations.md ├── executing_a_network_task.md ├── handing_a_long_task.md ├── nonblocking_io_operations.md ├── schedulers.md ├── schedulers_defeating_the_android_mainthread_issue.md ├── strictmode.md ├── subscribeon_and_observeon.md └── summary7.md ├── chapter8 ├── app_structure.md ├── creating_activity_class.md ├── creating_recyclerview_adapter.md ├── rest_in_peace_rxjava_and_retrofit.md ├── retrofit.md ├── summary8.md └── the_project_goal.md ├── config.json ├── cover.jpg ├── cover ├── background.jpg ├── cover.jpg └── logo.png ├── images ├── .DS_Store ├── README.md ├── chapter2_1.png ├── chapter3_1.png ├── chapter3_2.png ├── chapter3_3.png ├── chapter3_4.png ├── chapter3_5.png ├── chapter4_1.png ├── chapter4_10.png ├── chapter4_11.png ├── chapter4_12.png ├── chapter4_13.png ├── chapter4_14.png ├── chapter4_15.png ├── chapter4_2.png ├── chapter4_3.png ├── chapter4_4.png ├── chapter4_5.png ├── chapter4_6.png ├── chapter4_7.png ├── chapter4_8.png ├── chapter4_9.png ├── chapter5_1.png ├── chapter5_10.png ├── chapter5_11.png ├── chapter5_12.png ├── chapter5_13.png ├── chapter5_14.png ├── chapter5_15.png ├── chapter5_2.png ├── chapter5_3.png ├── chapter5_4.png ├── chapter5_5.png ├── chapter5_6.png ├── chapter5_7.png ├── chapter5_8.png ├── chapter5_9.png ├── chapter6_1.png ├── chapter6_10.png ├── chapter6_11.png ├── chapter6_12.png ├── chapter6_13.png ├── chapter6_2.png ├── chapter6_3.png ├── chapter6_4.png ├── chapter6_5.png ├── chapter6_6.png ├── chapter6_7.png ├── chapter6_8.png ├── chapter6_9.png ├── chapter7_1.png ├── chapter7_2.png ├── chapter7_3.png ├── chapter7_4.png ├── chapter7_5.png ├── chapter7_6.png ├── chapter8_1.png ├── chapter8_2.png └── rxjava.jpg ├── rxjava.jpg └── styles └── website.css /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node rules: 2 | ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 3 | .grunt 4 | 5 | ## Dependency directory 6 | ## Commenting this out is preferred by some people, see 7 | ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git 8 | node_modules 9 | 10 | # Book build output 11 | _book 12 | 13 | # eBook build output 14 | *.epub 15 | *.mobi 16 | *.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RxJava Essentials 中文翻译版 2 | ======= 3 | 4 | ![](images/rxjava.jpg) 5 | 6 | 本书是对Ivan.Morgillo所写一书的中文翻译版本,仅供交流学习使用,严禁商业用途。另外推荐一本姊妹篇《Learning Reactive Programming》。 7 | 8 | * 《RxJava Essentials》[翻译中文版电子书](https://www.gitbook.com/book/yuxingxin/rxjava-essentials-cn/) 9 | 10 | * 《RxJava Essentials》一书作者[代码样例](https://github.com/hamen/rxjava-essentials) 11 | 12 | --- 13 | 14 | # 本书内容有 15 | 16 | ### **1.RX-from .NET to RxJava** 17 | 18 | > 本章带你进入reactive的世界。我们会比较reactive 方法和传统方法,进而探索它们之间的相似和不同的地方。 19 | 20 | ### **2.Why Observables?** 21 | 22 | > 本章会对观察者模式做一个概述,如何实现它以及怎样用RxJava来进行扩展,被观察者是什么,以及被观察者如何与迭代联系到一起的。 23 | 24 | ### **3.Hello Reactive World** 25 | 26 | > 本章会利用我们所学的知识来创建第一个reactive Android应用。 27 | 28 | ### **4.Filtering Observables** 29 | 30 | > 本章我们会研究Observable序列的本质:filtering.我们也将学到如何从一个发出的Observable中选取我们想要的值,如何获得一个有限的数值,如何处理溢出的场景,以及更多有用的技巧。 31 | 32 | ### **5.Transforming Observables** 33 | 34 | > 本章将讲述如何通过变换Observable序列来创建出我们所需要的序列。 35 | 36 | ### **6.Combining Observables** 37 | 38 | > 本章将研究与函数结合,同时也会学到当创建我们想要的Observable时又如何与多个Observable协同工作。 39 | 40 | ### **7.Schedulers-Defeating the Android MainThread Issue** 41 | 42 | > 本章将介绍如何使用RxJava Schedulers 来处理多线程和并发编程。我们也将用reactive的方式来创建网络操作、内存访问、耗时处理。 43 | 44 | ### **8.REST in peace-RxJava and Retrofit** 45 | 46 | > 本章教会你如何让Square公司的Retrofit和RxJava结合来一起使用,来创建一个更高效的REST客户端程序。 47 | 48 | # 学习这本书你需要做的: 49 | 50 | 为了能够运行书中的例子,你需要一个标准的Android开发环境: 51 | 52 | * Android Studio 或 Intellij IDEA 53 | * Android SDK 54 | * Java SDK 55 | 56 | 作为一个纯粹的Java开发者,当你接触RxJava时,很明显你需要一个你喜欢Java编辑器和一个标准的Java JDK 环境。这本书中的一些图表来自http://rxmarbles.com 和 http://reactivex.io。 57 | 58 | # 这本书适合哪些人看 59 | 60 | 如果你是一名有经验的Java开发者,reactive编程将会在后端系统中给你一种新的学习扩展和并发的方式,而这不需要更换开发语言。这本书将帮助你学习RxJava的核心方面,也能帮助你克服Android平台局限性从而创建一个基于事件驱动的,响应式的,流畅体验的Android应用。 61 | 62 | # 一些约定 63 | 64 | 在这本书中,你会发现许多用来区分不同信息的文本样式,这列举这些样式的一些例子和对他们释义的说明。 65 | 66 | 以下列举了些文本中的代码、数据库表名、文件夹名、文件名、文件扩展名、路径名、伪造的URL、用户输入、Twitter handles :“正如你看到的那样:zip()有三个参数:两个Observable和一个Func2” 67 | 68 | 如下面的一块代码: 69 | 70 | ```java 71 | public Observable> getMostPopularSOusers(int howmany){ 72 | return mStackExchangeService 73 | .getMostPopularSOusers(howmany) 74 | .map(UsersResponse::getUsers) 75 | .subscribeOn(Schedulers.io()) 76 | .observeOn(AndroidSchedulers.mainThread()); 77 | } 78 | ``` 79 | 80 | 当我们想对代码块的某一部分引起你的注意时,会在对应的那一行或列设置为粗体 81 | 82 | ```java 83 | public Observable> getMostPopularSOusers(int howmany){ 84 | return mStackExchangeService 85 | .getMostPopularSOusers(howmany) 86 | .map(UsersResponse::getUsers) //也就是这句加粗显示 87 | .subscribeOn(Schedulers.io()) 88 | .observeOn(AndroidSchedulers.mainThread()); 89 | } 90 | ``` 91 | 92 | **新的项目**和**重要的词语**都会以粗体显示。你在屏幕看到的字,例如在菜单或者对话框,会以类似这样的形式出现在文本中:“We will just need a fancy progress bar and a **DOWNLOAD** button. 93 | 94 | **Note** 95 | 96 | 类似这样的是警告或者出现在框中的重要提示。 97 | 98 | **Tip** 99 | 类似这样的是提示和技巧 100 | 101 | # 读者反馈 102 | 103 | 发送邮件到 feedback@packtpub.com 在你的邮件主题中要提到书的标题。 104 | 105 | 如果你有擅长的话题并且你对写作感兴趣或者想出书的话,可以看我们作者指南:http://www.packtpub.com/authors 106 | 107 | # 下载样例代码 108 | 109 | 你可以从你在http://www.packtpub.com的账户中下载所有你购买Packt 出版的图书的样例代码,如果你从别处购买这本书的话,你可以访问:http://www.packtpub.com/support 注册并将文件用附件直接发给你。 110 | 111 | ## 版权说明 112 | 113 | RxJava Essentials 中文翻译版 仅供交流学习使用,严禁商业用途。转载请联系作者[yuxingxin](https://github.com/yuxingxin)。 -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [说明](README.md) 4 | * [RX - 从 .NET 到 RxJava](/chapter1/chapter1.md) 5 | * [微软响应式扩展](/chapter1/microsoft_reactive_extensions.md) 6 | * [来到Java世界 - Netflix RxJava](/chapter1/landing_in_the_java_world_-_netflix_rxjava.md) 7 | * [RxJava的与众不同之处](/chapter1/what_is_different_in_rxjava.md) 8 | * [总结](/chapter1/summary1.md) 9 | * [为什么是Observables?](/chapter2/why_observables.md) 10 | * [观察者模式](/chapter2/the_observer_pattern.md) 11 | * [你什么时候使用观察者模式?](/chapter2/when_do_you_use_the_Observer_pattern.md) 12 | * [RxJava观察者模式工具包](/chapter2/rxjava_observer_pattern_toolkit.md) 13 | * [Observable](/chapter2/observable.md) 14 | * [Subject = Observable + Observer](/chapter2/subject_observable_observer.md) 15 | * [总结](/chapter2/summary2.md) 16 | * [向响应式世界问好](/chapter3/hello_reactive_world.md) 17 | * [启动引擎](/chapter3/start_the_engine.md) 18 | * [工具](/chapter3/utils.md) 19 | * [我们的第一个Observable](/chapter3/our_first_observable.md) 20 | * [从列表创建一个Observable](/chapter3/creating_an_observable_from_list.md) 21 | * [再多几个例子](/chapter3/a_few_more_examples.md) 22 | * [总结](/chapter3/summary3.md) 23 | * [过滤Observables](/chapter4/filtering_observables.md) 24 | * [过滤序列](/chapter4/filtering_a_sequence.md) 25 | * [获取我们需要的数据](/chapter4/let's_take_what_we_need.md) 26 | * [有且仅有一次](/chapter4/once_and_only_once.md) 27 | * [First and last](/chapter4/first_and_last.md) 28 | * [Skip and SkipLast](/chapter4/skip_and_skiplast.md) 29 | * [ElementAt](/chapter4/elementat.md) 30 | * [Sampling](/chapter4/sampling.md) 31 | * [Timeout](/chapter4/timeout.md) 32 | * [Debounce](/chapter4/debounce.md) 33 | * [总结](/chapter4/summary4.md) 34 | * [变换Observables](/chapter5/transforming_observables.md) 35 | * [*map家族](/chapter5/the_map_family.md) 36 | * [GroupBy](/chapter5/groupby.md) 37 | * [Buffer](/chapter5/buffer.md) 38 | * [Window](/chapter5/window.md) 39 | * [Cast](/chapter5/cast.md) 40 | * [总结](/chapter5/summary5.md) 41 | * [组合Observables](/chapter6/combining_observables.md) 42 | * [Merge](/chapter6/merge.md) 43 | * [Zip](/chapter6/zip.md) 44 | * [Join](/chapter6/join.md) 45 | * [combineLatest](/chapter6/combinelatest.md) 46 | * [And,Then和When](/chapter6/and_then_when.md) 47 | * [Switch](/chapter6/switch.md) 48 | * [StartWith](/chapter6/startwith.md) 49 | * [总结](/chapter6/summary6.md) 50 | * [Schedulers-解决Android主线程问题](/chapter7/schedulers_defeating_the_android_mainthread_issue.md) 51 | * [StrictMode](/chapter7/strictmode.md) 52 | * [避免阻塞I/O的操作](/chapter7/avoiding_blocking_io_operations.md) 53 | * [Schedulers](/chapter7/schedulers.md) 54 | * [非阻塞I/O操作](/chapter7/nonblocking_io_operations.md) 55 | * [SubscribeOn and ObserveOn](/chapter7/subscribeon_and_observeon.md) 56 | * [处理耗时的任务](/chapter7/handing_a_long_task.md) 57 | * [执行网络任务](/chapter7/executing_a_network_task.md) 58 | * [总结](/chapter7/summary7.md) 59 | * [与REST无缝结合-RxJava和Retrofit](/chapter8/rest_in_peace_rxjava_and_retrofit.md) 60 | * [项目目标](/chapter8/the_project_goal.md) 61 | * [Retrofit](/chapter8/retrofit.md) 62 | * [App架构](/chapter8/app_structure.md) 63 | * [创建Activity类](/chapter8/creating_activity_class.md) 64 | * [创建RecyclerView Adapter](/chapter8/creating_recyclerview_adapter.md) 65 | * [总结](/chapter8/summary8.md) 66 | 67 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "disqus" 4 | ], 5 | "pluginsConfig": { 6 | "disqus": { 7 | "shortName": "rxjavaessential" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /chapter1/chapter1.md: -------------------------------------------------------------------------------- 1 | # RX - 从.NET到RxJava 2 | 3 | 响应式编程是一种基于异步数据流概念的编程模式。数据流就像一条河:它可以被观测,被过滤,被操作,或者为新的消费者与另外一条流合并为一条新的流。 4 | 5 | 响应式编程的一个关键概念是事件。事件可以被等待,可以触发过程,也可以触发其它事件。事件是唯一的以合适的方式将我们的现实世界映射到我们的软件中:如果屋里太热了我们就打开一扇窗户。同样的,当我们更改电子表(变化的传播)中的一些数值时,我们需要更新整个表格或者我们的机器人碰到墙时会转弯(响应事件)。 6 | 7 | 今天,响应式编程最通用的一个场景是UI:我们的移动App必须做出对网络调用、用户触摸输入和系统弹框的响应。在这个世界上,软件之所以是事件驱动并响应的是因为现实生活也是如此。 -------------------------------------------------------------------------------- /chapter1/landing_in_the_java_world_-_netflix_rxjava.md: -------------------------------------------------------------------------------- 1 | # 来到Java世界 - Netflix RxJava 2 | 3 | Netflix在2012年开始意识到他们的架构要满足他们庞大的用户群体已经变得步履维艰。因此他们决定重新设计架构来减少REST调用的次数。取代几十次的REST调用,而是让客户端自己处理需要的数据,他们决定基于客户端需求创建一个专门优化过的REST调用。 4 | 5 | 为了实现这一目标,他们决定尝试响应式,开始将.NET Rx迁移到JVM上面。他们不想只基于Java语言;而是整个JVM,从而有可能为市场上的每一种基于JVM的语言:如Java、Clojure、Groovy、Scala等等提供一种新的工具。 6 | 7 | 2013年二月份,Ben Christensen 和 Jafar Husain发在Netflix技术博客的一篇文章第一次向世界展示了RxJava。 8 | 9 | 主要特点有: 10 | 11 | * 易于并发从而更好的利用服务器的能力。 12 | * 易于有条件的异步执行。 13 | * 一种更好的方式来避免回调地狱。 14 | * 一种响应式方法。 15 | 16 | 17 | 正如.NET,RxJava Observable 是push 迭代的等价体,即pull。pull方法是阻塞并等待的方法:消费者从源头pull值,并阻塞线程直到生产者提供新的值。 18 | 19 | push方法作用于订阅和响应:消费者订阅新值的发射,当它们可用时生产者push这些新值并通知消费者。在这一点上,消费者消费了它们。push方法很明显更灵活,因为从逻辑和实践的观点来看,开发者只需忽略他需要的数据是来自同步还是异步;他的代码将仍然起作用。 20 | -------------------------------------------------------------------------------- /chapter1/microsoft_reactive_extensions.md: -------------------------------------------------------------------------------- 1 | # 微软响应式扩展 2 | 3 | 函数响应式编程是一个来自90年代后期受微软的一名计算机科学家Erik Meijer启发的思想,用来设计和开发微软的Rx库。 4 | 5 | Rx 是微软.NET的一个响应式扩展。Rx借助可观测的序列提供一种简单的方式来创建异步的,基于事件驱动的程序。开发者可以使用Observables模拟异步数据流,使用LINQ语法查询Observables,并且很容易管理调度器的并发。 6 | 7 | Rx让众所周知的概念变得易于实现和消费,例如**push方法**。在响应式的世界里,我们不能假装作用户不关注或者是不抱怨它而一味的等待函数的返回结果,网络调用,或者数据库查询的返回结果。我们时刻都在等待某些东西,这就让我们失去了并行处理其他事情的机会,提供更好的用户体验,让我们的软件免受顺序链的影响,而阻塞编程。 8 | 9 | 下表列出的与.NET 枚举相关的.NET Observable 10 | 11 | | .NET Observable| 一个返回值| 多个返回值 | 12 | | ------------- |:-------------:| -----:| 13 | | Pull/Synchronous/Interactive|`T`| `IEnumerable` | 14 | | Push/Asynchronous/Reactive| `Task`|`IObservable`| 15 | 16 | push方法把这个问题逆转了:取而代之的是不再等待结果,开发者只是简单的请求结果,而当它返回时得到一个通知即可。开发者对即将发生的事件提供一个清晰的响应链。对于每一个事件,开发者都作出相应的响应;例如,用户被要求登录的时候,提交一个携带他的用户名和密码的表单。应用程序执行登录的网络请求,接下来将要发生的情况有: 17 | 18 | * 显示一个成功的信息,并保存用户的个人信息。 19 | * 显示一个错误的信息 20 | 21 | 正如你用push方法所看到的,开发者不需要等待结果。而是在结果返回时通知他。在这期间,他可以做他想做的任何事情: 22 | 23 | * 显示一个进度对话框 24 | * 为下次登录保存用户名和密码 25 | * 预加载一些他认为登录成功后需要耗时处理的事情 -------------------------------------------------------------------------------- /chapter1/summary1.md: -------------------------------------------------------------------------------- 1 | # 总结 2 | 3 | 本章中,我们初步探索了响应式的世界。从微软的.NET到Netflix的RxJava,我们了解了Rx是如何诞生的,我们也了解到传统的方法与响应式方法相比之间的相似和不同。 4 | 5 | 下一章,我们将学习到Observables是什么,以及如何创建它并把响应式编程应用到我们的日常编码中去。 -------------------------------------------------------------------------------- /chapter1/what_is_different_in_rxjava.md: -------------------------------------------------------------------------------- 1 | # RxJava的与众不同之处 2 | 3 | 从纯Java的观点看,RxJava Observable类源自于经典的Gang Of Four的观察者模式。 4 | 5 | 它添加了三个缺少的功能: 6 | 7 | * 生产者在没有更多数据可用时能够发出信号通知:onCompleted()事件。 8 | * 生产者在发生错误时能够发出信号通知:onError()事件。 9 | * RxJava Observables 能够组合而不是嵌套,从而避免开发者陷入回调地狱。 10 | 11 | 12 | Observables和Iterables共用一个相似的API:我们在Iterable可以执行的许多操作也都同样可以在Observables上执行。当然,由于Observables流的本质,没有如Iterable.remove()这样相应的方法。 13 | 14 | | Pattern| 一个返回值| 多个返回值 | 15 | | ------------- |:-------------:| -----:| 16 | | Synchronous|`T getData()`| `Iterable` | 17 | | Asynchronous| `Future getData()`|`Observable getData()`| 18 | 19 | 从语义的角度来看,RxJava就是.NET Rx。从语法的角度来看,Netflix考虑到了对应每个Rx方法,保留了Java代码规范和基本的模式。 20 | -------------------------------------------------------------------------------- /chapter2/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/chapter2/.DS_Store -------------------------------------------------------------------------------- /chapter2/observable.md: -------------------------------------------------------------------------------- 1 | # Observable 2 | 3 | 当我们异步执行一些复杂的事情,Java提供了传统的类,例如Thread、Future、FutureTask、CompletableFuture来处理这些问题。当复杂度提升,这些方案就会变得麻烦和难以维护。最糟糕的是,它们都不支持链式调用。 4 | 5 | RxJava Observables被设计用来解决这些问题。它们灵活,且易于使用,也可以链式调用,并且可以作用于单个结果程序上,更有甚者,也可以作用于序列上。无论何时你想发射单个标量值,或者一连串值,甚至是无穷个数值流,你都可以使用Observable。 6 | 7 | Observable的生命周期包含了三种可能的易于与Iterable生命周期事件相比较的事件,下表展示了如何将Observable async/push 与 Iterable sync/pull相关联起来。 8 | 9 | | Event| Iterable(pull)|Observable(push)| 10 | | ------------- |:-------------:| -----:| 11 | | 检索数据|`T next()`| `onNext(T)` | 12 | | 发现错误| `throws Exception`|`onError(Throwable)`| 13 | | 完成 |`!hasNext()`|`onCompleted()`| 14 | 15 | 使用Iterable时,消费者从生产者那里以同步的方式得到值,在这些值得到之前线程处于阻塞状态。相反,使用Observable时,生产者以异步的方式把值推给观察者,无论何时,这些值都是可用的。这种方法之所以更灵活是因为即便值是同步或异步方式到达,消费者在这两种场景都可以根据自己的需要来处理。 16 | 17 | 为了更好地复用Iterable接口,RxJava Observable类扩展了GOF观察者模式的语义。引入了两个新的接口: 18 | * onCompleted() 即通知观察者Observable没有更多的数据。 19 | * onError() 即观察者有错误出现了。 20 | 21 | 22 | ### 热Observables和冷Observables 23 | 24 | 从发射物的角度来看,有两种不同的Observables:热的和冷的。一个"热"的Observable典型的只要一创建完就开始发射数据,因此所有后续订阅它的观察者可能从序列中间的某个位置开始接受数据(有一些数据错过了)。一个"冷"的Observable会一直等待,直到有观察者订阅它才开始发射数据,因此这个观察者可以确保会收到整个数据序列。 25 | 26 | ### 创建一个Observable 27 | 28 | 在接下来的小节中将讨论Observables提供的两种创建Observable的方法。 29 | 30 | #### Observable.create() 31 | 32 | create()方法使开发者有能力从头开始创建一个Observable。它需要一个OnSubscribe对象,这个对象继承Action1,当观察者订阅我们的Observable时,它作为一个参数传入并执行call()函数。 33 | ```java 34 | Observable.create(new Observable.OnSubscribe(){ 35 | @Override 36 | public void call(Subscriber subscriber) { 37 | 38 | } 39 | }); 40 | ``` 41 | Observable通过使用subscriber变量并根据条件调用它的方法来和观察者通信。让我们看一个“现实世界”的例子: 42 | ```java 43 | Observable observableString = Observable.create(new Observable.OnSubscribe() { 44 | @Override 45 | public void call(Subscriber observer) { 46 | for (int i = 0; i < 5; i++) { 47 | observer.onNext(i); 48 | } 49 | observer.onCompleted(); 50 | } 51 | }); 52 | 53 | Subscription subscriptionPrint = observableString.subscribe(new Observer() { 54 | @Override 55 | public void onCompleted() { 56 | System.out.println("Observable completed"); 57 | } 58 | 59 | @Override 60 | public void onError(Throwable e) { 61 | System.out.println("Oh,no! Something wrong happened!"); 62 | } 63 | 64 | @Override 65 | public void onNext(Integer item) { 66 | System.out.println("Item is " + item); 67 | } 68 | }); 69 | ``` 70 | 例子故意写的简单,是因为即便是你第一次见到RxJava的操作,我想让你明白接下来要发生什么。 71 | 72 | 我们创建一个新的`Observable`,它执行了5个元素的for循环,一个接一个的发射他们,最后完成。 73 | 74 | 另一方面,我们订阅了Observable,返回一个Subscription 75 | 。一旦我们订阅了,我们就开始接受整数,并一个接一个的打印出它们。我们并不知道要接受多少整数。事实上,我们也无需知道是因为我们为每种场景都提供对应的处理操作: 76 | * 如果我们接收到了整数,那么就打印它。 77 | * 如果序列结束,我们就打印一个关闭的序列信息。 78 | * 如果错误发生了,我们就打印一个错误信息。 79 | 80 | #### Observable.from() 81 | 82 | 在上一个例子中,我们创建了一个整数序列并一个一个的发射它们。假如我们已经有一个列表呢?我们是不是可以不用for循环而也可以一个接一个的发射它们呢? 83 | 84 | 在下面的例子代码中,我们从一个已有的列表中创建一个Observable序列: 85 | ```java 86 | List items = new ArrayList(); 87 | items.add(1); 88 | items.add(10); 89 | items.add(100); 90 | items.add(200); 91 | 92 | Observable observableString = Observable.from(items); 93 | Subscription subscriptionPrint = observableString.subscribe(new Observer() { 94 | @Override 95 | public void onCompleted() { 96 | System.out.println("Observable completed"); 97 | } 98 | 99 | @Override 100 | public void onError(Throwable e) { 101 | System.out.println("Oh,no! Something wrong happened!"); 102 | } 103 | 104 | @Override 105 | public void onNext(Integer item) { 106 | System.out.println("Item is " + item); 107 | } 108 | }); 109 | ``` 110 | 111 | 输出的结果和上面的例子绝对是一样的。 112 | 113 | `from()`创建符可以从一个列表/数组来创建Observable,并一个接一个的从列表/数组中发射出来每一个对象,或者也可以从Java `Future`类来创建Observable,并发射Future对象的`.get()`方法返回的结果值。传入`Future`作为参数时,我们可以指定一个超时的值。Observable将等待来自`Future`的结果;如果在超时之前仍然没有结果返回,Observable将会触发`onError()`方法通知观察者有错误发生了。 114 | 115 | #### Observable.just() 116 | 117 | 如果我们已经有了一个传统的Java函数,我们想把它转变为一个Observable又改怎么办呢?我们可以用`create()`方法,正如我们先前看到的,或者我们也可以像下面那样使用以此来省去许多模板代码: 118 | ```java 119 | Observable observableString = Observable.just(helloWorld()); 120 | 121 | Subscription subscriptionPrint = observableString.subscribe(new Observer() { 122 | @Override 123 | public void onCompleted() { 124 | System.out.println("Observable completed"); 125 | } 126 | 127 | @Override 128 | public void onError(Throwable e) { 129 | System.out.println("Oh,no! Something wrong happened!"); 130 | } 131 | 132 | @Override 133 | public void onNext(String message) { 134 | System.out.println(message); 135 | } 136 | }); 137 | ``` 138 | 139 | `helloWorld()`方法比较简单,像这样: 140 | ```java 141 | private String helloWorld(){ 142 | return "Hello World"; 143 | } 144 | ``` 145 | 146 | 不管怎样,它可以是我们想要的任何函数。在刚才的例子中,我们一旦创建了Observable,`just()`执行函数,当我们订阅Observable时,它就会发射出返回的值。 147 | 148 | `just()`方法可以传入一到九个参数,它们会按照传入的参数的顺序来发射它们。`just()`方法也可以接受列表或数组,就像`from()`方法,但是它不会迭代列表发射每个值,它将会发射整个列表。通常,当我们想发射一组已经定义好的值时会用到它。但是如果我们的函数不是时变性的,我们可以用just来创建一个更有组织性和可测性的代码库。 149 | 150 | 最后注意`just()`创建符,它发射出值后,Observable正常结束,在上面那个例子中,我们会在控制台打印出两条信息:“Hello World”和“Observable completed”。 151 | 152 | #### Observable.empty(),Observable.never(),和Observable.throw() 153 | 154 | 当我们需要一个Observable毫无理由的不再发射数据正常结束时,我们可以使用`empty()`。我们可以使用`never()`创建一个不发射数据并且也永远不会结束的Observable。我们也可以使用`throw()`创建一个不发射数据并且以错误结束的Observable。 155 | 156 | 157 | -------------------------------------------------------------------------------- /chapter2/rxjava_observer_pattern_toolkit.md: -------------------------------------------------------------------------------- 1 | # RxJava观察者模式工具包 2 | 3 | 在RxJava的世界里,我们有四种角色: 4 | * Observable 5 | * Observer 6 | * Subscriber 7 | * Subjects 8 | 9 | Observables和Subjects是两个“生产”实体,Observers和Subscribers是两个“消费”实体。 -------------------------------------------------------------------------------- /chapter2/subject_observable_observer.md: -------------------------------------------------------------------------------- 1 | # Subject = Observable + Observer 2 | 3 | `subject`是一个神奇的对象,它可以是一个Observable同时也可以是一个Observer:它作为连接这两个世界的一座桥梁。一个Subject可以订阅一个Observable,就像一个观察者,并且它可以发射新的数据,或者传递它接受到的数据,就像一个Observable。很明显,作为一个Observable,观察者们或者其它Subject都可以订阅它。 4 | 5 | 一旦Subject订阅了Observable,它将会触发Observable开始发射。如果原始的Observable是“冷”的,这将会对订阅一个“热”的Observable变量产生影响。 6 | 7 | RxJava提供四种不同的Subject: 8 | * PublishSubject 9 | * BehaviorSubject 10 | * ReplaySubject. 11 | * AsyncSubject 12 | 13 | ### PublishSubject 14 | 15 | Publish是Subject的一个基础子类。让我们看看用PublishSubject实现传统的Observable `Hello World`: 16 | ```java 17 | PublishSubject stringPublishSubject = PublishSubject.create(); 18 | Subscription subscriptionPrint = stringPublishSubject.subscribe(new Observer() { 19 | @Override 20 | public void onCompleted() { 21 | System.out.println("Observable completed"); 22 | } 23 | 24 | @Override 25 | public void onError(Throwable e) { 26 | System.out.println("Oh,no!Something wrong happened!"); 27 | } 28 | 29 | @Override 30 | public void onNext(String message) { 31 | System.out.println(message); 32 | } 33 | }); 34 | stringPublishSubject.onNext("Hello World"); 35 | ``` 36 | 37 | 在刚才的例子中,我们创建了一个`PublishSubject`,用`create()`方法发射一个`String`值,然后我们订阅了PublishSubject。此时,没有数据要发送,因此我们的观察者只能等待,没有阻塞线程,也没有消耗资源。就在这随时准备从subject接收值,如果subject没有发射值那么我们的观察者就会一直在等待。再次声明的是,无需担心:观察者知道在每个场景中该做什么,我们不用担心什么时候是因为它是响应式的:系统会响应。我们并不关心它什么时候响应。我们只关心它响应时该做什么。 38 | 39 | 最后一行代码展示了手动发射字符串“Hello World”,它触发了观察者的`onNext()`方法,让我们在控制台打印出“Hello World”信息。 40 | 41 | 让我们看一个更复杂的例子。话说我们有一个`private`声明的Observable,外部不能访问。Observable在它生命周期内发射值,我们不用关心这些值,我们只关心他们的结束。 42 | 43 | 首先,我们创建一个新的PublishSubject来响应它的`onNext()`方法,并且外部也可以访问它。 44 | 45 | ```java 46 | final PublishSubject subject = PublishSubject.create(); 47 | 48 | subject.subscribe(new Observer() { 49 | @Override 50 | public void onCompleted() { 51 | 52 | } 53 | 54 | @Override 55 | public void onError(Throwable e) { 56 | 57 | } 58 | 59 | @Override 60 | public void onNext(Boolean aBoolean) { 61 | System.out.println("Observable Completed"); 62 | } 63 | }); 64 | ``` 65 | 然后,我们创建“私有”的Observable,只有subject才可以访问的到。 66 | ```java 67 | Observable.create(new Observable.OnSubscribe() { 68 | @Override 69 | public void call(Subscriber subscriber) { 70 | for (int i = 0; i < 5; i++) { 71 | subscriber.onNext(i); 72 | } 73 | subscriber.onCompleted(); 74 | } 75 | }).doOnCompleted(new Action0() { 76 | @Override 77 | public void call() { 78 | subject.onNext(true); 79 | } 80 | }).subscribe(); 81 | ``` 82 | `Observable.create()`方法包含了我们熟悉的for循环,发射数字。`doOnCompleted()`方法指定当Observable结束时要做什么事情:在subject上发射true。最后,我们订阅了Observable。很明显,空的`subscribe()`调用仅仅是为了开启Observable,而不用管已发出的任何值,也不用管完成事件或者错误事件。为了这个例子我们需要它像这样。 83 | 84 | 在这个例子中,我们创建了一个可以连接Observables并且同时可被观测的实体。当我们想为公共资源创建独立、抽象或更易观测的点时,这是极其有用的。 85 | 86 | ### BehaviorSubject 87 | 88 | 简单的说,BehaviorSubject会首先向他的订阅者发送截至订阅前最新的一个数据对象(或初始值),然后正常发送订阅后的数据流。 89 | 90 | ```java 91 | BehaviorSubject behaviorSubject = BehaviorSubject.create(1); 92 | ``` 93 | 在这个短例子中,我们创建了一个能发射整形(Integer)的BehaviorSubject。由于每当Observes订阅它时就会发射最新的数据,所以它需要一个初始值。 94 | ### ReplaySubject 95 | 96 | ReplaySubject会缓存它所订阅的所有数据,向任意一个订阅它的观察者重发: 97 | ```java 98 | ReplaySubject replaySubject = ReplaySubject.create(); 99 | ``` 100 | 101 | ### AsyncSubject 102 | 103 | 当Observable完成时AsyncSubject只会发布最后一个数据给已经订阅的每一个观察者。 104 | 105 | ```java 106 | AsyncSubject asyncSubject = AsyncSubject.create(); 107 | ``` -------------------------------------------------------------------------------- /chapter2/summary2.md: -------------------------------------------------------------------------------- 1 | # 总结 2 | 3 | 本章中,我们了解到了什么是观察者模式,为什么Observables在今天的编程场景中如此重要,以及如何创建Observables和subjects。 4 | 5 | 下一章中,我们将创建第一个基于RxJava的Android应用程序,学习如何检索数据来填充listview,以及探索如何创建一个基于RxJava的响应式UI。 -------------------------------------------------------------------------------- /chapter2/the_observer_pattern.md: -------------------------------------------------------------------------------- 1 | # 观察者模式 2 | 3 | 在今天,观察者模式是出现的最常用的软件设计模式之一。它基于subject这个概念。subject是一种特殊对象,当它改变时,那些由它保存的一系列对象将会得到通知。而这一系列对象被称作Observers,它们会对外暴漏了一个通知方法,当subject状态发生变化时会调用的这个方法。 4 | 5 | 在上一章中,我们看到了电子表单的例子。现在我们可以展开这个例子讲,展示一个更复杂的场景。让我们考虑这样一个填着账户数据的电子表单。我们可以把这些数据比作一张表,或者是3D柱状图,或者是饼状图。它们中每一个代表的意义都取决于同一组要展示的数据。每一个都是一个观察者,都依赖于那一个subject,维护着全部信息。 6 | 7 | 3D柱状图这个类、饼状图类、表这个类以及维护这些数据的类是完全解耦的:它们彼此相互独立复用,但也能协同工作。这些表示类彼此不清楚对方,但是正如它们所做的:它们知道在哪能找到它们需要展示的信息,它们也知道一旦数据发生变化就通知需要更新数据表示的那个类。 8 | 9 | 这有一张图描述了Subject/Observer的关系是怎样的一对多的关系: 10 | 11 | ![](../images/chapter2_1.png) 12 | 13 | 上面这张图展示了一个Subject为3个Observers提供服务。很明显,没有理由去限制Observers的数量:如果有需要,一个Subject可以有无限多个Observers,当subject状态发生变化时,这些Observers中的每一个都会收到通知。 -------------------------------------------------------------------------------- /chapter2/when_do_you_use_the_Observer_pattern.md: -------------------------------------------------------------------------------- 1 | # 你什么时候使用观察者模式? 2 | 3 | 观察者模式很适合下面这些场景中的任何一个: 4 | 5 | * 当你的架构有两个实体类,一个依赖另一个,你想让它们互不影响或者是独立复用它们时。 6 | * 当一个变化的对象通知那些与它自身变化相关联的未知数量的对象时。 7 | * 当一个变化的对象通知那些无需推断具体是谁的对象时。 8 | -------------------------------------------------------------------------------- /chapter2/why_observables.md: -------------------------------------------------------------------------------- 1 | # 为什么是Observables? 2 | 3 | 在面向对象的架构中,开发者致力于创建一组解耦的实体。这样的话,实体就可以在不用妨碍整个系统的情况下可以被测试、复用和维护。设计这种系统就带来一个棘手的负面影响:维护相关对象之间的统一。 4 | 5 | 在Smalltalk MVC架构中,创建模式的第一个例子就是用来解决这个问题的。用户界面框架提供一种途径使UI元素与包含数据的实体对象相分离,并且同时,它提供一种灵活的方法来保持它们之间的同步。 6 | 7 | 在这本畅销的四人组编写的《设计模式——可复用面向对象软件的基础》一书中,观察者模式是最有名的设计模式之一。它是一种行为模式并提供一种以一对多的依赖来绑定对象的方法:即当一个对象发生变化时,依赖它的所有对象都会被通知并且会自动更新。 8 | 9 | 在本章中,我们将会对观察者模式有一个概述,它是如何实现的以及如何用RxJava来扩展,Observable是什么,以及Observables如何与Iterables相关联。 -------------------------------------------------------------------------------- /chapter3/a_few_more_examples.md: -------------------------------------------------------------------------------- 1 | # 再多几个例子 2 | 3 | 在这一节中,我们将基于RxJava的`just()`,`repeat()`,`defer()`,`range()`,`interval()`,和`timer()`方法展示一些例子。 4 | 5 | ## just() 6 | 7 | 假如我们只有3个独立的AppInfo对象并且我们想把他们转化为Observable并填充到RecyclerView的item中: 8 | ```java 9 | List apps = ApplicationsList.getInstance().getList(); 10 | 11 | AppInfo appOne = apps.get(0); 12 | 13 | AppInfo appTwo = apps.get(10); 14 | 15 | AppInfo appThree = apps.get(24); 16 | 17 | loadApps(appOne,appTwo,appThree); 18 | 19 | ``` 20 | 21 | 我们可以像我们之前的例子那样检索列表并提取出这三个元素。然后我们将他们传到这个`loadApps()`函数里面: 22 | ```java 23 | private void loadApps(AppInfo appOne,AppInfo appTwo,AppInfo appThree) { 24 | mRecyclerView.setVisibility(View.VISIBLE); 25 | Observable.just(appOne,appTwo,appThree) 26 | .subscribe(new Observer() { 27 | 28 | @Override 29 | public void onCompleted() { 30 | mSwipeRefreshLayout.setRefreshing(false); 31 | Toast.makeText(getActivity(), "Here is the list!", Toast.LENGTH_LONG).show(); 32 | } 33 | 34 | @Override 35 | public void onError(Throwable e) { 36 | Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); 37 | mSwipeRefreshLayout.setRefreshing(false); 38 | } 39 | 40 | @Override 41 | public void onNext(AppInfo appInfo) { 42 | mAddedApps.add(appInfo); 43 | mAdapter.addApplication(mAddedApps.size() - 1,appInfo); 44 | } 45 | }); 46 | } 47 | ``` 48 | 49 | 正如你看到的,代码和之前的例子很像。这种方法让我们有机会来考虑一下代码的复用。 50 | 51 | 你可以将一个函数作为参数传给`just()`方法,你将会得到一个已存在代码的原始Observable版本。在一个新的响应式架构的基础上迁移已存在的代码,这个方法可能是一个有用的开始点。 52 | 53 | 54 | ## repeat() 55 | 56 | 假如你想对一个Observable重复发射三次数据。例如,我们用`just()`例子中的Observable: 57 | 58 | ```java 59 | private void loadApps(AppInfo appOne,AppInfo appTwo,AppInfo appThree) { 60 | mRecyclerView.setVisibility(View.VISIBLE); 61 | Observable.just(appOne,appTwo,appThree) 62 | .repeat(3) 63 | .subscribe(new Observer() { 64 | 65 | @Override 66 | public void onCompleted() { 67 | mSwipeRefreshLayout.setRefreshing(false); 68 | Toast.makeText(getActivity(), "Here is the list!", Toast.LENGTH_LONG).show(); 69 | } 70 | 71 | @Override 72 | public void onError(Throwable e) { 73 | Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); 74 | mSwipeRefreshLayout.setRefreshing(false); 75 | } 76 | 77 | @Override 78 | public void onNext(AppInfo appInfo) { 79 | mAddedApps.add(appInfo); 80 | mAdapter.addApplication(mAddedApps.size() - 1,appInfo); 81 | } 82 | }); 83 | } 84 | ``` 85 | 正如你看到的,我们在`just()`创建Observable后追加了`repeat(3)`,它将会创建9个元素的序列,每一个都单独发射。 86 | 87 | ## defer() 88 | 89 | 有这样一个场景,你想在这声明一个Observable但是你又想推迟这个Observable的创建直到观察者订阅时。看下面的`getInt()`函数: 90 | ```java 91 | private Observable getInt(){ 92 | return Observable.create(subscriber -> { 93 | if(subscriber.isUnsubscribed()){ 94 | return; 95 | } 96 | App.L.debug("GETINT"); 97 | subscriber.onNext(42); 98 | subscriber.onCompleted(); 99 | }); 100 | } 101 | ``` 102 | 103 | 这比较简单,并且它没有做太多事情,但是它正好为我们服务。现在,我们可以创建一个新的Observable并且应用`defer()`: 104 | 105 | ```java 106 | Observable deferred = Observable.defer(this::getInt); 107 | ``` 108 | 这次,`deferred`存在,但是`getInt()` `create()`方法还没有调用:logcat日志也没有“GETINT”打印出来: 109 | 110 | ```java 111 | deferred.subscribe(number -> { 112 | App.L.debug(String.valueOf(number)); 113 | }); 114 | ``` 115 | 但是一旦我们订阅了,`create()`方法就会被调用并且我们也可以在logcat日志中得到下卖弄两个:GETINT和42。 116 | 117 | ## range() 118 | 119 | 你需要从一个指定的数字X开始发射N个数字吗?你可以用`range`: 120 | ```java 121 | Observable.range(10,3) 122 | .subscribe(new Observer() { 123 | 124 | @Override 125 | public void onCompleted() { 126 | Toast.makeText(getActivity(), "Yeaaah!", Toast.LENGTH_LONG).show(); 127 | } 128 | 129 | @Override 130 | public void onError(Throwable e) { 131 | Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); 132 | } 133 | 134 | @Override 135 | public void onNext(Integer number) { 136 | Toast.makeText(getActivity(), "I say " + number, Toast.LENGTH_SHORT).show(); 137 | } 138 | }); 139 | ``` 140 | 141 | `range()`函数用两个数字作为参数:第一个是起始点,第二个是我们想发射数字的个数。 142 | 143 | 144 | ## interval() 145 | 146 | `interval()`函数在你需要创建一个轮询程序时非常好用。 147 | ```java 148 | Subscription stopMePlease = Observable.interval(3,TimeUnit.SECONDS) 149 | .subscribe(new Observer() { 150 | 151 | @Override 152 | public void onCompleted() { 153 | Toast.makeText(getActivity(), "Yeaaah!", Toast.LENGTH_LONG).show(); 154 | } 155 | 156 | @Override 157 | public void onError(Throwable e) { 158 | Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); 159 | } 160 | 161 | @Override 162 | public void onNext(Integer number) { 163 | Toast.makeText(getActivity(), "I say " + number, Toast.LENGTH_SHORT).show(); 164 | } 165 | }); 166 | ``` 167 | `interval()`函数的两个参数:一个指定两次发射的时间间隔,另一个是用到的时间单位。 168 | 169 | 170 | ## timer() 171 | 172 | 如果你需要一个一段时间之后才发射的Observable,你可以像下面的例子使用`timer()`: 173 | 174 | ```java 175 | Observable.timer(3,TimeUnit.SECONDS) 176 | .subscribe(new Observer() { 177 | 178 | @Override 179 | public void onCompleted() { 180 | 181 | } 182 | 183 | @Override 184 | public void onError(Throwable e) { 185 | 186 | } 187 | 188 | @Override 189 | public void onNext(Long number) { 190 | Log.d("RXJAVA", "I say " + number); 191 | } 192 | }); 193 | ``` 194 | 它将3秒后发射0,然后就完成了。让我们使用`timer()`的第三个参数,就像下面的例子: 195 | ```java 196 | Observable.timer(3,3,TimeUnit.SECONDS) 197 | .subscribe(new Observer() { 198 | 199 | @Override 200 | public void onCompleted() { 201 | 202 | } 203 | 204 | @Override 205 | public void onError(Throwable e) { 206 | 207 | } 208 | 209 | @Override 210 | public void onNext(Long number) { 211 | Log.d("RXJAVA", "I say " + number); 212 | } 213 | }); 214 | ``` 215 | 用这个代码,你可以创建一个以初始值来延迟(上一个例子是3秒)执行的`interval()`版本,然后每隔N秒就发射一个新的数字(前面的例子是3秒)。 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | -------------------------------------------------------------------------------- /chapter3/creating_an_observable_from_list.md: -------------------------------------------------------------------------------- 1 | # 从列表创建一个Observable 2 | 3 | 在这个例子中,我们将引入`from()`函数。使用这个特殊的“创建”函数,我们可以从一个列表中创建一个Observable。Observable将发射出列表中的每一个元素,我们可以通过订阅它们来对这些发出的元素做出响应。 4 | 5 | 为了实现和第一个例子同样的结果,我们在每一个`onNext()`函数更新我们的适配器,添加元素并通知插入。 6 | 7 | 我们将复用和第一个例子同样的结构。主要的不同的是我们不再检索已安装的应用列表。列表由外部实体提供: 8 | 9 | ```java 10 | mApps = ApplicationsList.getInstance().getList(); 11 | ``` 12 | 获得列表后,我们仅需将它响应化并填充RecyclerView的item: 13 | ```java 14 | private void loadList(List apps) { 15 | mRecyclerView.setVisibility(View.VISIBLE); 16 | Observable.from(apps) 17 | .subscribe(new Observer() { 18 | 19 | @Override 20 | public void onCompleted() { 21 | mSwipeRefreshLayout.setRefreshing(false); 22 | Toast.makeText(getActivity(), "Here is the list!", Toast.LENGTH_LONG).show(); 23 | } 24 | 25 | @Override 26 | public void onError(Throwable e) { 27 | Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); 28 | mSwipeRefreshLayout.setRefreshing(false); 29 | } 30 | 31 | @Override 32 | public void onNext(AppInfo appInfo) { 33 | mAddedApps.add(appInfo); 34 | mAdapter.addApplication(mAddedApps.size() - 1,appInfo); 35 | } 36 | }); 37 | } 38 | ``` 39 | 正如你看到的,我们将已安装的应用程序列表作为参数传进`from()`函数,然后我们订阅生成的Observable。观察者和我们第一个例子中的观察者十分相像。一个主要的不同是我们在`onCompleted()`函数中停掉进度条是因为我们一个一个的发射元素;第一个例子中的Observable发射的是整个list,因此在`onNext()`函数中停掉进度条的做法是安全的。 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /chapter3/hello_reactive_world.md: -------------------------------------------------------------------------------- 1 | # 向响应式世界问好 2 | 3 | 在上一章中,我们对观察者模式有个理论上的快速概述。我们也看了从头开始、从列表、或者从已经存在的函数来创建Observables。在本章中,我们将用我们学到的来创建我们第一个响应式Android应用程序。首先,我们需要搭建环境,导入需要的库和有用的库。然后我们将创建一个简单的应用程序,在不同的flavors中包含几个用RxJava填充的RecycleView items。 -------------------------------------------------------------------------------- /chapter3/our_first_observable.md: -------------------------------------------------------------------------------- 1 | # 我们的第一个Observable 2 | 3 | 在我们的第一个列子里,我们将检索安装的应用列表并填充RecycleView的item来展示它们。我们也设想一个下拉刷新的功能和一个进度条来告知用户当前任务正在执行。 4 | 5 | 首先,我们创建Observable。我们需要一个函数来检索安装的应用程序列表并把它提供给我们的观察者。我们一个接一个的发射这些应用程序数据,将它们分组到一个单独的列表中,以此来展示响应式方法的灵活性。 6 | 7 | ```java 8 | private Observable getApps(){ 9 | return Observable.create(subscriber -> { 10 | List apps = new ArrayList(); 11 | 12 | final Intent mainIntent = new Intent(Intent.ACTION_MAIN,null); 13 | mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); 14 | 15 | List infos = getActivity().getPackageManager().queryIntentActivities(mainIntent, 0); 16 | 17 | for(ResolveInfo info : infos){ 18 | apps.add(new AppInfoRich(getActivity(),info)); 19 | } 20 | 21 | for (AppInfoRich appInfo:apps) { 22 | Bitmap icon = Utils.drawableToBitmap(appInfo.getIcon()); 23 | String name = appInfo.getName(); 24 | String iconPath = mFilesDir + "/" + name; 25 | Utils.storeBitmap(App.instance, icon,name); 26 | 27 | if (subscriber.isUnsubscribed()){ 28 | return; 29 | } 30 | subscriber.onNext(new AppInfo(name,iconPath,appInfo.getLastUpdateTime())); 31 | } 32 | if (!subscriber.isUnsubscribed()){ 33 | subscriber.onCompleted(); 34 | } 35 | }); 36 | } 37 | ``` 38 | AppInfo对象如下: 39 | ```java 40 | @Data 41 | @Accessors(prefix = "m") 42 | public class AppInfo implements Comparable { 43 | 44 | long mLastUpdateTime; 45 | String mName; 46 | String mIcon; 47 | 48 | public AppInfo(String nName, long lastUpdateTime, String icon) { 49 | mName = nName; 50 | mIcon = icon; 51 | mLastUpdateTime = lastUpdateTime; 52 | } 53 | 54 | @Override 55 | public int compareTo(Object another) { 56 | AppInfo f = (AppInfo)another; 57 | return getName().compareTo(f.getName()); 58 | } 59 | } 60 | ``` 61 | 需要重点注意的是在发射新的数据或者完成序列之前要检测观察者的订阅情况。这样的话代码会更高效,因为如果没有观察者等待时我们就不生成没有必要的数据项。 62 | 63 | 此时,我们可以订阅Observable并观察它。订阅一个Observable意味着当我们需要的数据进来时我们必须提供对应的操作来执行它。 64 | 65 | 当前的场景是什么?我们展示一个进度条来等待数据。当数据到来时,我们需要隐藏掉进度条,填充list,最终展示列表。现在,我们知道当一切都准备好了该做什么。那么错误的场景呢?对于错误这种情况,我们仅仅是用Toast展示一个错误的信息。 66 | 67 | 使用Butter Knife,我们得到list和下拉刷新组件的引用: 68 | ```java 69 | @InjetcView(R.id.fragment_first_example_list) 70 | RecyclerView mRecycleView; 71 | 72 | @InjectView(R.id.fragment_first_example_swipe_container) 73 | SwipeRefreshLayout mSwipeRefreshLayout; 74 | ``` 75 | 76 | 我们使用Android 5的标准组件:RecyclerView和SwipeRefreshLayout。截屏展示了我们这个简单App的list Fragment的layout文件: 77 | 78 | ![](../images/chapter3_4.png) 79 | 80 | 我们使用一个下拉刷新方法,因此列表数据可以来自初始化加载,或由用户触发的一个刷新动作。针对这两个场景,我们用同样的行为,因此我们把我们的观察者放在一个易被复用的函数里面。下面是我们的观察者,定义了成功、失败、完成要做的事情: 81 | 82 | ```java 83 | private void refreshTheList() { 84 | getApps().toSortedList() 85 | .subscribe(new Observer>() { 86 | 87 | @Override 88 | public void onCompleted() { 89 | Toast.makeText(getActivity(), "Here is the list!", Toast.LENGTH_LONG).show(); 90 | } 91 | 92 | @Override 93 | public void onError(Throwable e) { 94 | Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); 95 | mSwipeRefreshLayout.setRefreshing(false); 96 | } 97 | 98 | @Override 99 | public void onNext(List appInfos) { 100 | mRecyclerView.setVisibility(View.VISIBLE); 101 | mAdapter.addApplications(appInfos); 102 | mSwipeRefreshLayout.setRefreshing(false); 103 | } 104 | }); 105 | } 106 | ``` 107 | 108 | 定义一个函数使我们能够用同样一个block来处理两种场景成为了可能。当fragment加载时我们只需调用`refreshTheList()`方法并设置`refreshTheList()`方法作为用户下拉这一行为所触发的方法。 109 | 110 | ```java 111 | mSwipeRefreshLayout.setOnRefreshListener(this::refreshTheList); 112 | ``` 113 | 114 | 我们第一个例子现在完成了,运行跑一下。 115 | 116 | ![](../images/chapter3_5.png) 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /chapter3/start_the_engine.md: -------------------------------------------------------------------------------- 1 | # 启动引擎 2 | 3 | 我们将使用IntelliJ IDEA/Android Studio来创建这个工程,因此你会对截图看起来比较熟悉。 4 | 5 | 让我们开始创建一个新的Android工程。你可以创建你自己的工程或者用本书中提供的导入。选择你自己喜欢的创建方式这取决于你。 6 | 7 | 如果你想用Android Studio创建一个新的工程,通常你可以参考官方文档:http://developer.android.com/intl/zh-cn/training/basics/firstapp/creating-project.html 8 | 9 | ![](../images/chapter3_1.png) 10 | 11 | ### 依赖 12 | 13 | 很明显,我们将使用**Gradle**来管理我们的依赖列表。我们的build.gradble文件看起来像这样: 14 | ![](../images/chapter3_2.png) 15 | ![](../images/chapter3_3.png) 16 | 正如你看到的我们引入了RxAndroid。RxAndroid是RxJava的增强版,尤其是针对Android设计的。 17 | ### RxAndroid 18 | 19 | RxAndroid是RxJava家族的一部分。它基于RxJava1.0.x,在普通的RxJava基础上添加了几个有用的类。大多数情况下,它为Android添加了特殊的调度器。我们将在第七章Schedulers-Defeating the Android MainThread Issue再讨论它。 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /chapter3/summary3.md: -------------------------------------------------------------------------------- 1 | # 总结 2 | 3 | 在本章中,我们创建了第一个由RxJava强化的Android应用程序。我们从头、从已有的列表、从已有的函数来创建Observable。我们也学习了如何创建重复发射的Observables,间隔发射的Observables以及延迟发射的Observables。 4 | 5 | 在下一章中,我们将掌握过滤操作,能够从我们接收到的序列中创建我们需要的序列。 -------------------------------------------------------------------------------- /chapter3/utils.md: -------------------------------------------------------------------------------- 1 | # 工具 2 | 3 | 出于实用,我们引入了Lombok 和 Butter Knife。这两个可以帮助我们在Android应用程序中少写许多模板类代码。 4 | 5 | ### Lombok 6 | 7 | Lombok使用注解的方式为你生成许多代码。大多数情况下我们使用其生成`getter/setter`、`toString()`、`equals()`、`hashCode()`。它来自于一个Gradle依赖和Android Studio插件。 8 | 9 | ### Butter Knife 10 | 11 | Butter Knife使用注解的方式来帮助我们免去写`findViewById()`和设置点击监听的痛苦。至于Lombok,我们可以通过导入依赖和安装Android Studio插件来获得更好的体验。 12 | 13 | ### Retrolambda 14 | 15 | 最后,我们导入Retrolambda,是因为我们开发的Android是基于Java 1.6,然后我们可以借助它来实现Java 8 Lambda函数从而减少许多模板代码。 -------------------------------------------------------------------------------- /chapter4/debounce.md: -------------------------------------------------------------------------------- 1 | # Debounce 2 | 3 | `debounce()`函数过滤掉由Observable发射的速率过快的数据;如果在一个指定的时间间隔过去了仍旧没有发射一个,那么它将发射最后的那个。 4 | 5 | 就像`sample()`和`timeout()`函数一样,`debounce()`使用`TimeUnit`对象指定时间间隔。 6 | 7 | 下图展示了多久从Observable发射一次新的数据,`debounce()`函数开启一个内部定时器,如果在这个时间间隔内没有新的数据发射,则新的Observable发射出最后一个数据: 8 | 9 | ![](../images/chapter4_15.png) 10 | 11 | -------------------------------------------------------------------------------- /chapter4/elementat.md: -------------------------------------------------------------------------------- 1 | # ElementAt 2 | 3 | 如果我们只想要可观测序列发射的第五个元素该怎么办?`elementAt()`函数仅从一个序列中发射第n个元素然后就完成了。 4 | 5 | 如果我们想查找第五个元素但是可观测序列只有三个元素可供发射时该怎么办?我们可以使用`elementAtOrDefault()`。下图展示了如何通过使用`elementAt(2)`从一个序列中选择第三个元素以及如何创建一个只发射指定元素的新的Observable。 6 | 7 | ![](../images/chapter4_12.png) 8 | 9 | -------------------------------------------------------------------------------- /chapter4/filtering_a_sequence.md: -------------------------------------------------------------------------------- 1 | # 过滤序列 2 | 3 | RxJava让我们使用`filter()`方法来过滤我们观测序列中不想要的值,在上一章中,我们在几个例子中使用了已安装的应用列表,但是我们只想展示以字母`C`开头的已安装的应用该怎么办呢?在这个新的例子中,我们将使用同样的列表,但是我们会过滤它,通过把合适的谓词传给`filter()`函数来得到我们想要的值。 4 | 5 | 上一章中`loadList()`函数可以改成这样: 6 | ```java 7 | private void loadList(List apps) { 8 | mRecyclerView.setVisibility(View.VISIBLE); 9 | Observable.from(apps) 10 | .filter((appInfo) -> 11 | appInfo.getName().startsWith("C")) 12 | .subscribe(new Observer() { 13 | 14 | @Override 15 | public void onCompleted() { 16 | mSwipeRefreshLayout.setRefreshing(false); 17 | } 18 | 19 | @Override 20 | public void onError(Throwable e) { 21 | Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); 22 | mSwipeRefreshLayout.setRefreshing(false); 23 | } 24 | 25 | @Override 26 | public void onNext(AppInfo appInfo) { 27 | mAddedApps.add(appInfo); 28 | mAdapter.addApplication(mAddedApps.size() - 1,appInfo); 29 | } 30 | }); 31 | } 32 | ``` 33 | 我们从上一章中的`loadList()`函数中添加下面一行: 34 | ```java 35 | .fliter((appInfo -> appInfo.getName().startsWith("C")) 36 | ``` 37 | 创建Observable完以后,我们从发出的每个元素中过滤掉开头字母不是C的。为了让这里更清楚一些,我们用Java 7的语法来实现: 38 | ```java 39 | .filter(new Func1(){ 40 | @Override 41 | public Boolean call(AppInfo appInfo){ 42 | return appInfo.getName().startsWith("C"); 43 | } 44 | }) 45 | ``` 46 | 47 | 我们传一个新的`Func1`对象给`filter()`函数,即只有一个参数的函数。`Func1`有一个`AppInfo`对象来作为它的参数类型并且返回`Boolean`对象。只要条件符合`filter()`函数就会返回`true`。此时,值会发射出去并且所有的观察者都会接收到。 48 | 49 | 正如你想的那样,从一个我们得到的可观测序列中创建一个我们需要的序列`filter()`是很好用的。我们不需要知道可观测序列的源或者为什么发射这么多不同的数据。我们只是想要这些元素的子集来创建一个可以在应用中使用的新序列。这种思想促进了我们编码中的分离性与抽象性。 50 | 51 | `filter()`函数最常用的用法之一时过滤`null`对象: 52 | ```java 53 | .filter(new Func1(){ 54 | @Override 55 | public Boolean call(AppInfo appInfo){ 56 | return appInfo != null; 57 | } 58 | }) 59 | ``` 60 | 这看起来简单,对于简单的事情有许多模板代码,但是它帮我们免去了在`onNext()`函数调用中再去检测`null`值,让我们把注意力集中在应用业务逻辑上。 61 | 62 | 下图展示了过滤出的C字母开头的已安装的应用列表。 63 | 64 | ![](../images/chapter4_1.png) 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /chapter4/filtering_observables.md: -------------------------------------------------------------------------------- 1 | # 过滤Observables 2 | 3 | 在上一章中,我们学习了使用RxJava创建一个Android工程以及如何创建一个可观测的列表来填充RecyclerView。我们现在知道了如何从头、从列表、从一个已存在的传统Java函数来创建Observable。 4 | 5 | 这一章中,我们将研究可观测序列的本质:过滤。我们将学到如何从发射的Observable中选取我们想要的值,如何获取有限个数的值,如何处理溢出的场景,以及更多的有用的技巧。 -------------------------------------------------------------------------------- /chapter4/first_and_last.md: -------------------------------------------------------------------------------- 1 | # First and last 2 | 3 | 下图展示了如何从一个从可观测源序列中创建只发射第一个元素的序列。 4 | 5 | ![](../images/chapter4_8.png) 6 | 7 | `first()`方法和`last()`方法很容易弄明白。它们从Observable中只发射第一个元素或者最后一个元素。这两个都可以传`Func1`作为参数,:一个可以确定我们感兴趣的第一个或者最后一个的谓词: 8 | 9 | 下图展示了`last()`应用在一个完成的序列上来创建一个仅仅发射最后一个元素的新的Observable。 10 | 11 | ![](../images/chapter4_9.png) 12 | 13 | 与`first()`和`last()`相似的变量有:`firstOrDefault()`和`lastOrDefault()`.这两个函数当可观测序列完成时不再发射任何值时用得上。在这种场景下,如果Observable不再发射任何值时我们可以指定发射一个默认的值 -------------------------------------------------------------------------------- /chapter4/let's_take_what_we_need.md: -------------------------------------------------------------------------------- 1 | # 获取我们需要的数据 2 | 3 | 当我们不需要整个序列时,而是只想取开头或结尾的几个元素,我们可以用`take()`或`takeLast()`。 4 | 5 | ## Take 6 | 7 | 如果我们只想要一个可观测序列中的前三个元素那将会怎么样,发射它们,然后让Observable完成吗?`take()`函数用整数N来作为一个参数,从原始的序列中发射前N个元素,然后完成: 8 | ```java 9 | private void loadList(List apps) { 10 | mRecyclerView.setVisibility(View.VISIBLE); 11 | Observable.from(apps) 12 | .take(3) 13 | .subscribe(new Observer() { 14 | 15 | @Override 16 | public void onCompleted() { 17 | mSwipeRefreshLayout.setRefreshing(false); 18 | } 19 | 20 | @Override 21 | public void onError(Throwable e) { 22 | Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); 23 | mSwipeRefreshLayout.setRefreshing(false); 24 | } 25 | 26 | @Override 27 | public void onNext(AppInfo appInfo) { 28 | mAddedApps.add(appInfo); 29 | mAdapter.addApplication(mAddedApps.size() - 1,appInfo); 30 | } 31 | }); 32 | } 33 | ``` 34 | 下图中展示了发射数字的一个可观测序列。我们对这个可观测序列应用`take(2)`函数,然后我们创建一个只发射可观测源的第一个和第二个数据的新序列。 35 | 36 | ![](../images/chapter4_2.png) 37 | 38 | ## TakeLast 39 | 40 | 如果我们想要最后N个元素,我们只需使用`takeLast()`函数: 41 | ```java 42 | Observable.from(apps) 43 | .takeLast(3) 44 | .subscribe(...); 45 | ``` 46 | 正如听起来那样不值一提,值得注意的是因为takeLast()方法只能作用于一组有限的序列(发射元素),它只能应用于一个完整的序列。 47 | 48 | 下图中展示了如何从可观测源中发射最后一个元素来创建一个新的序列: 49 | 50 | ![](../images/chapter4_3.png) 51 | 52 | 下图中展示了我们在已安装的应用列表使用`take()`和`takeLast()`函数后发生的结果: 53 | 54 | ![](../images/chapter4_4.png) 55 | -------------------------------------------------------------------------------- /chapter4/once_and_only_once.md: -------------------------------------------------------------------------------- 1 | # 有且仅有一次 2 | 3 | 一个可观测序列会在出错时重复发射或者被设计成重复发射。`distinct()`和`distinctUntilChanged()`函数可以方便的让我们处理这种重复问题。 4 | 5 | ## Distinct 6 | 7 | 如果我们想对一个指定的值仅处理一次该怎么办?我们可以对我们的序列使用`distinct()`函数去掉重复的。就像`takeLast()`一样,`distinct()`作用于一个完整的序列,然后得到重复的过滤项,它需要记录每一个发射的值。如果你在处理一大堆序列或者大的数据记得关注内存使用情况。 8 | 9 | 下图展示了如何在一个发射1和2两次的可观测源上创建一个无重的序列: 10 | ![](../images/chapter4_5.png) 11 | 12 | 为了创建我们例子中序列,我们将使用我们至今已经学到的几个方法: 13 | * `take()`:它有一小组的可识别的数据项。 14 | * `repeat()`:创建一个有重复的大的序列。 15 | 16 | 然后,我们将应用`distinct()`函数来去除重复。 17 | 18 | ## 注意 19 | 20 | 我们用程序实现一个重复的序列,然后过滤出它们。这听起来时不可思议的,但是为了实现这个例子来使用我们至今为止已学习到的东西则是个不错的练习。 21 | 22 | ```java 23 | Observable fullOfDuplicates = Observable.from(apps) 24 | .take(3) 25 | .repeat(3); 26 | ``` 27 | `fullOfDuplicates`变量里把我们已安装应用的前三个重复了3次:有9个并且许多重复的。然后,我们使用`distinct()`: 28 | ```java 29 | fullOfDuplicates.distinct() 30 | .subscribe(new Observer() { 31 | 32 | @Override 33 | public void onCompleted() { 34 | mSwipeRefreshLayout.setRefreshing(false); 35 | } 36 | 37 | @Override 38 | public void onError(Throwable e) { 39 | Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); 40 | mSwipeRefreshLayout.setRefreshing(false); 41 | } 42 | 43 | @Override 44 | public void onNext(AppInfo appInfo) { 45 | mAddedApps.add(appInfo); 46 | mAdapter.addApplication(mAddedApps.size() - 1,appInfo); 47 | } 48 | }); 49 | } 50 | ``` 51 | 结果,很明显,我们得到: 52 | 53 | ![](../images/chapter4_6.png) 54 | 55 | ## DistinctUntilsChanged 56 | 57 | 如果在一个可观测序列发射一个不同于之前的一个新值时让我们得到通知这时候该怎么做?我们猜想一下我们观测的温度传感器,每秒发射的室内温度: 58 | ``` 59 | 21°...21°...21°...21°...22°... 60 | ``` 61 | 每次我们获得一个新值,我们都会更新当前正在显示的温度。我们出于系统资源保护并不想在每次值一样时更新数据。我们想忽略掉重复的值并且在温度确实改变时才想得到通知。`ditinctUntilChanged()`过滤函数能做到这一点。它能轻易的忽略掉所有的重复并且只发射出新的值。 62 | 63 | 下图用图形化的方式展示了我们如何将`distinctUntilChanged()`函数应用在一个存在的序列上来创建一个新的不重复发射元素的序列。 64 | 65 | ![](../images/chapter4_7.png) 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /chapter4/sampling.md: -------------------------------------------------------------------------------- 1 | # Sampling 2 | 3 | 让我们再回到那个温度传感器。它每秒都会发射当前室内的温度。说实话,我们并不认为温度会变化这么快,我们可以使用一个小的发射间隔。在Observable后面加一个`sample()`,我们将创建一个新的可观测序列,它将在一个指定的时间间隔里由Observable发射最近一次的数值: 4 | 5 | ```java 6 | Observable sensor = [...] 7 | 8 | sensor.sample(30,TimeUnit.SECONDS) 9 | .subscribe(new Observer() { 10 | 11 | @Override 12 | public void onCompleted() { 13 | 14 | } 15 | 16 | @Override 17 | public void onError(Throwable e) { 18 | 19 | } 20 | 21 | @Override 22 | public void onNext(Integer currentTemperature) { 23 | updateDisplay(currentTemperature) 24 | } 25 | }); 26 | ``` 27 | 例子中Observable将会观测温度Observable然后每隔30秒就会发射最后一个温度值。很明显,`sample()`支持全部的时间单位:秒,毫秒,天,分等等。 28 | 29 | 下图中展示了一个间隔发射字母的Observable如何采样一个发射数字的Observable。Observable的结果将会发射每个已发射字母的最后一组数据:1,4,5. 30 | 31 | ![](../images/chapter4_13.png) 32 | 33 | 如果我们想让它定时发射第一个元素而不是最近的一个元素,我们可以使用`throttleFirst()`。 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /chapter4/skip_and_skiplast.md: -------------------------------------------------------------------------------- 1 | # Skip and SkipLast 2 | 3 | 下图中展示了如何使用`skip(2)`来创建一个不发射前两个元素而是发射它后面的那些数据的序列。 4 | 5 | ![](../images/chapter4_10.png) 6 | 7 | `skip()`和`skipLast()`函数与`take()`和`takeLast()`相对应。它们用整数N作参数,从本质上来说,它们不让Observable发射前N个或者后N个值。如果我们知道一个序列以没有太多用的“可控”元素开头或结尾时我们可以使用它。 8 | 9 | 下图与前一个场景相对应:我们创建一个新的序列,它会跳过后面两个元素从源序列中发射剩下的其他元素。 10 | 11 | ![](../images/chapter4_11.png) 12 | 13 | -------------------------------------------------------------------------------- /chapter4/summary4.md: -------------------------------------------------------------------------------- 1 | # 总结 2 | 3 | 这一章中,我们学习了如何过滤一个可观测序列。我们现在可以使用`filter()`,`skip()`,和`sample()`来创建我们想要的Observable。 4 | 5 | 下一章中,我们将学习如何转换一个序列,将函数应用到每个元素,给它们分组和扫描来创建我们所需要的能完成目标的特定Observable。 -------------------------------------------------------------------------------- /chapter4/timeout.md: -------------------------------------------------------------------------------- 1 | # Timeout 2 | 3 | 假设我们工作的是一个时效性的环境,我们温度传感器每秒都在发射一个温度值。我们想让它每隔两秒至少发射一个,我们可以使用`timeout()`函数来监听源可观测序列,就是在我们设定的时间间隔内如果没有得到一个值则发射一个错误。我们可以认为`timeout()`为一个Observable的限时的副本。如果在指定的时间间隔内Observable不发射值的话,它监听的原始的Observable时就会触发`onError()`函数。 4 | 5 | ```java 6 | Subscription subscription = getCurrentTemperature() 7 | .timeout(2,TimeUnit.SECONDS) 8 | .subscribe(new Observer() { 9 | 10 | @Override 11 | public void onCompleted() { 12 | 13 | } 14 | 15 | @Override 16 | public void onError(Throwable e) { 17 | Log.d("RXJAVA","You should go check the sensor, dude"); 18 | } 19 | 20 | @Override 21 | public void onNext(Integer currentTemperature) { 22 | updateDisplay(currentTemperature) 23 | } 24 | }); 25 | ``` 26 | 27 | 和`sample()`一样,`timeout()`使用`TimeUnit`对象来指定时间间隔。 28 | 29 | 下图中展示了一旦Observable超过了限时就会触发`onError()`函数:因为超时后它才到达,所以最后一个元素将不会发射出去。 30 | 31 | ![](../images/chapter4_14.png) 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /chapter5/buffer.md: -------------------------------------------------------------------------------- 1 | # Buffer 2 | 3 | RxJava中的`buffer()`函数将源Observable变换一个新的Observable,这个新的Observable每次发射一组列表值而不是一个一个发射。 4 | 5 | ![](../images/chapter5_10.png) 6 | 7 | 上图中展示了`buffer()`如何将`count`作为一个参数来指定有多少数据项被包在发射的列表中。实际上,`buffer()`函数有几种变体。其中有一个是允许你指定一个`skip`值:此后每`skip`项数据,然后又用count项数据填充缓冲区。如下图所示: 8 | 9 | ![](../images/chapter5_11.png) 10 | 11 | `buffer()`带一个`timespan`的参数,会创建一个每隔timespan时间段就会发射一个列表的Observable。 12 | 13 | ![](../images/chapter5_12.png) 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /chapter5/cast.md: -------------------------------------------------------------------------------- 1 | # Cast 2 | 3 | RxJava的`cast()`函数是本章中最后一个操作符。它是`map()`操作符的特殊版本。它将源Observable中的每一项数据都转换为新的类型,把它变成了不同的`Class`。 4 | 5 | ![](../images/chapter5_15.png) 6 | 7 | -------------------------------------------------------------------------------- /chapter5/groupby.md: -------------------------------------------------------------------------------- 1 | # GroupBy 2 | 3 | 拿第一个例子开始,我们安装的应用程序列表按照字母表的顺序排序。然而,如果现在我们想按照最近更新日期来排序我们的App时该怎么办?RxJava提供了一个有用的函数从列表中按照指定的规则:`groupBy()`来分组元素。下图中的例子展示了`groupBy()`如何将发射的值根据他们的形状来进行分组。 4 | 5 | ![](../images/chapter5_8.png) 6 | 7 | 这个函数将源Observable变换成一个发射Observables的新的Observable。它们中的每一个新的Observable都发射一组指定的数据。 8 | 9 | 为了创建一个分组了的已安装应用列表,我们在`loadList()`函数中引入了一个新的元素: 10 | ```java 11 | Observable> groupedItems = Observable.from(apps) 12 | .groupBy(new Func1(){ 13 | @Override 14 | public String call(AppInfo appInfo){ 15 | SimpleDateFormat formatter = new SimpleDateFormat("MM/yyyy"); 16 | return formatter.format(new Date(appInfo.getLastUpdateTime())); 17 | } 18 | }); 19 | ``` 20 | 现在我们创建了一个新的Observable,`groupedItems`,它将会发射一个带有`GroupedObservable`的序列。`GroupedObservable`是一个特殊的Observable,它源自一个分组的key。在这个例子中,key就是`String`,代表的意思是`Month/Year`格式化的最近更新日期。 21 | 22 | 这一点,我们已经创建了几个发射`AppInfo`数据的Observable,用来填充我们的列表。我们想保留字母排序和分组排序。我们将创建一个新的Observable将所有的联系起来,像往常一样然后订阅它: 23 | 24 | ```java 25 | Observable.concat(groupedItems) 26 | .subscribe(new Observer() { 27 | 28 | @Override 29 | public void onCompleted() { 30 | mSwipeRefreshLayout.setRefreshing(false); 31 | } 32 | 33 | @Override 34 | public void onError(Throwable e) { 35 | Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); 36 | mSwipeRefreshLayout.setRefreshing(false); 37 | } 38 | 39 | @Override 40 | public void onNext(AppInfo appInfo) { 41 | mAddedApps.add(appInfo); 42 | mAdapter.addApplication(mAddedApps.size() - 1,appInfo); 43 | } 44 | }); 45 | ``` 46 | 47 | 我们的`loadList()`函数完成了,结果是: 48 | 49 | ![](../images/chapter5_9.png) 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /chapter5/summary5.md: -------------------------------------------------------------------------------- 1 | # 总结 2 | 3 | 这一章中,我们学习了RxJava时如何控制和转换可观测序列。用我们现在所学的知识,我们可以创建、过滤、转换我们所想要的任何种类的可观测序列。 4 | 5 | 下一章,我们将学习如何组合Observable,合并它们,连接它们,再或者打包它们。 -------------------------------------------------------------------------------- /chapter5/the_map_family.md: -------------------------------------------------------------------------------- 1 | # *map家族 2 | 3 | RxJava提供了几个mapping函数:`map()`,`flatMap()`,`concatMap()`,`flatMapIterable()`以及`switchMap()`.所有这些函数都作用于一个可观测序列,然后变换它发射的值,最后用一种新的形式返回它们。让我们用合适的“真实世界”的例子一个个的学习下。 4 | 5 | ## Map 6 | 7 | RxJava的`map`函数接收一个指定的`Func`对象然后将它应用到每一个由Observable发射的值上。下图展示了如何将一个乘法函数应用到每个发出的值上以此创建一个新的Observable来发射转换的数据。 8 | 9 | ![](../images/chapter5_1.png) 10 | 11 | 考虑下我们已安装的应用列表。我们怎么才能够显示同样的列表,但是又要所有的名字都小写呢? 12 | 13 | 我们的`loadList()`函数可以改成这样: 14 | ```java 15 | private void loadList(List apps) { 16 | mRecyclerView.setVisibility(View.VISIBLE); 17 | Observable.from(apps) 18 | .map(new Func1(){ 19 | @Override 20 | public Appinfo call(AppInfo appInfo){ 21 | String currentName = appInfo.getName(); 22 | String lowerCaseName = currentName.toLowerCase(); 23 | appInfo.setName(lowerCaseName); 24 | return appInfo; 25 | } 26 | }) 27 | .subscribe(new Observer() { 28 | 29 | @Override 30 | public void onCompleted() { 31 | mSwipeRefreshLayout.setRefreshing(false); 32 | } 33 | 34 | @Override 35 | public void onError(Throwable e) { 36 | Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); 37 | mSwipeRefreshLayout.setRefreshing(false); 38 | } 39 | 40 | @Override 41 | public void onNext(AppInfo appInfo) { 42 | mAddedApps.add(appInfo); 43 | mAdapter.addApplication(mAddedApps.size() - 1,appInfo); 44 | } 45 | }); 46 | } 47 | ``` 48 | 49 | 正如你看到的,像往常一样创建我们发射的Observable之后,我们追加一个`map`调用,我们创建一个简单的函数来更新`AppInfo`对象并提供一个名字小写的新版本给观察者。 50 | 51 | ## FlatMap 52 | 53 | 在复杂的场景中,我们有一个这样的Observable:它发射一个数据序列,这些数据本身也可以发射Observable。RxJava的`flatMap()`函数提供一种铺平序列的方式,然后合并这些Observables发射的数据,最后将合并后的结果作为最终的Observable。 54 | 55 | ![](../images/chapter5_2.png) 56 | 57 | 当我们在处理可能有大量的Observables时,重要是记住任何一个Observables发生错误的情况,`flatMap()`将会触发它自己的`onError()`函数并放弃整个链。 58 | 59 | 重要的一点提示是关于合并部分:它允许交叉。正如上图所示,这意味着`flatMap()`不能够保证在最终生成的Observable中源Observables确切的发射顺序。 60 | 61 | ## ConcatMap 62 | 63 | RxJava的`concatMap()`函数解决了`flatMap()`的交叉问题,提供了一种能够把发射的值连续在一起的铺平函数,而不是合并它们,如下图所示: 64 | 65 | ![](../images/chapter5_3.png) 66 | 67 | ## FlatMapIterable 68 | 69 | 作为*map家族的一员,`flatMapInterable()`和`flatMap()`很像。仅有的本质不同是它将源数据两两结成对并生成Iterable,而不是原始数据项和生成的Observables。 70 | 71 | ![](../images/chapter5_4.png) 72 | 73 | ## SwitchMap 74 | 75 | 如下图所示,`switchMap()`和`flatMap()`很像,除了一点:每当源Observable发射一个新的数据项(Observable)时,它将取消订阅并停止监视之前那个数据项产生的Observable,并开始监视当前发射的这一个。 76 | 77 | ![](../images/chapter5_5.png) 78 | 79 | ## Scan 80 | 81 | RxJava的`scan()`函数可以看做是一个累积函数。`scan()`函数对原始Observable发射的每一项数据都应用一个函数,计算出函数的结果值,并将该值填充回可观测序列,等待和下一次发射的数据一起使用。 82 | 83 | 作为一个通用的例子,给出一个累加器: 84 | ```java 85 | Observable.just(1,2,3,4,5) 86 | .scan((sum,item) -> sum + item) 87 | .subscribe(new Subscriber() { 88 | @Override 89 | public void onCompleted() { 90 | Log.d("RXJAVA", "Sequence completed."); 91 | } 92 | 93 | @Override 94 | public void onError(Throwable e) { 95 | Log.e("RXJAVA", "Something went south!"); 96 | } 97 | 98 | @Override 99 | public void onNext(Integer item) { 100 | Log.d("RXJAVA", "item is: " + item); 101 | } 102 | }); 103 | ``` 104 | 我们得到的结果是: 105 | ```java 106 | RXJAVA: item is: 1 107 | RXJAVA: item is: 3 108 | RXJAVA: item is: 6 109 | RXJAVA: item is: 10 110 | RXJAVA: item is: 15 111 | RXJAVA: Sequence completed. 112 | ``` 113 | 我们也可以创建一个新版本的`loadList()`函数用来比较每个安装应用的名字从而创建一个名字长度递增的列表。 114 | 115 | ```java 116 | private void loadList(List apps) { 117 | mRecyclerView.setVisibility(View.VISIBLE); 118 | Observable.from(apps) 119 | .scan((appInfo,appInfo2) -> { 120 | if(appInfo.getName().length > appInfo2.getName().length()){ 121 | return appInfo; 122 | } else { 123 | return appInfo2; 124 | } 125 | }) 126 | .distinct() 127 | .subscribe(new Observer() { 128 | 129 | @Override 130 | public void onCompleted() { 131 | mSwipeRefreshLayout.setRefreshing(false); 132 | } 133 | 134 | @Override 135 | public void onError(Throwable e) { 136 | Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); 137 | mSwipeRefreshLayout.setRefreshing(false); 138 | } 139 | 140 | @Override 141 | public void onNext(AppInfo appInfo) { 142 | mAddedApps.add(appInfo); 143 | mAdapter.addApplication(mAddedApps.size() - 1,appInfo); 144 | } 145 | }); 146 | } 147 | ``` 148 | 149 | 结果如下: 150 | 151 | ![](../images/chapter5_6.png) 152 | 153 | 有一个`scan()`函数的变体,它用初始值作为第一个发射的值,方法签名看起来像:`scan(R,Func2)`,如下图中的例子这样: 154 | 155 | ![](../images/chapter5_7.png) 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /chapter5/transforming_observables.md: -------------------------------------------------------------------------------- 1 | # 转换Observables 2 | 3 | 在上一章中,我们探索了RxJava通用过滤方法。我们学习了如何使用`filter()`方法过滤我们不需要的值,如何使用`take()`得到发射元素的子集,如何使用`distinct()`函数来去除重复的。我们学习了如何借助`timeout()`,`sample()`,以及`debounce()`来利用时间。 4 | 5 | 这一章中,我们将通过学习如何变换可观测序列来创建一个能够更好的满足我们需求的序列。 -------------------------------------------------------------------------------- /chapter5/window.md: -------------------------------------------------------------------------------- 1 | # Window 2 | 3 | RxJava的`window()`函数和`buffer()`很像,但是它发射的是Observable而不是列表。下图展示了`window()`如何缓存3个数据项并把它们作为一个新的Observable发射出去。 4 | 5 | ![](../images/chapter5_13.png) 6 | 7 | 这些Observables中的每一个都发射原始Observable数据的一个子集,数量由`count`指定,最后发射一个`onCompleted()`结束。正如`buffer()`一样,`window()`也有一个`skip`变体,如下图所示: 8 | 9 | ![](../images/chapter5_14.png) -------------------------------------------------------------------------------- /chapter6/and_then_when.md: -------------------------------------------------------------------------------- 1 | # And,Then和When 2 | 3 | 在将来还有一些`zip()`满足不了的场景。如复杂的架构,或者是仅仅为了个人爱好,你可以使用And/Then/When解决方案。它们在RxJava的joins包下,使用Pattern和Plan作为中介,将发射的数据集合并到一起。 4 | 5 | ![](../images/chapter6_11.png) 6 | 7 | 我们的`loadList()`函数将会被修改从这样: 8 | ```java 9 | private void loadList(List apps) { 10 | 11 | mRecyclerView.setVisibility(View.VISIBLE); 12 | 13 | Observable observableApp = Observable.from(apps); 14 | 15 | Observable tictoc = Observable.interval(1, TimeUnit.SECONDS); 16 | 17 | Pattern2 pattern = JoinObservable.from(observableApp).and(tictoc); 18 | 19 | Plan0 plan = pattern.then(this::updateTitle); 20 | 21 | JoinObservable 22 | .when(plan) 23 | .toObservable() 24 | .observeOn(AndroidSchedulers.mainThread()) 25 | .subscribe(new Observer() { 26 | 27 | @Override 28 | public void onCompleted() { 29 | Toast.makeText(getActivity(), "Here is the list!", Toast.LENGTH_LONG).show(); 30 | } 31 | 32 | @Override 33 | public void onError(Throwable e) { 34 | mSwipeRefreshLayout.setRefreshing(false); 35 | Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); 36 | } 37 | 38 | @Override 39 | public void onNext(AppInfoappInfo) { 40 | if (mSwipeRefreshLayout.isRefreshing()) { 41 | mSwipeRefreshLayout.setRefreshing(false); 42 | } 43 | mAddedApps.add(appInfo); 44 | int position = mAddedApps.size() - 1; 45 | mAdapter.addApplication(position, appInfo); mRecyclerView.smoothScrollToPosition(position); 46 | } 47 | }); 48 | } 49 | ``` 50 | 和通常一样,我们有两个发射的序列,`observableApp`,发射我们安装的应用列表数据,`tictoc`每秒发射一个`Long`型整数。现在我们用`and()`连接源Observable和第二个Observable。 51 | 52 | ```java 53 | JoinObservable.from(observableApp).and(tictoc); 54 | ``` 55 | 这里创建一个`pattern`对象,使用这个对象我们可以创建一个`Plan`对象:"我们有两个发射数据的Observables,`then()`是做什么的?" 56 | ```java 57 | pattern.then(this::updateTitle); 58 | ``` 59 | 现在我们有了一个`Plan`对象并且当plan发生时我们可以决定接下来发生的事情。 60 | ```java 61 | .when(plan).toObservable() 62 | ``` 63 | 这时候,我们可以订阅新的Observable,正如我们总是做的那样。 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /chapter6/combinelatest.md: -------------------------------------------------------------------------------- 1 | # combineLatest 2 | 3 | RxJava的`combineLatest()`函数有点像`zip()`函数的特殊形式。正如我们已经学习的,`zip()`作用于最近未打包的两个Observables。相反,`combineLatest()`作用于最近发射的数据项:如果`Observable1`发射了A并且`Observable2`发射了B和C,`combineLatest()`将会分组处理AB和AC,如下图所示: 4 | 5 | ![](../images/chapter6_9.png) 6 | 7 | `combineLatest()`函数接受二到九个Observable作为参数,如果有需要的话或者单个Observables列表作为参数。 8 | 9 | 从之前的例子中把`loadList()`函数借用过来,我们可以修改一下来用于`combineLatest()`实现“真实世界”这个例子: 10 | ```java 11 | private void loadList(List apps) { 12 | mRecyclerView.setVisibility(View.VISIBLE); 13 | Observable appsSequence = Observable.interval(1000, TimeUnit.MILLISECONDS) 14 | .map(position ->apps.get(position.intValue())); 15 | Observable tictoc = Observable.interval(1500, TimeUnit.MILLISECONDS); 16 | Observable.combineLatest(appsSequence, tictoc, 17 | this::updateTitle) 18 | .observeOn(AndroidSchedulers.mainThread()) 19 | .subscribe(new Observer() { 20 | 21 | @Override 22 | public void onCompleted() { 23 | Toast.makeText(getActivity(), "Here is the list!", Toast.LENGTH_LONG).show(); 24 | } 25 | 26 | @Override 27 | public void onError(Throwable e) { 28 | mSwipeRefreshLayout.setRefreshing(false); 29 | Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); 30 | } 31 | 32 | @Override 33 | public void onNext(AppInfoappInfo) { 34 | if (mSwipeRefreshLayout.isRefreshing()) { 35 | mSwipeRefreshLayout.setRefreshing(false); 36 | } 37 | mAddedApps.add(appInfo); 38 | int position = mAddedApps.size() - 1; 39 | mAdapter.addApplication(position, appInfo); 40 | mRecyclerView.smoothScrollToPosition(position); 41 | } 42 | }); 43 | } 44 | ``` 45 | 这我们使用了两个Observables:一个是每秒钟从我们已安装的应用列表发射一个App数据,第二个是每隔1.5秒发射一个`Long`型整数。我们将他们结合起来并执行`updateTitle()`函数,结果如下: 46 | 47 | ![](../images/chapter6_10.png) 48 | 49 | 正如你看到的,由于不同的时间间隔,`AppInfo`对象如我们所预料的那样有时候会重复。 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /chapter6/combining_observables.md: -------------------------------------------------------------------------------- 1 | # 组合Observables 2 | 3 | 上一章中,我们学到如何转换可观测序列。我们也看到了`map()`,`scan()`,`groupBY()`,以及更多有用的函数的实际例子,它们帮助我们操作Observable来创建我们想要的Observable。 4 | 5 | 本章中,我们将研究组合函数并学习如何同时处理多个Observables来创建我们想要的Observable。 -------------------------------------------------------------------------------- /chapter6/join.md: -------------------------------------------------------------------------------- 1 | # Join 2 | 3 | 前面两个方法,`zip()`和`merge()`方法作用在发射数据的范畴内,在决定如何操作值之前有些场景我们需要考虑时间的。RxJava的`join()`函数基于时间窗口将两个Observables发射的数据结合在一起。 4 | 5 | ![](../images/chapter6_6.png) 6 | 7 | 为了正确的理解上一张图,我们解释下`join()`需要的参数: 8 | 9 | * 第二个Observable和源Observable结合。 10 | * `Func1`参数:在指定的由时间窗口定义时间间隔内,源Observable发射的数据和从第二个Observable发射的数据相互配合返回的Observable。 11 | * `Func1`参数:在指定的由时间窗口定义时间间隔内,第二个Observable发射的数据和从源Observable发射的数据相互配合返回的Observable。 12 | * `Func2`参数:定义已发射的数据如何与新发射的数据项相结合。 13 | * 14 | 如下练习的例子,我们可以修改`loadList()`函数像下面这样: 15 | ```java 16 | private void loadList(List apps) { 17 | mRecyclerView.setVisibility(View.VISIBLE); 18 | 19 | Observable appsSequence = 20 | Observable.interval(1000, TimeUnit.MILLISECONDS) 21 | .map(position -> { 22 | return apps.get(position.intValue()); 23 | }); 24 | 25 | Observable tictoc = Observable.interval(1000,TimeUnit.MILLISECONDS); 26 | 27 | appsSequence.join( 28 | tictoc, 29 | appInfo -> Observable.timer(2,TimeUnit.SECONDS), 30 | time -> Observable.timer(0, TimeUnit.SECONDS), 31 | this::updateTitle) 32 | .observeOn(AndroidSchedulers.mainThread()) 33 | .take(10) 34 | .subscribe(new Observer() { 35 | @Override 36 | public void onCompleted() { 37 | Toast.makeText(getActivity(), "Here is the list!", Toast.LENGTH_LONG).show(); 38 | } 39 | 40 | @Override 41 | public void onError(Throwable e) { 42 | mSwipeRefreshLayout.setRefreshing(false); 43 | Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); 44 | } 45 | 46 | @Override 47 | public void onNext(AppInfoappInfo) { 48 | if (mSwipeRefreshLayout.isRefreshing()) { 49 | mSwipeRefreshLayout.setRefreshing(false); 50 | } 51 | mAddedApps.add(appInfo); 52 | int position = mAddedApps.size() - 1; 53 | mAdapter.addApplication(position, appInfo); 54 | mRecyclerView.smoothScrollToPosition(position); 55 | } 56 | }); 57 | } 58 | ``` 59 | 60 | 我们有一个新的对象`appsSequence`,它是一个每秒从我们已安装的app列表发射app数据的可观测序列。`tictoc`这个Observable数据每秒只发射一个新的`Long`型整数。为了合并它们,我们需要指定两个`Func1`变量: 61 | 62 | ```java 63 | appInfo -> Observable.timer(2, TimeUnit.SECONDS) 64 | 65 | time -> Observable.timer(0, TimeUnit.SECONDS) 66 | ``` 67 | 上面描述了两个时间窗口。下面一行描述我们如何使用`Func2`将两个发射的数据结合在一起。 68 | ```java 69 | this::updateTitle 70 | ``` 71 | 72 | 结果如下: 73 | 74 | ![](../images/chapter6_7.png) 75 | 76 | 它看起来有点乱,但是注意app的名字和我们指定的时间窗口,我们可以看到:一旦第二个数据发射了我们就会将它与源数据结合,但我们用同一个源数据有2秒钟。这就是为什么标题重复数字增加的原因。 77 | 78 | 值得一提的是,为了简单起见,也有一个`join()`操作符作用于字符串然后简单的和发射的字符串连接成最终的字符串。 79 | 80 | ![](../images/chapter6_8.png) 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /chapter6/merge.md: -------------------------------------------------------------------------------- 1 | # Merge 2 | 3 | 在”异步的世界“中经常会创建这样的场景,我们有多个来源但是又只想有一个结果:多输入,单输出。RxJava的`merge()`方法将帮助你把两个甚至更多的Observables合并到他们发射的数据项里。下图给出了把两个序列合并在一个最终发射的Observable。 4 | 5 | ![](../images/chapter6_1.png) 6 | 7 | 正如你看到的那样,发射的数据被交叉合并到一个Observable里面。注意如果你同步的合并Observable,它们将连接在一起并且不会交叉。 8 | 9 | 像往常一样,我们用我们的App和已安装的App列表来创建了一个“真实世界”的例子。为此我们还需要第二个Observable。我们可以创建一个单独的应用列表然后让它逆序排列。当然这没有实际的意义,只是为了这个例子。对于第二个列表,我们的`loadList()`函数像下面这样: 10 | ```java 11 | private void loadList(List apps) { 12 | mRecyclerView.setVisibility(View.VISIBLE); 13 | List reversedApps = Lists.reverse(apps); 14 | Observable observableApps =Observable.from(apps); 15 | Observable observableReversedApps =Observable.from(reversedApps); 16 | Observable mergedObserbable = Observable.merge(observableApps,observableReversedApps); 17 | 18 | mergedObserbable.subscribe(new Observer(){ 19 | @Override 20 | public void onCompleted() { 21 | mSwipeRefreshLayout.setRefreshing(false); 22 | Toast.makeText(getActivity(), "Here is the list!", Toast.LENGTH_LONG).show(); 23 | } 24 | 25 | @Override 26 | public void onError(Throwable e) { 27 | Toast.makeText(getActivity(), "One of the two Observable threw an error!", Toast.LENGTH_SHORT).show(); 28 | mSwipeRefreshLayout.setRefreshing(false); 29 | } 30 | 31 | @Override 32 | public void onNext(AppInfoappInfo) { 33 | mAddedApps.add(appInfo); 34 | mAdapter.addApplication(mAddedApps.size() - 1, appInfo); 35 | } 36 | }); 37 | } 38 | ``` 39 | 40 | 我们创建了Observable和observableApps数据项以及新的observableReversedApps逆序列表。使用`Observable.merge()`,我们可以创建新的`Observable MergedObservable`,它在单个可观测序列中发射源Observables发出的所有数据。 41 | 42 | 正如你能看到的,每个方法签名都是一样的,因此我们的观察者无需在意任何不同就可以复用代码。结果如下: 43 | ![](../images/chapter6_2.png) 44 | 45 | 注意错误时的toast消息,你可以认为每个Observable抛出的错误都将会打断合并。如果你需要避免这种情况,RxJava提供了`mergeDelayError()`,它能从一个Observable中继续发射数据即便是其中有一个抛出了错误。当所有的Observables都完成时,`mergeDelayError()`将会发射`onError()`,如下图所示: 46 | 47 | ![](../images/chapter6_3.png) 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /chapter6/startwith.md: -------------------------------------------------------------------------------- 1 | # StartWith 2 | 3 | 我们已经学到如何连接多个Observables并追加指定的值到一个发射序列里。RxJava的`startWith()`是`concat()`的对应部分。正如`concat()`向发射数据的Observable追加数据那样,在Observable开始发射他们的数据之前, `startWith()`通过传递一个参数来先发射一个数据序列。 4 | 5 | ![](../images/chapter6_13.png) -------------------------------------------------------------------------------- /chapter6/summary6.md: -------------------------------------------------------------------------------- 1 | # 总结 2 | 3 | 这章中,我们学习了如何将两个或者更多个Observable结合来创建一个新的可观测序列。我们将能够`merge` Observable,`join` Observables ,`zip` Observables 并在几种情况下把他们结合在一起。 4 | 5 | 下一章,我们将介绍调度器,它将很容易的帮助我们创建主线程以及提高我们应用程序的性能。我们也将学习如何正确的执行长任务或者I/O任务来获得更好的性能。 -------------------------------------------------------------------------------- /chapter6/switch.md: -------------------------------------------------------------------------------- 1 | # Switch 2 | 3 | 有这样一个复杂的场景就是在一个`subscribe-unsubscribe`的序列里我们能够从一个Observable自动取消订阅来订阅一个新的Observable。 4 | 5 | RxJava的`switch()`,正如定义的,将一个发射多个Observables的Observable转换成另一个单独的Observable,后者发射那些Observables最近发射的数据项。 6 | 7 | 给出一个发射多个Observables序列的源Observable,`switch()`订阅到源Observable然后开始发射由第一个发射的Observable发射的一样的数据。当源Observable发射一个新的Observable时,`switch()`立即取消订阅前一个发射数据的Observable(因此打断了从它那里发射的数据流)然后订阅一个新的Observable,并开始发射它的数据。 8 | 9 | ![](../images/chapter6_12.png) 10 | 11 | -------------------------------------------------------------------------------- /chapter6/zip.md: -------------------------------------------------------------------------------- 1 | # ZIP 2 | 3 | 在一种新的可能场景中处理多个数据来源时会带来:多从个Observables接收数据,处理它们,然后将它们合并成一个新的可观测序列来使用。RxJava有一个特殊的方法可以完成:`zip()`合并两个或者多个Observables发射出的数据项,根据指定的函数`Func*`变换它们,并发射一个新值。下图展示了`zip()`方法如何处理发射的“numbers”和“letters”然后将它们合并一个新的数据项: 4 | 5 | ![](../images/chapter6_4.png) 6 | 7 | 对于“真实世界”的例子来说,我们将使用已安装的应用列表和一个新的动态的Observable来让例子变得有点有趣味。 8 | 9 | ```java 10 | Observable tictoc = Observable.interval(1, TimeUnit.SECONDS); 11 | ``` 12 | `tictoc`Observable变量使用`interval()`函数每秒生成一个Long类型的数据:虽简单但有效,正如之前所说的,我们需要一个`Func`对象。因为它需要传两个参数,所以是`Func2`: 13 | 14 | ```java 15 | private AppInfo updateTitle(AppInfoappInfo, Long time) { 16 | appInfo.setName(time + " " + appInfo.getName()); 17 | return appInfo; 18 | } 19 | ``` 20 | 现在我们的`loadList()`函数变成这样: 21 | ```java 22 | private void loadList(List apps) { 23 | mRecyclerView.setVisibility(View.VISIBLE); 24 | Observable observableApp = Observable.from(apps); 25 | 26 | Observable tictoc = Observable.interval(1, TimeUnit.SECONDS); 27 | 28 | Observable.zip(observableApp, tictoc, 29 | (AppInfo appInfo, Long time) -> updateTitle(appInfo, time)) 30 | .observeOn(AndroidSchedulers.mainThread()) 31 | .subscribe(new Observer() { 32 | @Override 33 | public void onCompleted() { 34 | Toast.makeText(getActivity(), "Here is the list!", Toast.LENGTH_LONG).show(); 35 | } 36 | 37 | @Override 38 | public void onError(Throwable e) { 39 | mSwipeRefreshLayout.setRefreshing(false); 40 | Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); 41 | } 42 | 43 | @Override 44 | public void onNext(AppInfoappInfo) { 45 | if (mSwipeRefreshLayout.isRefreshing()) { 46 | mSwipeRefreshLayout.setRefreshing(false); 47 | } 48 | mAddedApps.add(appInfo); 49 | int position = mAddedApps.size() - 1; 50 | mAdapter.addApplication(position, appInfo); 51 | mRecyclerView.smoothScrollToPosition(position); 52 | } 53 | }); 54 | } 55 | ``` 56 | 正如你看到的那样,`zip()`函数有三个参数:两个Observables和一个`Func2`。 57 | 58 | 仔细一看会发现`observeOn()`函数。它将在下一章中讲解:现在我们可以小试一下。 59 | 60 | 结果如下: 61 | 62 | ![](../images/chapter6_5.png) 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /chapter7/avoiding_blocking_io_operations.md: -------------------------------------------------------------------------------- 1 | # 避免阻塞I/O的操作 2 | 3 | 阻塞I/O的操作会导致App必须等待结果返回(阻塞结束)才能进行下一步操作。在UI线程上执行一个阻塞操作会将UI强行卡住,直接造成很糟糕的用户体验。 4 | 5 | 我们激活`StrictMode`后,我们开始收到了关于我们的App错误操作磁盘I/O的不良信息。 6 | 7 | ```java 8 | D/StrictMode StrictMode policy violation; ~duration=998 ms: android.os.StrictMode$StrictModeDiskReadViolation: policy=31 violation=2 9 | at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk (StrictMode.java:1135) 10 | at libcore.io.BlockGuardOs.open(BlockGuardOs.java:106) at libcore.io.IoBridge.open(IoBridge.java:393) 11 | at java.io.FileOutputStream.(FileOutputStream.java:88) 12 | at android.app.ContextImpl.openFileOutput(ContextImpl.java:918) 13 | at android.content.ContextWrapper.openFileOutput(ContextWrapper. java:185) 14 | at com.packtpub.apps.rxjava_essentials.Utils.storeBitmap (Utils.java:30) 15 | ``` 16 | 上一条信息告诉我们`Utils.storeBitmap()`函数执行完耗时998ms:在UI线程上近1秒的不必要的工作和App上近1秒不必要的迟钝。这是因为我们以阻塞的方式访问磁盘。我们的`storeBitmap()`函数包含了: 17 | ```java 18 | FileOutputStream fOut = context.openFileOutput(filename, Context.MODE_PRIVATE); 19 | ``` 20 | 它直接访问智能手机的固态存储然后就慢了。我们该如何提高访问速度呢?`storeBitmap()`函数保存了已安装App的图标。他的返回值类型为`void`,因此在执行下一个操作前我们毫无理由去等待直到它完成。我们可以启动它并让它执行在不同的线程。近几年来Android的线程管理发生了许多变化,导致App出现诡异的行为。我们可以使用`AsyncTask`,但是我们要避免掉入前几章里的`onPre... onPost...doInBackGround`地狱。下面我们将换用RxJava的方式。调度器万岁! 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /chapter7/executing_a_network_task.md: -------------------------------------------------------------------------------- 1 | # 执行网络任务 2 | 3 | 在当今99%的移动应用中网络都是必不可缺的一部分:总是需要连接远程服务器来检索App需要的信息。 4 | 5 | 作为网络访问的第一个案例,我们将创建下面这样一个场景: 6 | 7 | * 加载一个进度条。 8 | * 用一个按钮开始文件下载。 9 | * 下载过程中更新进度条。 10 | * 下载完后开始视频播放。 11 | 12 | 我们的用户界面非常简单,我们只需要一个有趣的进度条和一个下载按钮。 13 | 14 | ![](../images/chapter7_5.png) 15 | 16 | 首先,我们创建`mDownloadProgress` 17 | 18 | ```java 19 | private PublishSubject mDownloadProgress = PublishSubject.create(); 20 | ``` 21 | 这个主题我们用来管理进度的更新,它和`download`函数协同工作。 22 | ```java 23 | private boolean downloadFile(String source, String destination) { 24 | boolean result = false; 25 | InputStream input = null; 26 | OutputStream output = null; 27 | HttpURLConnection connection = null; 28 | try { 29 | URL url = new URL(source); 30 | connection = (HttpURLConnection) url.openConnection(); 31 | connection.connect(); 32 | if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { 33 | return false; 34 | } 35 | int fileLength = connection.getContentLength(); 36 | input = connection.getInputStream(); 37 | output = new FileOutputStream(destination); 38 | byte data[] = new byte[4096]; 39 | long total = 0; 40 | int count; 41 | while ((count = input.read(data)) != -1) { 42 | total += count; 43 | if (fileLength >0) { 44 | int percentage = (int) (total * 100 / fileLength); 45 | mDownloadProgress.onNext(percentage); 46 | } 47 | output.write(data, 0, count); 48 | } 49 | mDownloadProgress.onCompleted(); 50 | result = true; 51 | } catch (Exception e) { 52 | mDownloadProgress.onError(e); 53 | } finally { 54 | try { 55 | if (output != null) { 56 | output.close(); 57 | } 58 | if (input != null) { 59 | input.close(); 60 | } 61 | } catch (IOException e) { 62 | mDownloadProgress.onError(e); 63 | } 64 | if (connection != null) { 65 | connection.disconnect(); 66 | mDownloadProgress.onCompleted(); 67 | } 68 | } 69 | return result; 70 | } 71 | ``` 72 | 上面的这段代码将会触发`NetworkOnMainThreadException`异常。我们可以创建RxJava版本的函数进入我们挚爱的响应式世界来解决这个问题: 73 | 74 | ```java 75 | private Observable obserbableDownload(String source, String destination) { 76 | return Observable.create(subscriber -> { 77 | try { 78 | boolean result = downloadFile(source, destination); 79 | if (result) { 80 | subscriber.onNext(true); 81 | subscriber.onCompleted(); 82 | } else { 83 | subscriber.onError(new Throwable("Download failed.")); 84 | } 85 | } catch (Exception e) { 86 | subscriber.onError(e); 87 | } 88 | }); 89 | } 90 | ``` 91 | 现在我们需要触发下载操作,点击下载按钮: 92 | ```java 93 | @OnClick(R.id.button_download) 94 | void download() { 95 | mButton.setText(getString(R.string.downloading)); 96 | mButton.setClickable(false); 97 | mDownloadProgress.distinct() 98 | .observeOn(AndroidSchedulers.mainThread()) 99 | .subscribe(new Observer() { 100 | 101 | @Override 102 | public void onCompleted() { 103 | App.L.debug("Completed"); 104 | } 105 | 106 | @Override 107 | public void onError(Throwable e) { 108 | App.L.error(e.toString()); 109 | } 110 | 111 | @Override 112 | public void onNext(Integer progress) { 113 | mArcProgress.setProgress(progress); 114 | } 115 | }); 116 | 117 | String destination = "sdcardsoftboy.avi"; 118 | obserbableDownload("http://archive.blender.org/fileadmin/movies/softboy.avi", destination) 119 | .subscribeOn(Schedulers.io()) 120 | .observeOn(AndroidSchedulers.mainThread()) 121 | .subscribe(success -> { 122 | resetDownloadButton(); 123 | Intent intent = new Intent(android.content.Intent.ACTION_VIEW); 124 | File file = new File(destination); 125 | intent.setDataAndType(Uri.fromFile(file),"video/avi"); 126 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 127 | startActivity(intent); 128 | }, error -> { 129 | Toast.makeText(getActivity(), "Something went south", Toast.LENGTH_SHORT).show(); 130 | resetDownloadButton(); 131 | }); 132 | } 133 | ``` 134 | 我们使用Butter Knife的注解`@OnClick`来绑定按钮的方法并更新按钮信息和点击状态:我们不想让用户点击多次从而触发多次下载事件。 135 | 136 | 然后,我们创建一个subscription来观察下载进度并相应的更新进度条。很明显,我们订阅在主线程是因为进度条是UI元素。 137 | 138 | ```java 139 | obserbableDownload("http://archive.blender.org/fileadmin/movies/softboy.avi", "sdcardsoftboy.avi";) 140 | ``` 141 | 这是一个下载Observable。网络调用是一个I/O任务,理应使用I/O调度器。当下载完成,就会在`onNext()` 中启动视频播放器,并且播放器将会在目标路径找到下载的文件.。 142 | 143 | 下图展示了下载进度和视频播放器选择对话框: 144 | 145 | ![](../images/chapter7_6.png) 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /chapter7/handing_a_long_task.md: -------------------------------------------------------------------------------- 1 | # 处理耗时的任务 2 | 3 | 我们已经知道如何处理缓慢的I/O操作。让我们看一个与I/O无关的耗时的任务。例如,我们修改`loadList()`函数并创建一个新的`slow`函数发射我们已安装的app数据。 4 | 5 | ```java 6 | private Observable getObservableApps(List apps) { 7 | return Observable .create(subscriber -> { 8 | for (double i = 0; i < 1000000000; i++) { 9 | double y = i * i; 10 | } 11 | for (AppInfo app : apps) { 12 | subscriber.onNext(app); 13 | } 14 | subscriber.onCompleted(); 15 | }); 16 | } 17 | ``` 18 | 19 | 正如你看到的,这个函数执行了一些毫无意义的计算,只是针对这个例子消耗时间,然后从`List`对象中发射我们的`AppInfo`数据,现在,我们重排`loadList()`函数如下: 20 | 21 | ```java 22 | private void loadList(List apps) { 23 | mRecyclerView.setVisibility(View.VISIBLE); 24 | getObservableApps(apps) 25 | .subscribe(new Observer() { 26 | @Override 27 | public void onCompleted() { 28 | mSwipeRefreshLayout.setRefreshing(false); 29 | Toast.makeText(getActivity(), "Here is the list!", Toast.LENGTH_LONG).show(); 30 | } 31 | 32 | @Override 33 | public void onError(Throwable e) { 34 | Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); 35 | mSwipeRefreshLayout.setRefreshing(false); 36 | } 37 | 38 | @Override 39 | public void onNext(AppInfo appInfo) { 40 | mAddedApps.add(appInfo); 41 | mAdapter.addApplication(mAddedApps.size() - 1, appInfo); 42 | } 43 | }); 44 | } 45 | ``` 46 | 47 | 如果我们运行这段代码,当我们点击`Navigation Drawer`菜单项时App将会卡住一会,然后你能看到下图中半关闭的菜单: 48 | 49 | ![](../images/chapter7_3.png) 50 | 51 | 如果我们不够走运的话,我们可以看到下图中经典的ANR信息框: 52 | 53 | ![](../images/chapter7_4.png) 54 | 55 | 可以确定的是,我们将会看到下面在logcat中不愉快的信息: 56 | 57 | ```java 58 | I/Choreographer Skipped 598 frames! The application may be doing too much work on its main thread. 59 | ``` 60 | 61 | 这条信息比较清楚,Android在告诉我们用户体验非常差的原因是我们用不必要的工作量阻塞了UI线程。但是我们已经知道了如何处理它:我们有调度器!我们只须添加几行代码到我们的Observable链中就能去掉加载慢和`Choreographer`信息: 62 | 63 | ```java 64 | getObservableApps(apps) 65 | .onBackpressureBuffer() 66 | .subscribeOn(Schedulers.computation()) 67 | .observeOn(AndroidSchedulers.mainThread()) 68 | .subscribe(new Observer() { [...] 69 | ``` 70 | 用这几行代码,我们将可以快速关掉`Navigation Drawer`,一个漂亮的进度条,一个工作在独立的线程缓慢执行的计算任务,并在主线程返回结果让我们更新已安装的应用列表。 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /chapter7/nonblocking_io_operations.md: -------------------------------------------------------------------------------- 1 | # 非阻塞I/O操作 2 | 3 | 现在我们知道如何在一个指定I/O调度器上来调度一个任务,我们可以修改`storeBitmap()`函数并再次检查`StrictMode`的不合规做法。为了这个例子,我们可以在新的`blockingStoreBitmap()`函数中重排代码。 4 | 5 | ```java 6 | private static void blockingStoreBitmap(Context context, Bitmap bitmap, String filename) { 7 | FileOutputStream fOut = null; 8 | try { 9 | fOut = context.openFileOutput(filename, Context.MODE_PRIVATE); 10 | bitmap.compress(Bitmap.CompressFormat.PNG, 100, fOut); 11 | fOut.flush(); 12 | fOut.close(); 13 | } catch (Exception e) { 14 | throw new RuntimeException(e); 15 | } finally { 16 | try { 17 | if (fOut != null) { 18 | fOut.close(); 19 | } 20 | } catch (IOException e) { 21 | throw new RuntimeException(e); 22 | } 23 | } 24 | } 25 | ``` 26 | 现在我们可以使用`Schedulers.io()`创建非阻塞的版本: 27 | 28 | ```java 29 | public static void storeBitmap(Context context, Bitmap bitmap, String filename) { 30 | Schedulers.io().createWorker().schedule(() -> { 31 | blockingStoreBitmap(context, bitmap, filename); 32 | }); 33 | } 34 | ``` 35 | 36 | 每次我们调用`storeBitmap()`,RxJava处理创建所有它需要从I / O线程池一个特定的I/ O线程执行我们的任务。所有要执行的操作都避免在UI线程执行并且我们的App比之前要快上1秒:logcat上也不再有`StrictMode`的不合规做法。 37 | 38 | 下图展示了我们在`storeBitmap()`场景看到的两种方法的不同: 39 | 40 | ![](../images/chapter7_1.png) 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /chapter7/schedulers.md: -------------------------------------------------------------------------------- 1 | # Schedulers 2 | 3 | 调度器以一种最简单的方式将多线程用在你的Apps的中。它们时RxJava重要的一部分并能很好地与Observables协同工作。它们无需处理实现、同步、线程、平台限制、平台变化而可以提供一种灵活的方式来创建并发程序。 4 | 5 | RxJava提供了5种调度器: 6 | 7 | * `.io()` 8 | * `.computation()` 9 | * `.immediate()` 10 | * `.newThread()` 11 | * `.trampoline()` 12 | 13 | 让我们一个一个的来看下它们: 14 | 15 | ## Schedulers.io() 16 | 17 | 这个调度器时用于I/O操作。它基于根据需要,增长或缩减来自适应的线程池。我们将使用它来修复我们之前看到的`StrictMode`违规做法。由于它专用于I/O操作,所以并不是RxJava的默认方法;正确的使用它是由开发者决定的。 18 | 19 | 重点需要注意的是线程池是无限制的,大量的I/O调度操作将创建许多个线程并占用内存。一如既往的是,我们需要在性能和简捷两者之间找到一个有效的平衡点。 20 | 21 | ## Schedulers.computation() 22 | 23 | 这个是计算工作默认的调度器,它与I/O操作无关。它也是许多RxJava方法的默认调度器:`buffer()`,`debounce()`,`delay()`,`interval()`,`sample()`,`skip()`。 24 | 25 | ## Schedulers.immediate() 26 | 27 | 这个调度器允许你立即在当前线程执行你指定的工作。它是`timeout()`,`timeInterval()`,以及`timestamp()`方法默认的调度器。 28 | 29 | ## Schedulers.newThread() 30 | 31 | 这个调度器正如它所看起来的那样:它为指定任务启动一个新的线程。 32 | 33 | ## Schedulers.trampoline() 34 | 35 | 当我们想在当前线程执行一个任务时,并不是立即,我们可以用`.trampoline()`将它入队。这个调度器将会处理它的队列并且按序运行队列中每一个任务。它是`repeat()`和`retry()`方法默认的调度器。 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /chapter7/schedulers_defeating_the_android_mainthread_issue.md: -------------------------------------------------------------------------------- 1 | # Schedulers-解决Android主线程问题 2 | 3 | 前面一章是最后一章关于RxJava的Observable的创建和操作的章节。我们学习到了如何将两个或更多的Observables合并在一起,`join`它们,`zip`它们,`merge`它们以及如何创建一个新的Observable来满足我们特殊的需求。 4 | 5 | 本章中,我们提升标准看看如何使用RxJava的调度器来处理多线程和并发编程的问题。我们将学习到如何以响应式的方式创建网络操作,内存访问,以及耗时任务。 -------------------------------------------------------------------------------- /chapter7/strictmode.md: -------------------------------------------------------------------------------- 1 | # StrictMode 2 | 3 | 为了获得更多出现在代码中的关于公共问题的信息,我们激活了`StrictMode`模式。 4 | 5 | `StrictMode`帮助我们侦测敏感的活动,如我们无意的在主线程执行磁盘访问或者网络调用。正如你所知道的,在主线程执行繁重的或者长时的任务是不可取的。因为Android应用的主线程时UI线程,它被用来处理和UI相关的操作:这也是获得更平滑的动画体验和响应式App的唯一方法。 6 | 7 | 为了在我们的App中激活`StrictMode`,我们只需要在`MainActivity`中添加几行代码,即`onCreate()`方法中这样: 8 | ```java 9 | @Override 10 | public void onCreate() { 11 | super.onCreate(); 12 | if (BuildConfig.DEBUG) { 13 | StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build()); 14 | StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build()); 15 | } 16 | } 17 | ``` 18 | 我们并不想它总是激活着,因此我们只在debug构建时使用。这种配置将报告每一种关于主线程用法的违规做法,并且这些做法都可能与内存泄露有关:`Activities`、`BroadcastReceivers`、`Sqlite`等对象。 19 | 20 | 选择了`penaltyLog()`,当违规做法发生时,`StrictMode`将会在logcat打印一条信息。 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /chapter7/subscribeon_and_observeon.md: -------------------------------------------------------------------------------- 1 | # SubscribeOn and ObserveOn 2 | 3 | 我们学到了如何在一个调度器上运行一个任务。但是我们如何利用它来和Observables一起工作呢?RxJava提供了`subscribeOn()`方法来用于每个Observable对象。`subscribeOn()`方法用`Scheduler`来作为参数并在这个Scheduler上执行Observable调用。 4 | 5 | 在“真实世界”这个例子中,我们调整`loadList()`函数。首先,我们需要一个新的`getApps()`方法来检索已安装的应用列表: 6 | 7 | ```java 8 | private Observable getApps() { 9 | return Observable.create(subscriber -> { 10 | List apps = new ArrayList<>(); 11 | SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE); 12 | Type appInfoType = new TypeToken>(){}.getType(); 13 | String serializedApps = sharedPref.getString("APPS", ""); 14 | if (!"".equals(serializedApps)) { 15 | apps = new Gson().fromJson(serializedApps,appInfoType); 16 | } 17 | for (AppInfo app : apps) { 18 | subscriber.onNext(app); 19 | } 20 | subscriber.onCompleted(); 21 | }); 22 | } 23 | ``` 24 | `getApps()`方法返回一个`AppInfo`的Observable。它先从Android的SharePreferences读取到已安装的应用程序列表。反序列化,并一个接一个的发射AppInfo数据。使用新的方法来检索列表,`loadList()`函数改成下面这样: 25 | ```java 26 | private void loadList() { 27 | mRecyclerView.setVisibility(View.VISIBLE); 28 | getApps().subscribe(new Observer() { 29 | @Override 30 | public void onCompleted() { 31 | mSwipeRefreshLayout.setRefreshing(false); 32 | Toast.makeText(getActivity(), "Here is the list!", Toast.LENGTH_LONG).show(); 33 | } 34 | 35 | @Override 36 | public void onError(Throwable e) { 37 | Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); 38 | mSwipeRefreshLayout.setRefreshing(false); 39 | } 40 | 41 | @Override 42 | public void onNext(AppInfo appInfo) { 43 | mAddedApps.add(appInfo); 44 | mAdapter.addApplication(mAddedApps.size() - 1, appInfo); 45 | } 46 | }); 47 | } 48 | ``` 49 | 如果我们运行代码,`StrictMode`将会报告一个不合规操作,这是因为`SharePreferences`会减慢I/O操作。我们所需要做的是指定`getApps()`需要在调度器上执行: 50 | 51 | ```java 52 | 53 | getApps().subscribeOn(Schedulers.io()) 54 | .subscribe(new Observer() { [...] 55 | ``` 56 | `Schedulers.io()`将会去掉`StrictMode`的不合规操作,但是我们的App现在崩溃了是因为: 57 | ```java 58 | at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.jav a:58) 59 | at java.util.concurrent.Executors$RunnableAdapter.call(Executors. java:422) 60 | at java.util.concurrent.FutureTask.run(FutureTask.java:237) 61 | at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutu reTask.access$201(ScheduledThreadPoolExecutor.java:152) 62 | at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutu reTask.run(ScheduledThreadPoolExecutor.java:265) 63 | at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolEx ecutor.java:1112) 64 | at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolE xecutor.java:587) 65 | at java.lang.Thread.run(Thread.java:841) Caused by: 66 | android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. 67 | 68 | ``` 69 | Only the original thread that created a view hierarchy can touch its views. 70 | 71 | 我们再次回到Android的世界。这条信息简单的告诉我们我们试图在一个非UI线程来修改UI操作。意思是我们需要在I/O调度器上执行我们的代码。因此我们需要和I/O调度器一起执行代码,但是当结果返回时我们需要在UI线程上操作。RxJava让你能够订阅一个指定的调度器并观察它。我们只需在`loadList()`函数添加几行代码,那么每一项就都准备好了: 72 | 73 | ```java 74 | getApps() 75 | .onBackpressureBuffer() 76 | .subscribeOn(Schedulers.io()) 77 | .observeOn(AndroidSchedulers.mainThread()) 78 | .subscribe(new Observer() { [...] 79 | ``` 80 | `observeOn()`方法将会在指定的调度器上返回结果:如例子中的UI线程。`onBackpressureBuffer()`方法将告诉Observable发射的数据如果比观察者消费的数据要更快的话,它必须把它们存储在缓存中并提供一个合适的时间给它们。做完这些工作之后,如果我们运行App,就会出现已安装的程序列表: 81 | 82 | ![](../images/chapter7_2.png) 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /chapter7/summary7.md: -------------------------------------------------------------------------------- 1 | # 总结 2 | 3 | 这一章中,我们学习了如何简单的将多线程应用在我们的App中。RxJava为此提供了极其实用的工具:调度器。调度器以及不同应用场景下的优化方案一起,将我们从`StrictMode`中的不合法操作以及阻塞I/O的方法中解放出来。我们现在可以用简单的,响应式的,并在整个App中保持一致的方式来访问本地存储和网络。 4 | 5 | 下一章中,我们将会冒更大的险来创建一个正儿八经的App,并使用Square公司开源的REST API库[Retrofit][1]来获取不同的远程数据并创建一个复杂的material design UI。 6 | 7 | [1]: http://https://github.com/square/retrofit "GitHub" -------------------------------------------------------------------------------- /chapter8/app_structure.md: -------------------------------------------------------------------------------- 1 | # App架构 2 | 3 | 我们不使用任何MVC,MVP,或者MVVM模式。因为那不是这本书的目的,因此我们的`Activity`类将包含我们需要创建和展示用户列表的所有逻辑。 -------------------------------------------------------------------------------- /chapter8/creating_activity_class.md: -------------------------------------------------------------------------------- 1 | # 创建Activity类 2 | 3 | 我们将在`onCreate()`方法里创建`SwipeRefreshLayout`和`RecyclerView`;我们有一个`refreshList()`方法来处理用户列表的获取和展示,`showRefreshing()`方法来管理进度条和`RecyclerView`的显示。 4 | 5 | 我们的`refreshList()`函数看起来如下: 6 | ```java 7 | private void refreshList() { 8 | showRefresh(true); 9 | mSeApiManager.getMostPopularSOusers(10) 10 | .subscribe(users -> { 11 | showRefresh(false); 12 | mAdapter.updateUsers(users); 13 | }, error -> { 14 | App.L.error(error.toString()); 15 | showRefresh(false); 16 | }); 17 | } 18 | ``` 19 | 我们显示了进度条,从StackExchange API 管理器观测用户列表。一旦获取到列表数据,我们开始展示它并更新`Adapter`的内容并让`RecyclerView`显示为可见。 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /chapter8/creating_recyclerview_adapter.md: -------------------------------------------------------------------------------- 1 | # 创建RecyclerView Adapter 2 | 3 | 我们从REST API获取到数据后,我们需要把它绑定View上,并用一个适配器填充列表。我们的RecyclerView适配器是标准的。它继承于`RecyclerView.Adapter`并指定它自己的`ViewHolder`: 4 | ```java 5 | public static class ViewHolder extends RecyclerView.ViewHolder { 6 | @InjectView(R.id.name) TextView name; 7 | @InjectView(R.id.city) TextView city; 8 | @InjectView(R.id.reputation) TextView reputation; 9 | @InjectView(R.id.user_image) ImageView user_image; 10 | public ViewHolder(View view) { 11 | super(view); 12 | ButterKnife.inject(this, view); 13 | } 14 | } 15 | ``` 16 | 我们一旦收到来自API管理器的数据,我们可以设置界面上所有的标签:`name`,`city`和`reputation`。 17 | 18 | 为了展示用户的头像,我们将使用Sergey Tarasevich写的[Universal Image Loader][1]。实践证明,UIL是非常有名的好用的图片管理库。我们也可以使用Square公司的Picasso,Glide或者Facebook公司的Fresco。取决于你自己的喜好。最关键的是无需重复造轮子:库能够方便开发者并让他们更快速实现目标。 19 | 20 | 在我们的适配器中,我们可以这样: 21 | ```java 22 | @Override 23 | public void onBindViewHolder(SoAdapter.ViewHolder holder, int position) { 24 | User user = mUsers.get(position); 25 | holder.setUser(user); 26 | } 27 | ``` 28 | 在`ViewHolder`,我们可以这样: 29 | ```java 30 | public void setUser(User user) { 31 | name.setText(user.getDisplayName()); 32 | city.setText(user.getLocation()); 33 | reputation.setText(String.valueOf(user.getReputation())); 34 | 35 | ImageLoader.getInstance().displayImage(user.getProfileImage(), user_image); 36 | } 37 | ``` 38 | 此时,我们可以允许代码获得一个用户列表,正如下图所示: 39 | 40 | ![](../images/chapter8_1.png) 41 | 42 | ### 检索天气预报 43 | 44 | 我们加大难度,将当地城市的天气加入列表中。**OpenWeatherMap**是一个灵活公共在线天气API,我们可以查询许多有用的预报信息。 45 | 46 | 和往常一样,我们将使用Retrofit映射到API然后通过RxJava来访问它。至于StackExchange API,我们将创建`interface`,`RestAdapter`和一个灵活的管理器: 47 | 48 | ```java 49 | public interface OpenWeatherMapService { 50 | @GET("data2.5/weather") 51 | Observable getForecastByCity(@Query("q") String city); 52 | } 53 | ``` 54 | 这个方法用城市名字作为参数提供当地的预报信息。我们像下面这样将接口和`RestAdapter`类绑定在一起: 55 | ```java 56 | RestAdapter restAdapter = new RestAdapter.Builder() 57 | .setEndpoint("http://api.openweathermap.org") 58 | .setLogLevel(RestAdapter.LogLevel.BASIC) 59 | .build(); 60 | mOpenWeatherMapService = restAdapter.create(OpenWeatherMapService.class); 61 | ``` 62 | 像以前一样,我们只有两件事需要立马去做:设置API端口和log级别。 63 | 64 | `OpenWeatherMapApiManager`类将提供下面的方法: 65 | ```java 66 | public Observable getForecastByCity(String city) { 67 | return mOpenWeatherMapService.getForecastByCity(city) 68 | .subscribeOn(Schedulers.io()) 69 | .observeOn(AndroidSchedulers.mainThread()); 70 | } 71 | ``` 72 | 现在,我们有了用户列表,我们可以根据城市名来查询OpenWeatherMap获得天气预报信息。下一步是修改我们的`ViewHolder`类来为每位用户展示相应的天气图标。 73 | 74 | 我们使用这些工具方法先验证用户主页信息并获得一个合法的城市名字: 75 | ```java 76 | private boolean isCityValid(String location) { 77 | int separatorPosition = getSeparatorPosition(location); 78 | return !"".equals(location) && separatorPosition > -1; 79 | } 80 | 81 | private int getSeparatorPosition(String location) { 82 | int separatorPosition = -1; 83 | if (location != null) { 84 | separatorPosition = location.indexOf(","); 85 | } 86 | return separatorPosition; 87 | } 88 | 89 | private String getCity(String location, int position) { 90 | if (location != null) { 91 | return location.substring(0, position); 92 | } else { 93 | return ""; 94 | } 95 | } 96 | ``` 97 | 借助一个有效的城市名,我们可以用下面命令来获得我们所需要天气的所有数据: 98 | ```java 99 | OpenWeatherMapApiManager.getInstance().getForecastByCity(city) 100 | ``` 101 | 用天气响应的结果,我们可以获得天气图标的URL: 102 | ```java 103 | getWeatherIconUrl(weatherResponse); 104 | ``` 105 | 用图标URL,我们可以检索到图标本身: 106 | ```java 107 | private Observable loadBitmap(String url) { 108 | return Observable.create(subscriber -> { 109 | ImageLoader.getInstance().displayImage(url,city_image, new ImageLoadingListener() { 110 | @Override 111 | public void onLoadingStarted(String imageUri, View view) { 112 | } 113 | 114 | @Override 115 | public void onLoadingFailed(String imageUri, View view, FailReason failReason) { 116 | subscriber.onError(failReason.getCause()); 117 | } 118 | 119 | @Override 120 | public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { 121 | subscriber.onNext(loadedImage); 122 | subscriber.onCompleted(); 123 | } 124 | 125 | @Override 126 | public void onLoadingCancelled(String imageUri, View view) { 127 | subscriber.onError(new Throwable("Image loading cancelled")); 128 | } 129 | }); 130 | }); 131 | } 132 | ``` 133 | 这个`loadBitmap()`返回的Observable可以链接前面一个,并且最后我们可以为这个任务返回一个单独的Observable: 134 | ```java 135 | if (isCityValid(location)) { 136 | String city = getCity(location, separatorPosition); 137 | OpenWeatherMapApiManager.getInstance().getForecastByCity(city) 138 | .filter(response -> response != null) 139 | .filter(response -> response.getWeather().size() > 0) 140 | .flatMap(response -> { 141 | String url = getWeatherIconUrl(response); 142 | return loadBitmap(url); 143 | }) 144 | .subscribeOn(Schedulers.io()) 145 | .observeOn(AndroidSchedulers.mainThread()) 146 | .subscribe(new Observer() { 147 | 148 | @Override 149 | public void onCompleted() { 150 | } 151 | 152 | @Override 153 | public void onError(Throwable e) { 154 | App.L.error(e.toString()); 155 | } 156 | 157 | @Override 158 | public void onNext(Bitmap icon) { 159 | city_image.setImageBitmap(icon); 160 | } 161 | }); 162 | } 163 | ``` 164 | 运行代码,我们可以在下面列表中为每个用户获得新的天气图标: 165 | ![](../images/chapter8_2.png) 166 | 167 | ### 打开网站 168 | 169 | 使用用户主页包含的信息,我们将会创建一个`onClick`监听器来导航到用户web页面,如果有的话,否则打开在Stack Overflow上的个人主页。 170 | 171 | 为了实现它,我们简单实现`Activity`类的接口,用来在适配器触发Android的`onClick`事件。 172 | 173 | 我们的`Adapter ViewHolder`指定这个接口: 174 | ```java 175 | public interface OpenProfileListener { 176 | public void open(String url); 177 | } 178 | ``` 179 | 180 | `Activity`实现它: 181 | ```java 182 | [...] implements SoAdapter.ViewHolder.OpenProfileListener { [...] 183 | mAdapter.setOpenProfileListener(this); 184 | [...] 185 | 186 | @Override 187 | public void open(String url) { 188 | Intent i = new Intent(Intent.ACTION_VIEW); 189 | i.setData(Uri.parse(url)); 190 | startActivity(i); 191 | } 192 | ``` 193 | `Activity`收到URL并用外部Android浏览器打开它。我们的`ViewHolder`负责在用户列表的每个卡片上创建`OnClickListener`并检查我们是打开Stack Overflow用户主页还是外部个人站: 194 | 195 | ```java 196 | mView.setOnClickListener(view -> { 197 | if (mProfileListener != null) { 198 | String url = user.getWebsiteUrl(); 199 | if (url != null && !url.equals("") && !url.contains("search")) { 200 | mProfileListener.open(url); 201 | } else { 202 | mProfileListener.open(user.getLink()); 203 | } 204 | } 205 | )}; 206 | ``` 207 | 一旦我们点击了,我们将直接重定向到预期的网站。在Android上,我们可以用RxAndroid的一种特殊形式(ViewObservable)以更加响应式的方式实现同样的结果。 208 | ```java 209 | ViewObservable.clicks(mView) 210 | .subscribe(onClickEvent -> { 211 | if (mProfileListener != null) { 212 | String url = user.getWebsiteUrl(); 213 | if (url != null && !url.equals("") && !url.contains("search")) { 214 | mProfileListener.open(url); 215 | } else { 216 | mProfileListener.open(user.getLink()); 217 | } 218 | } 219 | }); 220 | ``` 221 | 上面两块代码片段是等价的,你可以选择最喜欢的方式来实现。 222 | 223 | 224 | [1]: https://github.com/nostra13/Android-Universal-ImageLoader 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | -------------------------------------------------------------------------------- /chapter8/rest_in_peace_rxjava_and_retrofit.md: -------------------------------------------------------------------------------- 1 | # 与REST无缝结合-RxJava和Retrofit 2 | 3 | 在上一章中,我们学习了如何使用调度器在不同于UI线程的线程上操作。我们学习了如何高效的运行I/O任务而不用阻塞UI以及如何运行耗时的计算任务而不耗损应用性能。在最后一章中,我们将创建一个最终版的应用实例,用Retrofit映射远程API,异步查询数据,轻松创造一个丰富的UI。 -------------------------------------------------------------------------------- /chapter8/retrofit.md: -------------------------------------------------------------------------------- 1 | # Retrofit 2 | 3 | Retrofit是Square公司专为Android和Java设计的一个类型安全的REST客户端。它帮助你轻松地与任意REST API交互,并完美兼容RxJava:所有的JSON响应对象都被映射成原始的Java对象,并且所有的网络调用都基于Rxjava Observable这些对象。 4 | 5 | 使用API文档,我们可以定义我们从服务器接收的JSON响应数据。为了很容易的将JSON响应数据映射为我们的Java代码,我们将使用[jsonschema2pojo][1], 这个服务将灵活地生成所有与JSON响应数据相映射的Java类。 6 | 7 | 当我们把所有的Java model准备好后,我们就可以开始建立Retrofit。Retrofi使用标准的Java接口来映射API路由。例如例子中,我们将使用来自API的一个路由,下面是我们Retrofit的接口: 8 | 9 | ```java 10 | public interface StackExchangeService { 11 | @GET("/2.2/users?order=desc&sort=reputation&site=stackoverflow") 12 | Observable getMostPopularSOusers(@Query("pagesize") int howmany); 13 | } 14 | ``` 15 | 16 | `interface`接口只包含一个方法,即`getMostPopularSOusers`。这个方法用整型`howmany`作为一个参数并返回`UserResponse`的Observable。 17 | 18 | 当我们有了`interface`,我们可以创建`RestAdapter`类,为了更清楚的组织我们的代码,我们创建一个`SeApiManager`函数提供一种更适当的方式来和StackExchange API交互。 19 | 20 | ```java 21 | public class SeApiManager { 22 | private final StackExchangeService mStackExchangeService; 23 | 24 | public SeApiManager() { 25 | RestAdapter restAdapter = new RestAdapter.Builder() 26 | .setEndpoint("https://api.stackexchange.com") 27 | .setLogLevel(RestAdapter.LogLevel.BASIC) 28 | .build(); 29 | mStackExchangeService = restAdapter.create(StackExchangeService.class); 30 | } 31 | 32 | public Observable> getMostPopularSOusers(int howmany) { 33 | return mStackExchangeService 34 | .getMostPopularSOusers(howmany) 35 | .map(UsersResponse::getUsers) 36 | .subscribeOn(Schedulers.io()) 37 | .observeOn(AndroidSchedulers.mainThread()); 38 | } 39 | } 40 | ``` 41 | 42 | 为了简化例子,我们不再将这个类设计为它本该设计成的单例。使用依赖注入解决方案,如Dagger2,可使代码质量更高。 43 | 44 | 创建`RestAdapter`类,需要对客户端API设置几个重要的方面。这个例子中,我们设置了`endpoint`和`log level`。由于这个例子中URL只是硬编码,像这样使用外部资源来存储数据很重要。避免在代码中硬编码字符串是一个好的实践。 45 | 46 | Retrofit把`RestAdapter`类和我们的API接口绑定在一起后就完成了创建。它返回给我们一个对象用来请求API。我们可以选择直接暴露这个对象,或者以某种封装方式来限制对它的访问。在这个例子中,我们封装它并只暴露`getMostPopularSOusers`方法。这个方法执行查询,使用Retrofit解析JSON响应数据。获得用户列表,并返回给订阅者。如你所见,使用Retrofit、RxJava和Retrolambda,我们几乎没有模板代码:它非常紧凑而且可读性很高。 47 | 48 | 现在,我们已经有一个API管理者来提供一个响应式的方法,它从远程API获取数据并给I/O调度器,解析映射最后为我们的消费者提供一个简洁的用户列表。 49 | 50 | 51 | [1]: http://www.jsonschema2pojo.org "官网" 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | ``` 99 | -------------------------------------------------------------------------------- /chapter8/summary8.md: -------------------------------------------------------------------------------- 1 | # 总结 2 | 3 | 我们的旅程结束了。相信你已经准备好将你的Java应用带到一个新的代码质量水平。你可以享受一个新的编程模式并把更流畅的思维方式应用到日常编程生活中。RxJava提供了一种以面向时序的方式考虑数据的机会:所有事情都是持续变化的,数据在更新,事件在触发,然后你就可以创建事件响应式的、灵活的、运行流畅的App。 4 | 5 | 刚开始切换到RxJava看起来困难并且耗时,但我们已经体验到了如何通过响应式的方式有效地处理日常问题。现在你可以把你的旧代码迁移到RxJava上:给这些同步`getters`一种新的响应式。 6 | 7 | RxJava是一个正在不断发展和扩大的世界。还有许多方法我们还没有去探索。有些方法甚至还没有,通过RxJava,你可以创建你自己的操作符并把他们发展地更远。 8 | 9 | Android是一个好玩的地方,但是它也有局限性。作为一个Android开发者,你可以用RxJava和RxAndroid克服其中的许多。我们用AndroidScheduler只简单提了下RxAndroid,除了在最后一章,你了解了`ViewObservable`。RxAndroid给了你许多:例如,`WidgetObservable`,`LifecycleObservable`。往后将它发展地更长远的任务就取决于你了。 10 | 11 | 谨记可观测序列就像一条河:它们是流动的。你可以“过滤”(filter)一条河,你可以“转换”(transform)一条河,你可以将两条河合并(combine)成一个,然后依然畅流如初。最后,它就成了你想要的那条河。 12 | 13 | ``` 14 | “Be Water,my friend” 15 | 16 | --Bruce Lee 17 | ``` 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /chapter8/the_project_goal.md: -------------------------------------------------------------------------------- 1 | # 项目目标 2 | 3 | 我们将在已有的例子中创建一个新的`Activity`。这个`Activity`将通过StackExchange API从stackoverflow检索出最活跃的10位用户。App使用这些信息来展示一个包含用户头像、姓名、名望数以及住址的列表。对每一位用户,app使用OpenWeatherMap API来检索该用户住址当地的天气预报,并显示一个小天气图标。基于从StackOverflow检索的信息,app对列表中的每一位用户提供一个`onClick`事件,打开他们在个人信息中设定的个人网站或者Stack Overflow的个人主页。 -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RxJava Essentials 中文翻译版", 3 | "introduction": "这本书将帮助你学习RxJava的核心方面,也能帮助你克服Android平台局限性从而创建一个基于事件驱动的,响应式的,流畅体验的Android应用。", 4 | "path":{ 5 | "toc":"SUMMARY.md" 6 | } 7 | } -------------------------------------------------------------------------------- /cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/cover.jpg -------------------------------------------------------------------------------- /cover/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/cover/background.jpg -------------------------------------------------------------------------------- /cover/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/cover/cover.jpg -------------------------------------------------------------------------------- /cover/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/cover/logo.png -------------------------------------------------------------------------------- /images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/.DS_Store -------------------------------------------------------------------------------- /images/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/README.md -------------------------------------------------------------------------------- /images/chapter2_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter2_1.png -------------------------------------------------------------------------------- /images/chapter3_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter3_1.png -------------------------------------------------------------------------------- /images/chapter3_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter3_2.png -------------------------------------------------------------------------------- /images/chapter3_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter3_3.png -------------------------------------------------------------------------------- /images/chapter3_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter3_4.png -------------------------------------------------------------------------------- /images/chapter3_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter3_5.png -------------------------------------------------------------------------------- /images/chapter4_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter4_1.png -------------------------------------------------------------------------------- /images/chapter4_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter4_10.png -------------------------------------------------------------------------------- /images/chapter4_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter4_11.png -------------------------------------------------------------------------------- /images/chapter4_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter4_12.png -------------------------------------------------------------------------------- /images/chapter4_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter4_13.png -------------------------------------------------------------------------------- /images/chapter4_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter4_14.png -------------------------------------------------------------------------------- /images/chapter4_15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter4_15.png -------------------------------------------------------------------------------- /images/chapter4_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter4_2.png -------------------------------------------------------------------------------- /images/chapter4_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter4_3.png -------------------------------------------------------------------------------- /images/chapter4_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter4_4.png -------------------------------------------------------------------------------- /images/chapter4_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter4_5.png -------------------------------------------------------------------------------- /images/chapter4_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter4_6.png -------------------------------------------------------------------------------- /images/chapter4_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter4_7.png -------------------------------------------------------------------------------- /images/chapter4_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter4_8.png -------------------------------------------------------------------------------- /images/chapter4_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter4_9.png -------------------------------------------------------------------------------- /images/chapter5_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter5_1.png -------------------------------------------------------------------------------- /images/chapter5_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter5_10.png -------------------------------------------------------------------------------- /images/chapter5_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter5_11.png -------------------------------------------------------------------------------- /images/chapter5_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter5_12.png -------------------------------------------------------------------------------- /images/chapter5_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter5_13.png -------------------------------------------------------------------------------- /images/chapter5_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter5_14.png -------------------------------------------------------------------------------- /images/chapter5_15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter5_15.png -------------------------------------------------------------------------------- /images/chapter5_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter5_2.png -------------------------------------------------------------------------------- /images/chapter5_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter5_3.png -------------------------------------------------------------------------------- /images/chapter5_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter5_4.png -------------------------------------------------------------------------------- /images/chapter5_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter5_5.png -------------------------------------------------------------------------------- /images/chapter5_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter5_6.png -------------------------------------------------------------------------------- /images/chapter5_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter5_7.png -------------------------------------------------------------------------------- /images/chapter5_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter5_8.png -------------------------------------------------------------------------------- /images/chapter5_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter5_9.png -------------------------------------------------------------------------------- /images/chapter6_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter6_1.png -------------------------------------------------------------------------------- /images/chapter6_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter6_10.png -------------------------------------------------------------------------------- /images/chapter6_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter6_11.png -------------------------------------------------------------------------------- /images/chapter6_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter6_12.png -------------------------------------------------------------------------------- /images/chapter6_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter6_13.png -------------------------------------------------------------------------------- /images/chapter6_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter6_2.png -------------------------------------------------------------------------------- /images/chapter6_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter6_3.png -------------------------------------------------------------------------------- /images/chapter6_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter6_4.png -------------------------------------------------------------------------------- /images/chapter6_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter6_5.png -------------------------------------------------------------------------------- /images/chapter6_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter6_6.png -------------------------------------------------------------------------------- /images/chapter6_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter6_7.png -------------------------------------------------------------------------------- /images/chapter6_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter6_8.png -------------------------------------------------------------------------------- /images/chapter6_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter6_9.png -------------------------------------------------------------------------------- /images/chapter7_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter7_1.png -------------------------------------------------------------------------------- /images/chapter7_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter7_2.png -------------------------------------------------------------------------------- /images/chapter7_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter7_3.png -------------------------------------------------------------------------------- /images/chapter7_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter7_4.png -------------------------------------------------------------------------------- /images/chapter7_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter7_5.png -------------------------------------------------------------------------------- /images/chapter7_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter7_6.png -------------------------------------------------------------------------------- /images/chapter8_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter8_1.png -------------------------------------------------------------------------------- /images/chapter8_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/chapter8_2.png -------------------------------------------------------------------------------- /images/rxjava.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/images/rxjava.jpg -------------------------------------------------------------------------------- /rxjava.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuxingxin/RxJava-Essentials-CN/561d8a0eb17de84356ee072962035e6a578ed3d2/rxjava.jpg -------------------------------------------------------------------------------- /styles/website.css: -------------------------------------------------------------------------------- 1 | /* CSS for website */ 2 | --------------------------------------------------------------------------------