这是一个存储库,其中包含在 Android 中使用 RxJava 的实际有用示例。它通常处于“正在进行中”(WIP) 的恒定状态。
我还使用本存储库中列出的许多示例进行了有关学习 Rx 的演讲。
using
)一个常见的要求是将冗长、繁重的 I/O 密集型操作卸载到后台线程(非 UI 线程),并在完成后将结果反馈给 UI/主线程。这是如何将长时间运行的操作卸载到后台线程的演示。操作完成后,我们恢复到主线程。全部使用RxJava!将此视为 AsyncTasks 的替代品。
长操作是通过阻塞 Thread.sleep 调用来模拟的(因为这是在后台线程中完成的,所以我们的 UI 永远不会中断)。
真正看到这个例子的光芒。多次点击按钮,看看按钮点击(这是一个 UI 操作)如何永远不会被阻止,因为长操作仅在后台运行。
这是如何使用“缓冲区”操作累积事件的演示。
提供了一个按钮,我们累积一段时间内该按钮的点击次数,然后得出最终结果。
如果您点击该按钮一次,您将收到一条消息,说明该按钮已被点击一次。如果您在 2 秒内连续点击该按钮 5 次,那么您会得到一条日志,表示您点击该按钮 5 次(而 5 个单独的日志则显示“按钮点击一次”)。
笔记:
如果您正在寻找一种更简单的解决方案,可以累积“连续”点击而不是仅记录一段时间内的点击次数,请查看 EventBus 演示,其中使用了publish
和buffer
运算符的组合。如需更详细的解释,您还可以查看这篇博文。
这是一个演示如何以仅尊重最后一个事件的方式吞并事件。一个典型的例子是即时搜索结果框。当您键入“Bruce Lee”一词时,您不想执行 B、Br、Bru、Bruce、Bruce、Bruce L 等搜索。而是明智地等待几分钟,确保用户打完整个词,然后喊出一句“李小龙”。
当您在输入框中键入内容时,它不会在每次输入字符更改时都发出日志消息,而是仅选择最后发出的事件(即输入)并记录该事件。
这是RxJava中的debounce/throttleWithTimeout方法。
Square 的 Retrofit 是一个令人惊叹的库,可以帮助轻松联网(即使您还没有跳转到 RxJava,您确实应该检查一下)。它与 RxJava 配合使用效果更好,这些是使用 GitHub API 的示例,直接取自 Android 半神开发者 Jake Wharton 在 Netflix 的演讲。您可以通过此链接观看演讲。顺便说一句,我使用 RxJava 的动机是来自参加 Netflix 的这次演讲。
(注意:您很可能很快就会达到 GitHub API 配额,因此如果您想经常运行这些示例,请发送 OAuth 令牌作为参数)。
自动更新视图是一件很酷的事情。如果您以前接触过 Angular JS,它们有一个非常漂亮的概念,称为“双向数据绑定”,因此当 HTML 元素绑定到模型/实体对象时,它会不断“侦听”该实体上的更改,并且根据模型自动更新其状态。使用本示例中的技术,您可以轻松地使用类似演示视图模型模式的模式。
虽然这里的示例非常简单,但使用Publish Subject
实现双重绑定的技术更有趣。
这是使用 RxJava 调度程序进行轮询的示例。这在您想要不断轮询服务器并可能获取新数据的情况下非常有用。网络调用是“模拟的”,因此它会在返回结果字符串之前强制延迟。
有两种变体:
第二个例子基本上是指数退避的变体。
我们在这里使用 RepeatWithDelay,而不是使用 RetryWithDelay。为了理解重试(何时)和重复(何时)之间的区别,我建议丹在这个主题上发表精彩的文章。
不使用repeatWhen
的延迟轮询的另一种方法是使用链式嵌套延迟可观察量。请参阅 ExponentialBackOffFragment 示例中的 startExecutingWithExponentialBackoffDelay。
指数退避是一种策略,根据某个输出的反馈,我们改变进程的速率(通常减少重试次数或增加重试或重新执行某个进程之前的等待时间)。
这个概念通过例子更有意义。 RxJava 使得实现这种策略(相对)简单。我感谢迈克提出这个想法。
假设您出现网络故障。一个明智的策略是不要每 1 秒重试一次网络调用。相反,随着延迟的增加而重试是明智的(不……优雅!)。那么你尝试在第 1 秒执行网络调用,没有骰子吗? 10秒后尝试...是否定的? 20秒后尝试,没有cookie? 1分钟后尝试。如果这个东西还是不行的话,你就得放弃网络了哟!
我们使用 RxJava 和retryWhen
运算符来模拟此行为。
RetryWithDelay
代码片段礼貌:
另请查看轮询示例,其中我们使用非常相似的指数退避机制。
指数退避策略的另一种变体是执行给定次数但具有延迟间隔的操作。因此,您从现在起 1 秒后执行某个操作,然后从现在起 10 秒后再次执行该操作,然后从现在起 20 秒后执行该操作。总共执行 3 次后,您将停止执行。
模拟这种行为实际上比之前的重试机制要简单得多。您可以使用delay
运算符的变体来实现此目的。
.combineLatest
)感谢 Dan Lew 在零散的播客中给了我这个想法 - 第 4 集(大约 4:30)。
.combineLatest
允许您在一个位置同时紧凑地监视多个可观察对象的状态。演示的示例展示了如何使用.combineLatest
来验证基本表单。此表单有 3 个主要输入被视为“有效”(电子邮件、密码和号码)。一旦所有输入都有效,该表单将变为有效(下面的文本变为蓝色:P)。如果不是,则会针对无效输入显示错误。
我们有 3 个独立的可观察对象,用于跟踪每个表单字段的文本/输入更改(RxAndroid 的WidgetObservable
可以方便地监视文本更改)。在从所有3 个输入中注意到事件更改后,结果将被“组合”并评估表单的有效性。
请注意,检查有效性的Func3
函数仅在所有 3 个输入都收到文本更改事件后才会启动。
当表单中有更多数量的输入字段时,此技术的价值变得更加明显。否则,使用一堆布尔值来处理它会使代码变得混乱并且难以理解。但是使用.combineLatest
所有逻辑都集中在一个漂亮的紧凑代码块中(我仍然使用布尔值,但这是为了使示例更具可读性)。
我们有两个源 Observables:磁盘(快速)缓存和网络(新鲜)调用。通常,磁盘 Observable 比网络 Observable 快得多。但为了演示其工作原理,我们还使用了一个假的“较慢”磁盘缓存来查看运算符的行为。
这是使用 4 种技术来演示的:
.concat
.concatEager
.merge
.publish
选择器+合并+takeUntil第四种技术可能是您最终想要使用的技术,但了解技术的进展并理解其原因是很有趣的。
concat
很棒。它从第一个 Observable(在我们的例子中是磁盘缓存)中检索信息,然后从后续的网络 Observable 中检索信息。由于磁盘缓存可能更快,因此一切都显示良好,并且磁盘缓存加载速度很快,一旦网络调用完成,我们就交换出“新鲜”结果。
concat
的问题是,直到第一个 Observable 完成之后,后续的 observable 才会开始。这可能是个问题。我们希望所有可观察量同时开始,但以我们期望的方式产生结果。值得庆幸的是,RxJava 引入了concatEager
它正是这样做的。它启动两个 Observable,但缓冲后一个 Observable 的结果,直到前一个 Observable 完成。这是一个完全可行的选择。
但有时,您只想立即开始显示结果。假设第一个可观察量(由于某种奇怪的原因)需要很长时间才能运行完其所有项目,即使第二个可观察量的前几个项目已经通过线路传输,它也会被强制排队。你不一定想“等待”任何 Observable。在这些情况下,我们可以使用merge
运算符。它在发出项目时将其交错。这非常有效,并且一旦显示结果就开始输出。
与concat
运算符类似,如果您的第一个 Observable 始终比第二个 Observable 快,那么您不会遇到任何问题。然而, merge
的问题是:如果由于某种奇怪的原因,缓存或较慢的可观察值在较新/较新鲜的可观察值之后发出一个项目,它将覆盖较新的内容。单击示例中的“合并(慢速磁盘)”按钮以查看此问题的实际情况。 @JakeWharton 和 @swankjesse 的贡献变为 0!在现实世界中,这可能很糟糕,因为这意味着新数据将被过时的磁盘数据覆盖。
为了解决这个问题,您可以将合并与超级漂亮的publish
运算符结合使用,该运算符接受“选择器”。我在一篇博客文章中写了这种用法,但我要感谢 Jedi JW 提醒我这种技术。我们publish
网络可观察对象并为其提供一个选择器,该选择器开始从磁盘缓存中发出,直到网络可观察对象开始发出。一旦网络 observable 开始发出,它就会忽略磁盘 observable 的所有结果。这是完美的,可以解决我们可能遇到的任何问题。
以前,我使用merge
运算符,但通过监视“resultAge”克服了结果被覆盖的问题。如果您想了解这个旧的实现,请参阅旧的PseudoCacheMergeFragment
示例。
这是一个超级简单明了的示例,它向您展示了如何使用 RxJava 的timer
、 interval
和delay
运算符来处理许多您想要以特定时间间隔运行任务的情况。基本上对 Android TimerTask
说“不”。
此处展示的案例:
随附的博客文章可以更好地解释此演示的详细信息:
在 Android 中使用 RxJava 时提出的一个常见问题是,“如果发生配置更改(活动轮换、语言区域更改等),我如何恢复可观察对象的工作?”。
此示例向您展示了一种策略,即。使用保留的片段。在阅读了 Alex Lockwood 的这篇精彩文章后,我开始使用保留的片段作为“工人片段”。
点击开始按钮并根据需要旋转屏幕;你会看到可观察的从它停止的地方继续。
本示例中使用的源可观察量的“热度”存在某些怪癖。查看我的博客文章,其中我解释了具体细节。
此后我使用另一种方法重写了这个示例。虽然ConnectedObservable
方法有效,但它进入了“多播”领域,这可能很棘手(线程安全、.refcount 等)。另一方面,主题要简单得多。您可以在此处看到它使用Subject
重写。
我写了另一篇关于如何思考主题的博文,其中我详细介绍了一些内容。
Volley 是 Google 在 IO '13 上推出的另一个网络库。 github 的一位好心公民贡献了这个示例,因此我们知道如何将 Volley 与 RxJava 集成。
我在这里简单地使用了主题。老实说,如果您还没有通过Observable
获取项目(例如通过 Retrofit 或网络请求),那么就没有充分的理由使用 Rx 并使事情复杂化。
此示例基本上将页码发送到主题,然后主题处理添加项目。请注意concatMap
的使用以及_itemsFromNetworkCall
中Observable<List>
的返回。
为了好玩,我还提供了一个PaginationAutoFragment
示例,这个“自动分页”不需要我们点击按钮。如果您了解前面示例的工作原理,那么遵循起来应该很简单。
这里有一些其他奇特的实现(虽然我喜欢阅读它们,但我没有将它们用于我的现实世界应用程序,因为我个人认为没有必要):
下面的 ascii 图以华丽的方式表达了我们下一个示例的意图。 f1、f2、f3、f4、f5 本质上是网络调用,在进行这些调用时,会返回未来计算所需的结果。
(flatmap)
f1 ___________________ f3 _______
(flatmap) | (zip)
f2 ___________________ f4 _______| ___________ final output
|
____________ f5 _______|
此示例的代码已由互联网上的一位 Mr.skehlet 编写。转到代码要点。它是用纯 Java (6) 编写的,因此如果您理解了前面的示例,那么它就很容易理解。当时间允许或者我已经用完其他令人信服的例子时,我会在这里再次刷新它。
这是一个演示.timeout
运算符用法的简单示例。按钮 1 将在超时限制之前完成任务,而按钮 2 将强制出现超时错误。
请注意我们如何提供一个自定义 Observable 来指示如何在超时异常下做出反应。
using
) using
运营商相对鲜为人知,而且众所周知很难通过 Google 搜索。它是一个漂亮的 API,有助于设置(昂贵的)资源、使用它,然后以干净的方式处理掉。
该运算符的好处在于,它提供了一种以严格范围的方式使用可能昂贵的资源的机制。使用 -> 设置、使用和处置。想想数据库连接(如 Realm 实例)、套接字连接、线程锁等。
Rx 中的多播就像一门黑暗艺术。没有多少人知道如何毫无顾虑地实现这一目标。此示例配置两个订阅者(以按钮的形式),并允许您在不同时间点添加/删除订阅者,并查看不同运营商在这些情况下的行为。
源 observale 是一个计时器( interval
)observable,选择它的原因是有意选择一个非终止 observable,这样您就可以测试/确认您的多播实验是否会泄漏。
我还在 360|Andev 上详细介绍了多播。如果您有兴趣和时间,我强烈建议您先观看该演讲(特别是多播运算符排列部分),然后再尝试一下此处的示例。
这里的所有示例都已迁移为使用 RxJava 2.X。
在某些情况下,我们使用 David Karnok 的 Interop 库,因为某些库(如 RxBindings、RxRelays、RxJava-Math 等)尚未移植到 2.x。
我尽力确保这些示例不会过度做作,而是反映现实世界的用例。如果您有类似的有用示例演示 RxJava 的使用,请随时发送拉取请求。
我也正在研究 RxJava,所以如果您觉得有更好的方法来完成上面提到的示例之一,请提出一个问题来解释如何做。更好的是,发送拉取请求。
Rx 线程是一件很混乱的事情。为了提供帮助,该项目使用 YourKit 工具进行分析。
YourKit 通过创新和智能的工具支持开源项目,用于监视和分析 Java 应用程序。 YourKit 是 YourKit Java Profiler 的创建者。
根据 Apache 许可证 2.0 版(“许可证”)获得许可。您可以在以下位置获取许可证副本:
http://www.apache.org/licenses/LICENSE-2.0
除非适用法律要求或书面同意,否则根据许可证分发的软件均按“原样”分发,不带任何明示或暗示的保证或条件。请参阅许可证,了解许可证下管理权限和限制的特定语言。
您同意对该存储库的所有贡献(以修复、拉取请求、新示例等形式)均遵循上述许可。