如果您的Android应用要在Google Play上获得这些五星级的评价,那么它需要能够执行多项任务。
最低限度,今天的移动用户希望在后台运行您的应用程序时仍然能够与您的应用程序进行交互。 这听起来可能很简单,但是Android默认情况下是单线程的,因此,如果您要满足听众的期望,那么迟早您将不得不创建一些其他线程。
在本系列的上一篇文章中,我们对RxJava进行了介绍,RxJava是JVM的反应式库,可以帮助您创建对数据和事件进行响应的Android应用程序。 但是,您也可以使用此库同时对数据和事件做出反应。
在这篇文章中,我将向您展示如何使用RxJava的运算符来最终使Android上的并发成为一种轻松的体验。 到本文结尾,您将知道如何使用RxJava运算符创建其他线程,指定在这些线程上应该执行的工作,然后将结果发布回Android的所有重要主UI线程-只需一个几行代码。
而且,由于没有完美的技术,在向您展示如何使用运算符确保在您自己的Android项目中不会发生此问题之前,我还将向您介绍将RxJava库添加到项目中的主要潜在陷阱。
介绍运营商
RxJava拥有大量的运算符,这些运算符主要用于帮助您修改,过滤,合并和转换Observable
发出的数据。 您可以在官方文档中找到RxJava运算符的完整列表 ,尽管没有人希望您记住每个运算符 ,但是值得花些时间阅读此列表,以使您大致了解不同的数据您可以执行的转换。
RxJava的运算符列表已经非常详尽,但是如果您找不到想要的用于数据转换的完美运算符,则可以始终将多个运算符链接在一起。 将运算符应用于Observable
通常会返回另一个Observable
,因此您可以继续应用运算符,直到获得所需的结果。
一篇文章中介绍的RxJava运算符太多,而且RxJava官方文档已经很好地介绍了可用于数据转换的所有运算符,因此,我将重点介绍两个具有RxJava运算符的运算符。最有潜力让您的生活作为一个Android开发者更容易: subscribeOn()
和observeOn()
使用RxJava运算符进行多线程
如果您的应用程序要提供最佳的用户体验,则它需要能够执行密集或长期运行的任务,并同时执行多个任务,而又不会阻塞Android的所有重要主UI线程。
例如,假设您的应用程序需要从两个不同的数据库中获取一些信息。 如果您在Android的主线程上一个接一个地执行这两项任务,那么这不仅会花费大量时间,而且UI将无响应,直到您的应用程序已从两个数据库中检索到每条信息为止。 并非完美的用户体验!
更好的解决方案是创建两个其他线程,您可以在其中同时执行这两个任务,而又不会阻塞主UI线程。 这种方法意味着工作将以两倍的速度完成, 并且用户将能够继续与您的应用程序的用户界面进行交互。 潜在地,您的用户甚至可能不知道您的应用程序正在后台执行一些密集且长期运行的工作-所有数据库信息都将简单地出现在应用程序的UI中,就像是在魔术一样!
Android确实提供了一些现成的工具,您可以使用它们来创建其他线程,包括Service
和IntentService
,但是这些解决方案实施起来很棘手,并且会很快导致复杂,冗长的代码。 另外,如果您没有正确实现多线程,您可能会发现自己的应用程序正在泄漏内存并引发各种错误。
为了使Android上的多线程更加令人头疼,Android的主UI线程是唯一可以更新应用程序用户界面的线程。 如果您要使用在任何其他线程上执行的工作结果来更新应用程序的UI,则通常需要在主UI线程上创建一个Handler
,然后使用该Handler
将数据从后台线程传输到主线程。 这意味着更多的代码,更多的复杂性以及错误蔓延到您的项目的更多机会。
但是RxJava具有两个运算符,可以帮助您避免这种复杂性和潜在的错误。
注意,将这些运算符与Schedulers
结合使用, Schedulers
本质上是允许您指定线程的组件。 现在,仅将调度程序视为thread的同义词。
- subscriptionOn
subscribeOn(Scheduler)
:默认情况下,Observable
在声明订阅的线程(即您调用.subscribe
方法的线程)上发出其数据。 在Android中,这通常是主要的UI线程。 您可以使用subscribeOn()
运算符定义另一个Scheduler
,在该Scheduler
中Observable
应该执行并发出其数据。 -
observeOn(Scheduler)
:您可以使用此运算符将Observable
的发射重定向到其他Scheduler
,从而有效地更改Observable
的通知发送位置的线程,并扩展该线程消耗其数据的线程。
RxJava附带了许多调度程序,可用于创建不同的线程,包括:
-
Schedulers.io()
:设计用于与IO相关的任务。 -
Schedulers.computation()
:设计用于计算任务。 默认情况下,计算调度程序中的线程数限制为设备上可用的CPU数。 -
Schedulers.newThread()
:创建一个新线程。
现在你把所有的运动部件的概述,让我们来看看如何一些例子subscribeOn()
和observeOn()
被使用,并且在行动中看到一些调度。
subscribeOn()
在Android中,通常将使用subscribeOn()
和随附的Scheduler
来更改执行一些长时间运行或密集工作的线程,因此不会有阻塞主UI线程的风险。 例如,您可能决定导入的大量数据的io()
调度或在执行一些计算computation()
调度。
在下面的代码,我们正在创造一个新的线程,其中Observable
将执行其业务并发射值1
, 2
和3
。
Observable.just(1, 2, 3)
.subscribeOn(Schedulers.newThread())
.subscribe(Observer);
尽管这是创建线程并开始在该线程上发送数据所需的全部,但是您可能需要一些确认,以确保该可观察的对象确实在新线程上运行。 一种方法是在Android Studio的系统中打印应用程序当前正在使用的线程的名称 Logcat监视器。
方便地,在上一篇文章RxJava入门中 ,我们创建了一个应用程序,该应用程序在Observable的生命周期的各个阶段将消息发送到Logcat Monitor,因此我们可以重用许多此类代码。
打开您在该文章中创建的项目,并调整代码,以便将上述Observable
用作其源Observable
。 然后添加subscriptionOn subscribeOn()
运算符,并指定要发送到Logcat的消息应包括当前线程的名称。
完成的项目应如下所示:
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
public class MainActivity extends AppCompatActivity {
public static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Observable.just(1, 2, 3)
.subscribeOn(Schedulers.newThread())
.subscribe(Observer);
}
Observer<Integer> Observer = new Observer<Integer>() {
@Override
public void onSubscribe(Disposable d) {
Log.e(TAG, "onSubscribe" + Thread.currentThread().getName());
}
@Override
public void onNext(Integer value) {
Log.e(TAG, "onNext: " + value + Thread.currentThread().getName());
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: ");
}
@Override
public void onComplete() {
Log.e(TAG, "onComplete: All Done!" + Thread.currentThread().getName());
}
};
}
确保Android Studio的Logcat Monitor已打开(通过选择Android Monitor选项卡,然后选择Logcat ),然后在物理Android设备或AVD上运行项目。 您应该在Logcat Monitor中看到以下输出:
在这里,您可以看到在主UI线程上正在调用.subscribe
,但是observable在完全不同的线程上运行。
无论您将其放置在可观察链中的什么位置, subscribeOn()
运算符都将具有相同的效果。 但是,不能在同一链中使用多个subscribeOn()
运算符。 如果确实包含多个subscribeOn()
,则您的链将仅使用最接近可观察源的subscribeOn()
。
observeOn()
不像subscribeOn()
在其中放置observeOn()
在你的链确实重要,因为这样操作者只需改变了的二手由出现下游的观测线程。
例如,如果将以下内容插入到链中,则从此刻起链中出现的每个可观察对象将使用新线程。
.observeOn(Schedulers.newThread())
该链将继续在新线程上运行,直到遇到另一个observeOn()
运算符,此时它将切换到该运算符指定的线程。 您可以通过在链中插入多个observeOn()
运算符来控制特定可观察对象向其发送通知的线程。
在开发Android应用程序时,通常会使用observeOn()
将在后台线程上执行的工作结果发送到Android的主UI线程。 将发射重定向到Android主UI线程的最简单方法是使用AndroidSchedulers.mainThread Scheduler
,它是RxAndroid库而不是RxJava库的一部分。
RxAndroid库包含针对RxJava 2的特定于Android的绑定,这使其成为Android开发人员的宝贵额外资源(我们将在本系列的下一篇文章中对其进行详细介绍)。
要将RxAndroid添加到您的项目中,请打开模块级别的build.gradle文件,然后将库的最新版本添加到“依赖项”部分。 在撰写本文时,RxAndroid的最新版本是2.0.1,因此我添加了以下内容:
dependencies {
...
...
...
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
}
将此库添加到您的项目后,您可以指定使用单个代码行将可观察对象的结果发送到应用程序的主UI线程:
.observeOn(AndroidSchedulers.mainThread())
考虑到与应用程序的主UI线程进行通信占用了完整的Android官方文档的整页,因此这是一项巨大的改进,可以在创建多线程Android应用程序时为您节省大量时间。
RxJava的主要缺点
尽管RxJava可以为Android开发人员提供大量功能,但没有完美的技术,而且RxJava确实存在一个重大陷阱,有可能使您的应用程序崩溃。
默认情况下,RxJava运行基于推送的工作流:数据由Observable
上游生成,然后向下游推送到分配的Observer
。 基于推送的工作流程的主要问题是,生产者(在本例中为Observable
)以太快的速度发出项目,以至于消费者( Observer
)无法处理。
健谈的Observable
和慢速的Observer
会Swift导致积压未消耗的物品,这将吞噬系统资源,甚至可能导致OutOfMemoryException
。 这个问题被称为背压 。
如果您怀疑应用程序中正在发生背压,则有几种可能的解决方案,包括使用运算符来减少要生产的物品数量。
使用sample()
和throttlefirst()
创建采样周期
如果一个Observable
发出大量项目,则分配的Observer
不必接收这些项目中的每个项目。
如果您可以放心地忽略某些Observable
的发射,则可以使用一些运算符来创建采样周期,然后挑选在这些周期内发射的特定值:
-
sample()
运算符按您指定的时间间隔检查Observable的输出,然后获取在该采样期间内发出的最新项目。 例如,如果您在项目中包括.sample(5, SECONDS)
,则观察者将收到在每五秒间隔内发出的最后一个值。 -
throttleFirst()
FirstthrottleFirst()
运算符采用采样期间发出的第一个值。 例如,如果包含.throttlefirst(5, SECONDS)
则观察者将收到在每五秒间隔内发出的第一个值。
使用buffer()
批量排放
如果你不能安全地跳过任何排放,那么你仍然可以起飞陷入困境的一些压力Observer
通过分组排放到批生产,然后发送向前集体 。 处理批量排放通常比分别处理多个排放更有效,因此此方法应提高消耗率。
您可以使用buffer()
运算符创建批量排放。 在这里,我们使用buffer()
批处理三秒钟内发出的所有项目:
Observable.range(0, 10)
.buffer(3, SECONDS)
.subscribe(System.out::println);
另外,您可以使用buffer()
创建一个由特定数量的排放组成的批处理。 例如,在这里我们告诉buffer()
将发射捆绑成四个一组:
Observable.range(0, 10)
.buffer(4)
.subscribe(System.out::println);
用可流动物代替可观察物
减少排放数量的另一种方法是更换Observable
这是造成你一个问题Flowable
。
在RxJava 2中,RxJava团队决定将标准Observable
分为两种类型:我们在整个系列中一直在研究的常规类型和Flowable
。
Flowable
的功能与Observable
的功能几乎相同,但有一个主要区别: Flowable
只能发送观察者请求的项目。 如果您的Observable
发出的项目超出其指定的观察者可以消耗的数量,那么您可能需要考虑切换到Flowable
。
在开始在项目中使用Flowable
之前,需要添加以下import语句:
import io.reactivex.Flowable;
然后,您可以创建Flowable
使用完全相同用于创建相同的技术小号Observable
秒。 例如,以下每个代码段都将创建一个能够发送数据的Flowable
:
Flowable<String> flowable = Flowable.fromArray(new String[] {"south", "north", "west", “east”});
...
flowable.subscribe()
Flowable<Integer> flowable = Flowable.range(0, 20);
...
flowable.subscribe()
在这一点上,您可能会想:为什么我只使用Flowable
而不用担心背压的时候会使用Observable
呢? 答案是, Flowable
比常规的Observable
会产生更多的开销,因此,为了创建一个高性能的应用程序,除非您怀疑应用程序正面临背压,否则应坚持使用Observable
s。
单打
一个Flowable
是不是唯一的变化Observable
,你会发现在RxJava,因为库还包括Single
级。
当您只需要发出一个值时, Singles
很有用。 在这些情况下,创建一个Observable
感觉有点过头了,但是Single
旨在通过发出以下命令来简单地发出单个值然后完成:
-
onSuccess()
:Single
发出其唯一的值。 -
onError()
:如果Single
无法发射其物品,则它将通过此方法将其生成的Throwable
传递给它。
Single
将仅调用这些方法之一,然后立即终止。
让我们看一个Single
例操作的例子,为了节省时间,我们在重复使用代码:
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import io.reactivex.Single;
import io.reactivex.SingleObserver;
import io.reactivex.disposables.Disposable;
public class MainActivity extends AppCompatActivity {
public static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Single.just("Hello World")
.subscribe(getSingleObserver());
}
private SingleObserver<String> getSingleObserver() {
return new SingleObserver<String>() {
@Override
public void onSubscribe(Disposable d) {
Log.e(TAG, "onSubscribe");
}
@Override
public void onSuccess(String value) {
Log.e(TAG, " onSuccess : " + value);
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: ");
}
};
}
}
在AVD或物理Android设备上运行项目,您将在Android Studio的Logcat Monitor中看到以下输出:
如果您改变主意并希望随时将Single
转换为Observable
,则RxJava再一次具有您需要的所有运算符,包括:
-
mergeWith()
:将多个Singles
合并为一个Observable
。 -
concatWith()
:将多个Singles
发出的项目链接在一起,以形成Observable
发射。 -
toObservable()
:将Single
转换为Observable
,该Observable
发出最初由Single发出的项,然后完成。
摘要
在本文中,我们探索了一些RxJava运算符,您可以使用它们来创建和管理多个线程,而不会像传统上在Android上伴随多线程那样带来复杂性和潜在的错误。 我们还看到了如何使用RxAndroid库通过单行代码与Android的所有重要主UI线程进行通信,以及如何确保背压不会成为应用程序中的问题。
在本系列中,我们已经多次涉及RxAndroid库,但是该库中包含了特定于Android的RxJava绑定,当在Android平台上使用RxJava时,该绑定是无价的,因此在本系列的最后一篇文章中,我们将详细了解RxAndroid库。
翻译自: https://code.tutsplus.com/tutorials/reactive-programming-operators-in-rxjava-20--cms-28396