0x00 取消订阅
首先我们知道使用rxjava执行操作的时候,基本流程大致如下,取消操作时只需要调用subscription.unsubscribe()
即可:
Subscription subscription = Observable.<String>create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
}
println("call...");
subscriber.onNext("aaaaaaa");
subscriber.onCompleted();
}
}).subscribeOn(Schedulers.io())
.observeOn(Schedulers.newThread())
.unsubscribeOn(Schedulers.newThread())
.subscribe(new PrintSubscriber<>());
那么我们在执行操作的过程中,如何知道一个操作被取消了呢?熟悉rxjava的童鞋可能一下子就会想到,直接使用doOnUnsubscribe()
不就可以了吗?这有什么好纠结的。
Subscription subscription = Observable.<String>create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
}
println("call...");
subscriber.onNext("aaaaaaa");
subscriber.onCompleted();
}
}).doOnUnsubscribe(new Action0() {
@Override
public void call() {
System.out.println("doOnUnsubscribe");
}
}).subscribeOn(Schedulers.io())
.observeOn(Schedulers.newThread())
.unsubscribeOn(Schedulers.newThread())
.subscribe(new PrintSubscriber<>());
我们直接执行上述代码片段,就会发现当一个Observable执行完成(无论是正常执行完成,还是异常执行完成)后,都会调用unsubscribe
的全部hook,为什么呢?
0x01 doOnUnsubscribe()调用时机
我们知道在RxJava中,所有的Subscriber
最终都会被包裹成一个SafeSubscriber
来执行,而在SafeSubscriber
中,只要其执行完onCompleted()
、onError()
都会在finally函数块中调用unsubscribe()
,进而回调到用户先前通过doOnUnsubscribe
注册的回调。
@Override
public void onCompleted() {
if (!done) {
done = true;
try {
actual.onCompleted();
} catch (Throwable e) {
// we handle here instead of another method so we don't add stacks to the frame
// which can prevent it from being able to handle StackOverflow
Exceptions.throwIfFatal(e);
RxJavaPluginUtils.handleException(e);
throw new OnCompletedFailedException(e.getMessage(), e);
} finally {
try {
// Similarly to onError if failure occurs in unsubscribe then Rx contract is broken
// and we throw an UnsubscribeFailureException.
unsubscribe();
} catch (Throwable e) {
RxJavaPluginUtils.handleException(e);
throw new UnsubscribeFailedException(e.getMessage(), e);
}
}
}
}
protected void _onError(Throwable e) {
RxJavaPluginUtils.handleException(e);
try {
actual.onError(e);
} catch (Throwable e2) {
if (e2 instanceof OnErrorNotImplementedException) {
/*
* onError isn't implemented so throw
*
* https://github.com/ReactiveX/RxJava/issues/198
*
* Rx Design Guidelines 5.2
*
* "when calling the Subscribe method that only has an onNext argument, the OnError behavior
* will be to rethrow the exception on the thread that the message comes out from the observable
* sequence. The OnCompleted behavior in this case is to do nothing."
*/
try {
unsubscribe();
} catch (Throwable unsubscribeException) {
RxJavaPluginUtils.handleException(unsubscribeException);
throw new RuntimeException("Observer.onError not implemented and error while unsubscribing.", new CompositeException(Arrays.asList(e, unsubscribeException)));
}
throw (OnErrorNotImplementedException) e2;
} else {
/*
* throw since the Rx contract is broken if onError failed
*
* https://github.com/ReactiveX/RxJava/issues/198
*/
RxJavaPluginUtils.handleException(e2);
try {
unsubscribe();
} catch (Throwable unsubscribeException) {
RxJavaPluginUtils.handleException(unsubscribeException);
throw new OnErrorFailedException("Error occurred when trying to propagate error to Observer.onError and during unsubscription.", new CompositeException(Arrays.asList(e, e2, unsubscribeException)));
}
throw new OnErrorFailedException("Error occurred when trying to propagate error to Observer.onError", new CompositeException(Arrays.asList(e, e2)));
}
}
// if we did not throw above we will unsubscribe here, if onError failed then unsubscribe happens in the catch
try {
unsubscribe();
} catch (RuntimeException unsubscribeException) {
RxJavaPluginUtils.handleException(unsubscribeException);
throw new OnErrorFailedException(unsubscribeException);
}
}
那么问题来了,既然无论什么情况下doOnUnsubscribe()
注册的回调都会被调用,我们如何区分一个Observable是正常执行完成了,还是被主动取消了呢?
0x02 区分主动取消与被动取消
我们知道Rxjava包含很多的hook,可以从doOnUnsubscribe()
、doOnCompleted()
、doOnError()
这些hook入手,然后在各个hook中区分好执行顺序即可,大致代码如下:
import rx.Observable;
import rx.Subscriber;
import rx.Subscription;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.schedulers.Schedulers;
import java.util.concurrent.atomic.AtomicBoolean;
public class UserCancelDemo {
public static void main(String[] args) {
final UserCancelDemo userCancelDemo = new UserCancelDemo();
Subscription subscription = userCancelDemo.create()
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.newThread())
.unsubscribeOn(Schedulers.newThread())
.subscribe();
subscription.unsubscribe();
while (true) ;
}
private AtomicBoolean mCompleteOccurs = new AtomicBoolean(false);
private AtomicBoolean mErrorOccurs = new AtomicBoolean(false);
private Subscriber mSubscriber;
public Observable<String> create() {
return Observable.<String>create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
}
System.out.println("call...");
subscriber.onNext("aaaaaaa");
subscriber.onCompleted();
}
}).doOnError(new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
if (mSubscriber.isUnsubscribed()) {
System.out.println("doOnError: user have cancel subscription...");
return;
}
System.out.println("doOnError: error occurs " + throwable);
mErrorOccurs.set(true);
}
}).doOnCompleted(new Action0() {
@Override
public void call() {
if (mSubscriber.isUnsubscribed()) {
System.out.println("doOnCompleted: user have cancel subscription...");
return;
}
System.out.println("doOnCompleted: onCompleted.");
mCompleteOccurs.set(true);
}
}).doOnUnsubscribe(new Action0() {
@Override
public void call() {
if (mErrorOccurs.get() || mCompleteOccurs.get()) {
System.out.println("doOnUnsubscribe: rxjava auto unsubscribe...");
} else {
System.out.println("doOnUnsubscribe: user cancel subscription...");
}
}
}).lift(new Observable.Operator<String, String>() {
@Override
public Subscriber<? super String> call(Subscriber<? super String> subscriber) {
mSubscriber = subscriber;
return mSubscriber;
}
});
}
}
0x03 为什么要使用原子操作?
我们在使用RxJava时,可以使用subscribeOn()
、observeOn()
、unsubscribeOn()
指定不同内容运行的线程,而doOnUnsubscribe()
、doOnCompleted()
、doOnError()
三个hook则分别运行在上面三个函数指定的线程中。
.subscribeOn(Schedulers.io()) // 指定Observable、doOnCompleted()、doOnError()的线程。
.observeOn(Schedulers.newThread()) // 指定Subscriber的订阅者的线程。
.unsubscribeOn(Schedulers.newThread()) // 指定doOnUnsubscribe()的线程。