为什么点击事件需要onBackpressure()?

时间:2016-02-11 15:31:29

标签: android rx-java rx-android

我正在尝试针对按钮点击的某些操作进行错误处理。对于绑定我使用RxAndroid + RxAndroid。似乎它必须使用下面的代码,但它不与onBackpressure()的注释行一起使用:

CurrentUser signIn() {
    throw new RuntimeException();
}
Integer x = 1;
PublishSubject<Throwable> loginingFailedSubject = PublishSubject.create();

@Override
public void onStart() {
    super.onStart();
    RxView.clicks(loginButton)
            .observeOn(AndroidSchedulers.mainThread())
            .doOnNext((v) -> setLoginingWaiting())

            .observeOn(Schedulers.newThread())
            .map((v) -> signIn())
            .lift(new SuppressErrorOperator<>(throwable -> {
                Log.e("MyTag", "Oops, failed " + x.toString() + " times!");
                ++x;
                loginingFailedSubject.onNext(throwable);
            }))
            //.onBackpressureBuffer()

            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(user -> setLoginedUser(user));

    loginingFailedSubject
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(throwable -> setLoginingFailed(throwable));
}

这是SuppressErrorOperator代码:

public final class SuppressErrorOperator<T> implements 

Observable.Operator<T, T> {
    final Action1<Throwable> errorHandler;

    public SuppressErrorOperator(Action1<Throwable> errorHandler) {
        this.errorHandler = errorHandler;
    }

    public SuppressErrorOperator() {this(null);}

    @Override
    public Subscriber<? super T> call(final Subscriber<? super T> subscriber) {
        return new Subscriber<T>(subscriber) {
            @Override
            public void onCompleted() {
                subscriber.onCompleted();
            }

            @Override
            public void onError(Throwable e) {
                if (errorHandler != null) {
                    errorHandler.call(e);
                }
            }

            @Override
            public void onNext(T t) {
                subscriber.onNext(t);
            }
        };
    }
}

这是我在最后100次点击后在logcat中得到的内容: Oops, failed 16 times!

它在exaclty 16次之后停止,并且在17日,它运行setLoginingWaiting()(我看到它,因为此方法禁用按钮也意味着,每个请求没有人可以点击超过1次。或者接近该数字)就这样。好像它根本没有到达.lift()

但如果我取消注释.onBackpressureBuffer(),它现在完全有效!我读了很多关于背压的文章。我甚至花了一整天时间来理解ObservableSubscriber e.t.c的源代码。

我知道,16 - 是Android缓冲区的常量大小。但为什么会受到打击?我不经常点击按钮。此外,根本没有onNext(),所以缓冲区在任何情况下都不能超过! onError()吞噬了所有Operator

我也知道observeOn()通过pull协议工作,所以它内部想要使用request()。如果我在observeOn()之前发表评论.subscribe(user -> setLoginedUser(user)); - 它也会起作用(但当然,这是不可接受的)。

但是为什么以及为什么需要onBackpressure()才能活着?另外,为什么它会在没有MissingBackpressureException之类的任何例外情况下死亡?

3 个答案:

答案 0 :(得分:3)

问题在于您干扰了流的生命周期。 map崩溃,但您取消了异常,并且没有值发送到下游。如果下游没有得到任何值,它就不知道它应该请求更多,因此整个序列停止,让缓冲区填满。 onBackpressureBuffer仅适用于请求Long.MAX_VALUE并保持源运行。

但请注意,map不应该像现在这样工作,而是取消订阅该函数的第一个错误信号。

正确的选择是:

RxView.clicks(loginButton)
.observeOn(AndroidSchedulers.mainThread())
.doOnNext((v) -> setLoginingWaiting())
.observeOn(Schedulers.newThread())
.flatMap(v -> {
    try {
        return Observable.just(signIn());
    } catch (Throwable ex) {
        Log.e("MyTag", "Oops, failed " + x.toString() + " times!");
        ++x;
        loginingFailedSubject.onNext(ex);
        return Observable.empty();
    }
 })
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(user -> setLoginedUser(user));

答案 1 :(得分:1)

OperatorObserveOn有一个大小为RxRingBuffer.SIZE的队列(在android上为16),如果超过此队列的大小,将抛出MissingBackpressure异常。

通常,为了避免背压问题,您可以限制切换线程,限制事件发射(油门,缓冲等)或使用onBackpressureXXX操作符。

虽然看起来像你的情况 - 登录按钮 - 你只需要一次处理一个请求,所以为什么不用ProgressBar隐藏按钮或者在请求时设置启用(false)

答案 2 :(得分:1)

我认为OperatorObserveOn::ObserveOnSubscriber中存在错误。

在init方法中,它设置新的生产者并由内部订阅者(子)requested递增ObserveOnSubscriberrequested)。但是当ObserveOnSubscriber是孩子时,它的默认值(如果是Android - 16)为Subscriber::requestedrequested的实际值为ObserveOnSubscriber::requested
问题是,Subscriber::requested已使用且未更新。所以解决方法是重写ObserveOnSubscriber的init方法(我刚刚复制了OperatorObserveOn的源代码,并在包中使用与RxJava同名的副本)。

    void init() {
        // don't want this code in the constructor because `this` can escape through the 
        // setProducer call
        Subscriber<? super T> localChild = child;

        localChild.setProducer(new Producer() {

            @Override
            public void request(long n) {
                if (n > 0L) {
                    BackpressureUtils.getAndAddRequest(requested, n);
                    ObserveOnSubscriber.this.request(requested.get());
                    schedule();
                }
            }

        });
        localChild.add(recursiveScheduler);
        localChild.add(this);
    }

我还在github上创建了issue