Releases: ReactiveX/RxJava
0.17.2
- Pull 963 A more robust JMH benchmarking set-up
- Pull 964 SubjectSubscriptionManager fix.
- Pull 970 Notifications for the allocation averse.
- Pull 973 Merge - Handle Bad Observables
- Pull 974 TestSubject, TestObserver and TestScheduler Improvements
- Pull 975 GroupBy & Time Gap Fixes
- Pull 976 parallel-merge unit test assertions
- Pull 977 Dematerialize - handle non-materialized terminal events
- Pull 982 Pivot Operator
- Pull 984 Tests and Javadoc for Pivot
- Pull 966 Reimplement the ElementAt operator and add it to rxjava-scala
- Pull 965 BugFix: Chain Subscription in TimeoutSubscriber and SerializedSubscriber
- Pull 986 Fix SynchronizedObserver.runConcurrencyTest
- Pull 987 Fix Non-Deterministic Pivot Test
- Pull 988 OnErrorFailedException
Artifacts: Maven Central
0.17.1
- Pull 953 Make ObserveOnTest.testNonBlockingOuterWhileBlockingOnNext deterministic
- Pull 930 Initial commit of the Android samples module
- Pull 938 OperatorWeakBinding (deprecates OperatorObserveFromAndroidComponent)
- Pull 952 rxjava-scala improvements and reimplemented the
amb
operator - Pull 955 Fixed ReplaySubject leak
- Pull 956 Fixed byLine test to use line.separator system property instead of \n.
- Pull 958 OperatorSkipWhile
- Pull 959 OperationToFuture must throw CancellationException on get() if cancelled
- Pull 928 Fix deadlock in SubscribeOnBounded
- Pull 960 Unit test for "Cannot subscribe to a Retry observable once all subscribers unsubscribed"
- Pull 962 Migrate from SynchronizedObserver to SerializedObserver
Artifacts: Maven Central
0.17.0
Version 0.17.0 contains some significant signature changes that allow us to significantly improve handling of synchronous Observables and simplify Schedulers. Many of the changes have backwards compatible deprecated methods to ease the migration while some are breaking.
The new signatures related to Observable
in this release are:
// A new create method takes `OnSubscribe` instead of `OnSubscribeFunc`
public final static <T> Observable<T> create(OnSubscribe<T> f)
// The new OnSubscribe type accepts a Subscriber instead of Observer and does not return a Subscription
public static interface OnSubscribe<T> extends Action1<Subscriber<? super T>>
// Subscriber is an Observer + Subscription
public abstract class Subscriber<T> implements Observer<T>, Subscription
// The main `subscribe` behavior receives a Subscriber instead of Observer
public final Subscription subscribe(Subscriber<? super T> subscriber)
// Subscribing with an Observer however is still appropriate
// and the Observer is automatically converted into a Subscriber
public final Subscription subscribe(Observer<? super T> observer)
// A new 'lift' function allows composing Operator implementations together
public <R> Observable<R> lift(final Operator<? extends R, ? super T> lift)
// The `Operator` used with `lift`
public interface Operator<R, T> extends Func1<Subscriber<? super R>, Subscriber<? super T>>
Also changed is the Scheduler
interface which is much simpler:
public abstract class Scheduler {
public Subscription schedule(Action1<Scheduler.Inner> action);
public Subscription schedule(Action1<Scheduler.Inner> action, long delayTime, TimeUnit unit);
public Subscription schedulePeriodically(Action1<Scheduler.Inner> action, long initialDelay, long period, TimeUnit unit);
public final Subscription scheduleRecursive(final Action1<Recurse> action)
public long now();
public int degreeOfParallelism();
public static class Inner implements Subscription {
public abstract void schedule(Action1<Scheduler.Inner> action, long delayTime, TimeUnit unit);
public abstract void schedule(Action1<Scheduler.Inner> action);
public long now();
}
public static final class Recurse {
public final void schedule();
public final void schedule(long delay, TimeUnit unit);
}
}
This release applies many lessons learned over the past year and seeks to streamline the API before we hit 1.0.
As shown in the code above the changes fall into 2 major sections:
1) Lift/Operator/OnSubscribe/Subscriber
Changes that allow unsubscribing from synchronous Observables without needing to add concurrency.
2) Schedulers
Simplification of the Scheduler
interface and make clearer the concept of "outer" and "inner" Schedulers for recursion.
Lift/Operator/OnSubscribe/Subscriber
New types Subscriber
and OnSubscribe
along with the new lift
function have been added. The reasons and benefits are as follows:
1) Synchronous Unsubscribe
RxJava versions up until 0.16.x are unable to unsubscribe from a synchronous Observable such as this:
Observable<Integer> oi = Observable.create(new OnSubscribe<Integer>() {
@Override
public void call(Observer<? super Integer> Observer) {
for (int i = 1; i < 1000000; i++) {
subscriber.onNext(i);
}
subscriber.onCompleted();
}
});
Subscribing to this Observable
will always emit all 1,000,000 values even if unsubscribed such as via oi.take(10)
.
Version 0.17.0 fixes this issue by injecting the Subscription
into the OnSubscribe
function to allow code like this:
Observable<Integer> oi = Observable.create(new OnSubscribe<Integer>() {
@Override
public void call(Subscriber<? super Integer> subscriber) {
// we now receive a Subscriber instead of Observer
for (int i = 1; i < 1000000; i++) {
// the OnSubscribe can now check for isUnsubscribed
if (subscriber.isUnsubscribed()) {
return;
}
subscriber.onNext(i);
}
subscriber.onCompleted();
}
});
Subscribing to this will now correctly only emit 10 onNext
and unsubscribe:
// subscribe with an Observer
oi.take(10).subscribe(new Observer<Integer>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Integer t) {
println("Received: " + t);
}
})
Or the new Subscriber
type can be used and the Subscriber
itself can unsubscribe
:
// or subscribe with a Subscriber which supports unsubscribe
oi.subscribe(new Subscriber<Integer>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Integer t) {
println("Received: " + t);
if(t >= 10) {
// a Subscriber can unsubscribe
this.unsubscribe();
}
}
})
2) Custom Operator Chaining
Because Java doesn't support extension methods, the only approach to applying custom operators without getting them added to rx.Observable
is using static methods. This has meant code like this:
MyCustomerOperators.operate(observable.map(...).filter(...).take(5)).map(...).subscribe()
In reality we want:
observable.map(...).filter(...).take(5).myCustomOperator().map(...).subscribe()
Using the newly added lift
we can get quite close to this:
observable.map(...).filter(...).take(5).lift(MyCustomOperator.operate()).map(...).subscribe()
Here is how the proposed lift
method looks if all operators were applied with it:
Observable<String> os = OBSERVABLE_OF_INTEGERS.lift(TAKE_5).lift(MAP_INTEGER_TO_STRING);
Along with the lift
function comes a new Operator
signature:
public interface Operator<R, T> extends Func1<Subscriber<? super R>, Subscriber<? super T>>
All operator implementations in the rx.operators
package will over time be migrated to this new signature.
NOTE: Operators that have not yet been migrated do not work with synchronous unsubscribe.
3) Simpler Operator Implementations
The lift
operator injects the necessary Observer
and Subscription
instances (via the new Subscriber
type) and eliminates (for most use cases) the need for manual subscription management. Because the Subscription
is available in-scope there are no awkward coding patterns needed for creating a Subscription
, closing over it and returning and taking into account synchronous vs asynchronous.
For example, the body of fromIterable
is simply:
public void call(Subscriber<? super T> o) {
for (T i : is) {
if (o.isUnsubscribed()) {
return;
}
o.onNext(i);
}
o.onCompleted();
}
The take
operator is:
public Subscriber<? super T> call(final Subscriber<? super T> child) {
final CompositeSubscription parent = new CompositeSubscription();
if (limit == 0) {
child.onCompleted();
parent.unsubscribe();
}
child.add(parent);
return new Subscriber<T>(parent) {
int count = 0;
boolean completed = false;
@Override
public void onCompleted() {
if (!completed) {
child.onCompleted();
}
}
@Override
public void onError(Throwable e) {
if (!completed) {
child.onError(e);
}
}
@Override
public void onNext(T i) {
if (!isUnsubscribed()) {
child.onNext(i);
if (++count >= limit) {
completed = true;
child.onCompleted();
unsubscribe();
}
}
}
};
}
4) Recursion/Loop Performance with Unsubscribe
The fromIterable
use case is 20x faster when implemented as a loop instead of recursive scheduler (see a18b8c1).
Several places we can remove recursive scheduling used originally for unsubscribe support and use a loop instead.
Schedulers
Schedulers were greatly simplified to a design based around Action1<Inner>
.
public abstract class Scheduler {
public Subscription schedule(Action1<Scheduler.Inner> action);
public Subscription schedule(Action1<Scheduler.Inner> action, long delayTime, TimeUnit unit);
public Subscription schedulePeriodically(Action1<Scheduler.Inner> action, long initialDelay, long period, TimeUnit unit);
public final Subscription scheduleRecursive(final Action1<Recurse> action)
public long now();
public int degreeOfParallelism();
public static class Inner implements Subscription {
public abstract void schedule(Action1<Scheduler.Inner> action, long delayTime, TimeUnit unit);
public abstract void schedule(Action1<Scheduler.Inner> action);
public long now();
}
public static final class Recurse {
public final void schedule();
public final void schedule(long delay, TimeUnit unit);
}
}
This design change originated from three findings:
- It was very easy to cause memory leaks or inadvertent parallel execution since the distinction between outer and inner scheduling was not obvious.
To solve this the new design explicitly has the outer Scheduler
and then Scheduler.Inner
for recursion.
- The passing of state is not useful since scheduling over network boundaries with this model does not work.
In this new design all state passing signatures have been removed. This was determined while implem...
0.17.0 Release Candidate 7
Pre-release of 0.17.0 for testing. See details at #802.
0.17.0 Release Candidate 6
Pre-release of 0.17.0 for testing. See details at #802.
Compared with RC5 this has changes to:
- Clojure bindings
- TestSubscriber
0.17.0 Release Candidate 5
Pre-release of 0.17.0 for testing. See details at #802.
Compared with RC4 this has changes to:
- error handling of operators via
lift
- debug module
0.17.0 Release Candidate 4
Pre-release of 0.17.0 for testing. See details at #802.
Compared with RC3 this has changes to:
- doOnTerminate
- zip bug fix
- execution hook cleanup
0.17.0 Release Candidate 3
Pre-release of 0.17.0 for testing. See details at #802.
Compared with RC2 this has changes to:
- a new RxJavaDefaultSchedulers plugin
- bug fixes
0.17.0 Release Candidate 2
Pre-release of 0.17.0 for testing. See details at #802.
Compared with RC1 this has changes to:
observeOn
subscribeOn
- new
unsubscribeOn
operator - Android and Swing component Subscriptions
- movement of
rx.util
packages - error handling
0.17.0 Release Candidate 1
Pre-release of 0.17.0 for testing. See details at #802.
In particular please test:
- observeOn
- subscribeOn
- groupBy
- zip
- Scheduler