RxJava学习 - 3. Cold versus hot Observables
在Observable和Observer的关系中,有一个微妙的行为,依赖于Observable是如何实现的。Observables的一个主要特征是cold和hot,当有多个Observers时,这决定了Observables的行为。
首先,我们看看cold Observables。
Cold Observables
Cold Observables很像一个音乐CD,可以被每个听众重新播放,每个人可以在任何时刻听这些乐曲。
同样的,cold Observables可以为每个Observer,replay它的emissions,确保所有的Observers拿到全部数据。大多数数据驱动的Observables是cold,包括Observable.just()和Observable.fromIterable()工厂。
下面的例子,我们有两个Observers订阅了一个Observable。Observable首先发射所有的emissions给第一个Observer,然后调用onComplete()。然后,它再次发射所有的emissions给第二个Observer,然后调用onComplete()。通过两个分开的流,他们都接收到相同的数据。这是cold Observable的典型行为:
import io.reactivex.Observable;
public class Launcher {
public static void main(String[] args) {
Observable<String> source =
Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon");
//first observer
source.subscribe(s -> System.out.println("Observer 1 Received: " + s));
//second observer
source.subscribe(s -> System.out.println("Observer 2 Received: " + s));
}
}
甚至在第二个Observer转换了emissions,它仍然获得一个自己的emissions流。使用map()和filter()仍然能产生cold的Observables:
import io.reactivex.Observable;
public class Launcher {
public static void main(String[] args) {
Observable<String> source =
Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon");
//first observer
source.subscribe(s -> System.out.println("Observer 1 Received: " + s));
//second observer
source.map(String::length).filter(i -> i >= 5)
.subscribe(s -> System.out.println("Observer 2 Received: " + s));
}
}
它的输出是
Observer 1 Received: Alpha
Observer 1 Received: Beta
Observer 1 Received: Gamma
Observer 1 Received: Delta
Observer 1 Received: Epsilon
Observer 2 Received: 5
Observer 2 Received: 5
Observer 2 Received: 5
Observer 2 Received: 7
这里有更实际的例子:Dave Moten’s RxJava-JDBC。
允许你增加cold Observables实现SQL查询。如果你想查询一个SQLite数据库,可以在你的=工程里包含SQLite JDBC驱动和RxJava-JDBC库。
然后可以反应式地查询数据库表,像下面这样:
import com.github.davidmoten.rx.jdbc.ConnectionProviderFromUrl;
import com.github.davidmoten.rx.jdbc.Database;
import rx.Observable;
import java.sql.Connection;
public class Launcher {
public static void main(String[] args) {
Connection conn =
new ConnectionProviderFromUrl("jdbc:sqlite:/home/thomas/rexon_metals.db").get();
Database db = Database.from(conn);
Observable<String> customerNames =
db.select("SELECT NAME FROM CUSTOMER").getAs(String.class);
customerNames.subscribe(s -> System.out.println(s));
}
}
SQL-driven Observable是cold。很多Observables从有限数据源(比如数据库、文本文件或者JSON)发射数据,他们都是cold。RxJava-JDBC为每个Observer执行查询。这意味着如果数据在两个订阅发生了变化,第二个Observer可以拿到与第一个不同的emissions。cold Observables会为每个Observer,重新生成emissions。
Hot Observables
hot Observable更像是一个广播电台。在同一个时刻,它向所有的Observers广播相同的emissions。如果一个Observer订阅了一个hot Observable,接收相同的emissions,然后来了另一个Observer,第二个Observer会错过这些emissions。就像广播电台,如果你打开晚了,你就听不到那首歌。
hot Observables通常代表事件而不是有限数据集。事件可以携带数据,但是,他们是时间敏感的组件,后来的observers会错过先前的数据。
例如,一个JavaFX或者一个Android UI事件能表示成一个hot Observable。在JavaFX里,你能使用一个ToggleButton对象的selectedProperty()方法
增加一个Observable。然后把布尔emissions转换成字符串,表示该按钮的状态(UP或者DOWN),使用一个Observer在Label里显示:
import io.reactivex.Observable;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class MyJavaFxApp extends Application {
@Override
public void start(Stage stage) throws Exception {
ToggleButton toggleButton = new ToggleButton("TOGGLE ME");
Label label = new Label();
Observable<Boolean> selectedStates = valuesOf(toggleButton.selectedProperty());
selectedStates.map(selected -> selected ? "DOWN" : "UP")
.subscribe(label::setText);
VBox vBox = new VBox(toggleButton, label);
stage.setScene(new Scene(vBox));
stage.show();
}
private static <T> Observable<T> valuesOf(final ObservableValue<T> fxObservable) {
return Observable.create(observableEmitter -> {
//emit initial state
observableEmitter.onNext(fxObservable.getValue());
//emit value changes uses a listener
final ChangeListener<T> listener = (observableValue, prev, current) -> observableEmitter.onNext(current);
fxObservable.addListener(listener);
});
}
}
JavaFX ObservableValue与RxJava Observable无关。它是JavaFX的,但是,使用valuesOf()工厂方法,很容易转成Observable。
每次点击ToggleButton,Observable会根据状态发射相应的true或者false。
JavaFX和Android的UI事件主要的hot Observables,你也能使用hot Observables反映服务器请求。如果你增加一个Observable
为某Twitter主题发射推文,这也是一个hot Observable。hot Observable不一定非要是无限的,只要是向所有的Observers共享emissions,
不replay错过的emissions的都是hot。
ConnectableObservable
一种有用的hot Observable是ConnectableObservable。它可以是任何Observable(包括cold),让它变hot,这样所有的emissions都只给Observers一次。想做这种转变,你只需要简单地在任何Observable上调用publish(),就会产生一个ConnectableObservable。但是,subscribing不能开始emissions。你需要调用它的connect()方法启动emissions的发射。这样,你可以预先设置你的Observers:
import io.reactivex.Observable;
import io.reactivex.observables.ConnectableObservable;
public class Launcher {
public static void main(String[] args) {
ConnectableObservable<String> source =
Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon").publish();
//Set up observer 1
source.subscribe(s -> System.out.println("Observer 1: " + s));
//Set up observer 2
source.map(String::length)
.subscribe(i -> System.out.println("Observer 2: " + i));
//Fire!
source.connect();
}
}
它的输出是
Observer 1: Alpha
Observer 2: 5
Observer 1: Beta
Observer 2: 4
Observer 1: Gamma
Observer 2: 5
Observer 1: Delta
Observer 2: 5
Observer 1: Epsilon
Observer 2: 7
注意,一个Observer收到字符串,另一个就收到长度,这两个是交错进行的。订阅都是预先设置好的,然后调用connect()开始发射。每个emission同时送给每个Observer。使用ConnectableObservable,强制每个emission同时发给所有的Observers,这叫做多播(multicasting),以后详细讲。
ConnectableObservable是有用的,防止数据被replay给每个Observer。
如果你觉得重播太昂贵了,你可能想这样做,每个emission只发射一次。甚至有多个下游Observers的时候,你也可以简单地强制上游使用一个流实例。
多个Observers通常会导致上游有多个流实例,但是,使用publish()返回ConnectableObservable来合并所有的上游形成一个流。
请记住,ConnectableObservable是hot。