Rxjava some knowledge points to learn

Disclaimer: This article is the experience notes of RxJava2 actual combat knowledge . It is recommended to read the original text for non-original and jerky parts.

1. Thread switching to clarify doubts

  • Schedulers.computation(): For computing tasks, the default number of threads is equal to the number of processors.
  • Schedulers.from(Executor executor): Use Executor as the scheduler, I have not used it.
  • Schedulers.io( ): It is used for IO-intensive tasks, such as accessing the network, database operations, etc. It is also the most commonly used.
  • Schedulers.newThread( ): Create a new thread for each task.
  • Schedulers.trampoline( ): When other queued tasks are completed, start execution in the current thread queue.
  • Schedulers.single(): All tasks share a background thread.

2.debounce, filter and switchMap do a real-time search

  • Using the debounce operator, when the input box changes, the event will not be sent to the downstream immediately, but will wait for 200ms. If the input box has not changed during this event, the event will be sent; After reaching the new keyword, continue to wait for 200ms.
    The debounce principle is similar to that after we receive a request, we send a delay message to the downstream. If no new request is received within this delay time, the downstream will receive the message; and if it is received within this delay time new request, the previous message will be cancelled, and a new delayed message will be resent, and so on.
    And if the upstream sends the onComplete message during this time, even if the waiting time is not reached, the downstream will receive the message immediately.
  • Using the filter operator, events are sent downstream only if the length of the keyword is greater than 0.
  • The switchMap operator is used, so that after the abc request is initiated, even if the ab result is returned, it will not be sent to the downstream, thus avoiding the problem of mismatch between the search term and the association result described above. The principle of switchMap is to convert upstream events into one or more new Observables, but it is important that if the node receives a new event, then if the Observable generated by the previously received time has not Send events to the downstream, then the downstream will never receive the events it sent.
 mPublishSubject.debounce(200, TimeUnit.MILLISECONDS).filter(new Predicate<String>() {

            @Override
            public boolean test(String s) throws Exception {
                return s.length() > 0;
            }

        }).switchMap(new Function<String, ObservableSource<String>>() {

            @Override
            public ObservableSource<String> apply(String query) throws Exception {
                return getSearchObservable(query);
            }

        }).observeOn(AndroidSchedulers.mainThread()).subscribe(mDisposableObserver);

    private Observable<String> getSearchObservable(final String query) {
        return Observable.create(new ObservableOnSubscribe<String>() {

            @Override
            public void subscribe(ObservableEmitter<String> observableEmitter) throws Exception {
                Log.d("SearchActivity", "开始请求,关键词为:" + query);
                try {
                    Thread.sleep(100 + (long) (Math.random() * 500));
                } catch (InterruptedException e) {
                    if (!observableEmitter.isDisposed()) {
                        observableEmitter.onError(e);
                    }
                }
                Log.d("SearchActivity", "结束请求,关键词为:" + query);
                observableEmitter.onNext("完成搜索,关键词为:" + query);
                observableEmitter.onComplete();
            }
        }).subscribeOn(Schedulers.io());
    }

3.zip operator

It receives multiple Observables and a function whose formal parameter is the data sent by these Observables, and will call back the function after all Observables have been emitted.
Through the zip operator, we can achieve the need to wait for multiple network requests to complete and then return.

4. Polling operation

• Fixed delay: Use the intervalRange operator to execute the task every 3s.
• Variable-length delay: Implemented using the repeatWhen operator. After the first task is executed, wait 4s before executing the second task. After the second task is executed, wait for 5s and increase sequentially.

 private void startSimplePolling() {
        Observable<Long> observable = Observable.intervalRange(0, 5, 0, 3000, TimeUnit.MILLISECONDS).take(5)
.doOnNext(new Consumer<Long>() {

            @Override
            public void accept(Long aLong) throws Exception {
      //这里使用了doOnNext,因此DisposableObserver的onNext要等到该方法执行完才会回调。
                doWork(); 
            }

        });
        DisposableObserver<Long> disposableObserver = getDisposableObserver();
        observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(disposableObserver);
        mCompositeDisposable.add(disposableObserver);
    }

    private void startAdvancePolling() {
        Log.d(TAG, "startAdvancePolling click");
        Observable<Long> observable = Observable.just(0L).doOnComplete(new Action() {

            @Override
            public void run() throws Exception {
                doWork();
            }

        }).repeatWhen(new Function<Observable<Object>, ObservableSource<Long>>() {

            private long mRepeatCount;

            @Override
            public ObservableSource<Long> apply(Observable<Object> objectObservable) throws Exception {
                //必须作出反应,这里是通过flatMap操作符。
                return objectObservable.flatMap(new Function<Object, ObservableSource<Long>>() {

                    @Override
                    public ObservableSource<Long> apply(Object o) throws Exception {
                        if (++mRepeatCount > 4) {
                            //return Observable.empty(); //发送onComplete消息,无法触发下游的onComplete回调。
                            return Observable.error(new Throwable("Polling work finished")); //发送onError消息,可以触发下游的onError回调。
                        }
                        Log.d(TAG, "startAdvancePolling apply");
                        return Observable.timer(3000 + mRepeatCount * 1000, TimeUnit.MILLISECONDS);
                    }

                });
            }

        });
        DisposableObserver<Long> disposableObserver = getDisposableObserver();
        observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(disposableObserver);
        mCompositeDisposable.add(disposableObserver);
    }

    private DisposableObserver<Long> getDisposableObserver() {

        return new DisposableObserver<Long>() {

            @Override
            public void onNext(Long aLong) {}

            @Override
            public void onError(Throwable throwable) {
                Log.d(TAG, "DisposableObserver onError, threadId=" + Thread.currentThread().getId() + ",reason=" + throwable.getMessage());
            }

            @Override
            public void onComplete() {
                Log.d(TAG, "DisposableObserver onComplete, threadId=" + Thread.currentThread().getId());
            }
        };
    }

    private void doWork() {
        long workTime = (long) (Math.random() * 500) + 500;
        try {
            Log.d(TAG, "doWork start,  threadId=" + Thread.currentThread().getId());
            Thread.sleep(workTime);
            Log.d(TAG, "doWork finished");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

4.1 intervalRange & doOnNext implement fixed delay polling

The advantages of this operator are:
• Compared with interval, it can specify the delay of the first sent data item and the number of sent data items.
• Compared with range, it can specify the delay between sending two pieces of data.
The meaning of the received parameter of intervalRange is:
// Observable.intervalRange(0, 5, 0, 3000, TimeUnit.MILLISECONDS)
• start: The starting value of the sent data , which is a Long type.
• count: How many items of data are sent in total.
• initialDelay: The initial delay when sending the first data item.
• period: The interval between two items of data.
• TimeUnit: Time unit.
In the polling operation, some time-consuming network requests are generally made, so we choose to process them in doOnNext, which will be called before the downstream onNext method is called back, but its running thread can be specified by subscribeOn, and the downstream running thread will be called again. Switch the main thread through observerOn, and verify the result by printing the corresponding thread ID.
When the required data items are sent, the onComplete method will be called back.

4.2 repeatWhen implements variable-length delay polling

repeatWhen implements polling because it provides us with the function of re-subscription, and re-subscription has two elements:
• The upstream tells us that a subscription has been completed, which requires the upstream to call back the onComplete function.
• We tell the upstream whether re-subscription is required, which is determined by the Observable returned by the Function function of repeatWhen. If the Observable sends onComplete or onError, it means that re-subscription is not required and the whole process is ended; otherwise, the re-subscription operation is triggered.

The difficulty of repeatWhen lies in how to define its Function parameter:
1. If the output Observable sends onComplete or onError, it means that re-subscription is not required and the whole process is ended; otherwise, the re-subscription operation is triggered. That is, it only serves as a notification whether to trigger a re-subscription, it doesn't matter what data is sent by onNext.
2. The Function function will only call back once for each subscribed data stream, and it is triggered when onComplete, it will not receive any onNext event.
And when we don't need to resubscribe, there are two ways:
• Return Observable.empty(), send onComplete message, but DisposableObserver will not call back onComplete.
• Return to Observable.error(new Throwable(“Polling work finished”)), DisposableObserver's onError will be called back and accept the past error message.

 private static final String[] MSG_ARRAY = new String[] {
            MSG_WAIT_SHORT,
            MSG_WAIT_SHORT,
            MSG_WAIT_LONG,
            MSG_WAIT_LONG
    };
 private void startRetryRequest() {
        Observable<String> observable = Observable.create(new ObservableOnSubscribe<String>() {

            @Override
            public void subscribe(ObservableEmitter<String> e) throws Exception {
                int msgLen = MSG_ARRAY.length;
                doWork();
                //模拟请求的结果,前四次都返回失败,并将失败信息递交给retryWhen。
                if (mMsgIndex < msgLen) { //模拟请求失败的情况。
                    e.onError(new Throwable(MSG_ARRAY[mMsgIndex]));
                    mMsgIndex++;
                } else { //模拟请求成功的情况。
                    e.onNext("Work Success");
                    e.onComplete();
                }
            }

        }).retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {

            private int mRetryCount;

            @Override
            public ObservableSource<?> apply(Observable<Throwable> throwableObservable) throws Exception {
                return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {

                    @Override
                    public ObservableSource<?> apply(Throwable throwable) throws Exception {
                        String errorMsg = throwable.getMessage();
                        long waitTime = 0;
                        switch (errorMsg) {
                            case MSG_WAIT_SHORT:
                                waitTime = 2000;
                                break;
                            case MSG_WAIT_LONG:
                                waitTime = 4000;
                                break;
                            default:
                                break;
                        }
                        Log.d(TAG, "发生错误,尝试等待时间=" + waitTime + ",当前重试次数=" + mRetryCount);
                        mRetryCount++;
                        return waitTime > 0 && mRetryCount <= 4 ? 
                        Observable.timer(waitTime, TimeUnit.MILLISECONDS) : Observable.error(throwable);
                    }

                });
            }

        });
        DisposableObserver<String> disposableObserver = new DisposableObserver<String>() {

            @Override
            public void onNext(String value) {
                Log.d(TAG, "DisposableObserver onNext=" + value);
            }

            @Override
            public void onError(Throwable e) {
                Log.d(TAG, "DisposableObserver onError=" + e);
            }

            @Override
            public void onComplete() {
                Log.d(TAG, "DisposableObserver onComplete");
            }
        };
        observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(disposableObserver);
        mCompositeDisposable.add(disposableObserver);
    }

retryWhen provides the function of re-subscription. For retryWhen, its re-subscription trigger has two elements:

  • The upstream notifies retryWhen the subscription stream has been completed, and asks whether it needs to be re-subscribed. The query is triggered by the onError event.
  • retryWhen decides whether to re-subscribe according to the type of onError. It notifies by returning an ObservableSource. If the ObservableSource returns onComplete/onError, then re-subscription will not be triggered; if onNext is sent, re-subscription will be triggered.

The key to realizing retryWhen is how to define its Function parameter:
• Function's input is an Observable, and its output is a generic ObservableSource. If we receive the message sent by the Observable, then we can get the error type sent by the upstream and process the response according to that type.
• If the output Observable sends onComplete or onError, it means that re-subscription is not required, and the whole process ends; otherwise, the re-subscription operation is triggered. That is, it only serves as a notification whether to trigger a re-subscription, it doesn't matter what data is sent by onNext.
• The Function function will only call back once for each subscribed data stream, and it will be triggered when onError(Throwable throwable), it will not receive any onNext event.
• In the Function function, the input Observable must be processed, here we use the flatMap operator to receive the upstream data.

4.3 Comparison of retryWhen and repeatWhen

The biggest difference between retryWhen and repeatWhen is: retryWhen triggers a query on whether to resubscribe after receiving onError, while repeatWhen triggers through onComplete.

5. Input form validation implemented by combineLatest

In the following example, there are two input boxes, corresponding to the user name and password respectively, and their length requirements are 2~8 and 4~16 respectively. If both are correct, the text of the login button becomes "Login". Otherwise, "Invalid username or password" is displayed.

  mEtName.addTextChangedListener(new EditTextMonitor(mNameSubject));
        mEtPassword.addTextChangedListener(new EditTextMonitor(mPasswordSubject));
        Observable<Boolean> observable = Observable.combineLatest(mNameSubject, 
        mPasswordSubject, new BiFunction<String, String, Boolean>() {

            @Override
            public Boolean apply(String name, String password) throws Exception {
                int nameLen = name.length();
                int passwordLen = password.length();
                return nameLen >= 2 && nameLen <= 8 && passwordLen >= 4 
                && passwordLen <= 16;
            }

        });
        DisposableObserver<Boolean> disposable = new DisposableObserver<Boolean>() {

            @Override
            public void onNext(Boolean value) {
                mBtLogin.setText(value ? "登录" : "用户名或密码无效");
            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onComplete() {

            }

        };
        observable.subscribe(disposable);

private class EditTextMonitor implements TextWatcher {

        private PublishSubject<String> mPublishSubject;

        EditTextMonitor(PublishSubject<String> publishSubject) {
            mPublishSubject = publishSubject;
        }

        @Override
        public void afterTextChanged(Editable s) {
            mPublishSubject.onNext(s.toString());
        }
    }

We first created two PublishSubjects for subscription with username and password respectively, and then combined these two PublishSubjects via combineLatest. In this way, when any PublishSubject sends an event, it will call back the apply method of the last function of combineLatest. This method will get the last emitted data of each observed PublishSubject, and we will verify it through this data.

The parameter format received by zip and combineLatest is the same. The difference between zip and combineLatest is:
• zip combines the earliest uncombined data item of all Observables after one of the Observables emits data items, that is, the combined Observable emits The nth data item must be composed of the nth data item emitted by each source from the Observable.
• combineLatest is to combine the last data item emitted by all Observables after one of the Observables emits data items (provided that all Observables emit at least one data item).

6. Use publish + merge to optimize the request process of loading the cache first and then reading the network data

In order to give everyone a deeper understanding of this process, we introduce four implementations of the model of "loading the cache first, and then requesting the network". The fourth implementation can achieve the effect we mentioned above, while the previous implementation Although the three implementations can also achieve the same requirements and work normally, in some special cases, unexpected situations will occur:
• Use concat to achieve
• Use concatEager to achieve
• Use merge to achieve
• Use publish to achieve

6.1 Preparations

We need to prepare two Observables, which represent the cache data source and the network data source respectively, and fill in the corresponding cache data and network data. In order to demonstrate some special cases later, we can specify the execution time when creating it:

//模拟缓存数据源。
    private Observable<List<NewsResultEntity>> getCacheArticle(final long simulateTime) {
        return Observable.create(new ObservableOnSubscribe<List<NewsResultEntity>>() {
            @Override
            public void subscribe(ObservableEmitter<List<NewsResultEntity>> observableEmitter) throws Exception {
                try {
                    Log.d(TAG, "开始加载缓存数据");
                    Thread.sleep(simulateTime);
                    List<NewsResultEntity> results = new ArrayList<>();
                    for (int i = 0; i < 10; i++) {
                        NewsResultEntity entity = new NewsResultEntity();
                        entity.setType("缓存");
                        entity.setDesc("序号=" + i);
                        results.add(entity);
                    }
                    observableEmitter.onNext(results);
                    observableEmitter.onComplete();
                    Log.d(TAG, "结束加载缓存数据");
                } catch (InterruptedException e) {
                    if (!observableEmitter.isDisposed()) {
                        observableEmitter.onError(e);
                    }
                }
            }
        });
    }
    //模拟网络数据源。
    private Observable<List<NewsResultEntity>> getNetworkArticle(final long simulateTime) {
        return Observable.create(new ObservableOnSubscribe<List<NewsResultEntity>>() {
            @Override
            public void subscribe(ObservableEmitter<List<NewsResultEntity>> observableEmitter) throws Exception {
                try {
                    Log.d(TAG, "开始加载网络数据");
                    Thread.sleep(simulateTime);
                    List<NewsResultEntity> results = new ArrayList<>();
                    for (int i = 0; i < 10; i++) {
                        NewsResultEntity entity = new NewsResultEntity();
                        entity.setType("网络");
                        entity.setDesc("序号=" + i);
                        results.add(entity);
                    }
                    observableEmitter.onNext(results);
                    observableEmitter.onComplete();
                    Log.d(TAG, "结束加载网络数据");
                } catch (InterruptedException e) {
                    if (!observableEmitter.isDisposed()) {
                        observableEmitter.onError(e);
                    }
                }
            }
        });
    }

6.2 Implementation using concat

Concat is the method recommended by many articles, because it will not have any problems. The implementation code is as follows (note that the complete event is not sent, and the second one will not receive the event):

private void refreshArticleUseContact() {
        Observable<List<NewsResultEntity>> contactObservable = Observable.concat(
                getCacheArticle(500).subscribeOn(Schedulers.io()), 
                getNetworkArticle(2000).subscribeOn(Schedulers.io()));
        DisposableObserver<List<NewsResultEntity>> disposableObserver = 
        getArticleObserver();
        contactObservable.observeOn(AndroidSchedulers.mainThread())
        .subscribe(disposableObserver);
    }

As can be seen from the console output, the whole process is to read the cache first, and then start requesting the network after the cached data is read. Therefore, the entire process takes the addition of two stages, that is, 2500ms.
write picture description here
So, what are the disadvantages of the concat operator? Obviously, we have wasted the previous period of reading the cache, can we initiate a request to read the cache and the network at the same time, instead of waiting until the read cache is completed, then request the network?

6.3 Implementation using concatEager

private void refreshArticleUseConcatEager() {
        List<Observable<List<NewsResultEntity>>> observables = new ArrayList<>();
        observables.add(getCacheArticle(500).subscribeOn(Schedulers.io()));
        observables.add(getNetworkArticle(2000).subscribeOn(Schedulers.io()));
        Observable<List<NewsResultEntity>> contactObservable = 
        Observable.concatEager(observables);
        DisposableObserver<List<NewsResultEntity>> disposableObserver = 
        getArticleObserver();
        contactObservable.observeOn(AndroidSchedulers.mainThread())
        .subscribe(disposableObserver);
    }

The biggest difference between it and concat is that multiple Observables can start to emit data at the same time. If the previous Observable has not finished emitting data after the emission of the latter Observable, it will cache the data of the latter Observable first and wait until the previous Observable. After the transmission is completed, the cached data is transmitted.

So what are the downsides of this implementation? That is, in some abnormal cases, if the time to read the cache is greater than the time of the network request, it will cause the "result of the network request" to wait until the process of "reading the cache" is completed before it can be passed to the downstream, which is a waste. for a while.

6.4 Implementing with merge

private void refreshArticleUseMerge() {
        Observable<List<NewsResultEntity>> contactObservable 
        = Observable.merge(getCacheArticle(500).subscribeOn(Schedulers.io()), 
        getNetworkArticle(2000).subscribeOn(Schedulers.io()));
        DisposableObserver<List<NewsResultEntity>> disposableObserver
         = getArticleObserver();
        contactObservable.observeOn(AndroidSchedulers.mainThread())
        .subscribe(disposableObserver);
    }

Like concatEager, it allows multiple Observables to start emitting data at the same time, but it does not require Observables to wait for each other, but sends them directly to the downstream. Therefore, it is possible that after the network data is loaded, the read cache data will overwrite the network data.

6.5 Implementing with publish

private void refreshArticleUsePublish() {
        Observable<List<NewsResultEntity>> publishObservable 
= getNetworkArticle(2000)
.subscribeOn(Schedulers.io())
.publish(new Function<Observable<List<NewsResultEntity>>, 
ObservableSource<List<NewsResultEntity>>>() {

            @Override
            public ObservableSource<List<NewsResultEntity>> 
            apply(Observable<List<NewsResultEntity>> network) throws Exception {
                return Observable
                .merge(network,getCacheArticle(500)
                .subscribeOn(Schedulers.io())
                .takeUntil(network));
            }

        });
        DisposableObserver<List<NewsResultEntity>>
         disposableObserver = getArticleObserver();
        publishObservable.observeOn(AndroidSchedulers.mainThread())
        .subscribe(disposableObserver);
    }

There are three operators involved, publish, merge and takeUnti. Let's see if it can solve the defects of our previous three methods:
• The time to read the cache is 500ms, and the time to request the network is 2000ms

write picture description here

• The time to read the cache is 2000ms, and the time to request the network is 500ms
write picture description here

If an error occurs when the network request returns first (for example, there is no network, etc.), the onError event is sent, so that the cached Observable cannot send events, and the final interface is blank.
To solve this problem, we need to optimize the Observable of the network so that it does not pass the onError event to the downstream. One solution is to use the onErrorResume operator, which can receive a Func function whose parameter is the error sent by the network, and which will be called back when an error occurs upstream. We can return a new Observable according to the type of error, let the subscribers mirror this new Observable, and ignore the onError event, thus avoiding the onError event leading to the end of the entire subscription relationship.
Here to avoid subscribers receiving extra time when mirroring to the new Observable, we return an Observable.never() that represents an upstream that never emits events.

private Observable<List<NewsResultEntity>> getNetworkArticle(final long simulateTime) {
        return Observable.create(new ObservableOnSubscribe<List<NewsResultEntity>>() {
            @Override
            public void subscribe(
                ObservableEmitter<List<NewsResultEntity>> observableEmitter) throws Exception {
                try {
                    Log.d(TAG, "开始加载网络数据");
                    Thread.sleep(simulateTime);
                    List<NewsResultEntity> results = new ArrayList<>();
                    for (int i = 0; i < 10; i++) {
                        NewsResultEntity entity = new NewsResultEntity();
                        entity.setType("网络");
                        entity.setDesc("序号=" + i);
                        results.add(entity);
                    }
                    //a.正常情况。
                    //observableEmitter.onNext(results);
                    //observableEmitter.onComplete();
                    //b.发生异常。
                    observableEmitter.onError(new Throwable("netWork Error"));
                    Log.d(TAG, "结束加载网络数据");
                } catch (InterruptedException e) {
                    if (!observableEmitter.isDisposed()) {
                        observableEmitter.onError(e);
                    }
                }
            }
        }).onErrorResumeNext(new Function<Throwable, 
            ObservableSource<? extends List<NewsResultEntity>>>() {

            @Override
            public ObservableSource<? extends List<NewsResultEntity>> 
                        apply(Throwable throwable) throws Exception {
                Log.d(TAG, "网络请求发生错误throwable=" + throwable);
                return Observable.never();
            }
        });
    } 

6.5.1 takeUntil

We pass another otherObservable to sourceObservable through takeUntil, which means that sourceObservable is not allowed to emit data after otherObservable emits data, which just satisfies what we said earlier "As long as the network source sends data, then the cache source No more data should be transmitted".
After that, we use the merge operator introduced earlier to let the two cache sources and the network source start working at the same time to fetch data.

6.5.2 publish

However, there is a slight flaw in the above, that is, calling merge and takeUntil will cause two subscriptions. At this time, you need to use the publish operator, which receives a Function function, which returns an Observable, which is the original Observable, which is the above network source. The result of the conversion of the Observable, the Observable can be shared by the takeUntil and merge operators, so as to achieve the effect of subscribing only once.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324651596&siteId=291194637