├── .gitignore ├── LICENSE.md ├── README.md ├── cases.md ├── conspect.md ├── improvement-thoughts ├── learning-rxjava.jpg ├── multicast-after-map.png ├── multicast-before-map.png └── start-stop.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##### Learning RxJava in Russian 2 | ##### Learning RxJava на русском 3 | 4 | 7 | 8 | My [conspect](https://github.com/rcd27/learning-rxjava-ru/blob/master/conspect.md) 9 | of great [book](https://www.packtpub.com/application-development/learning-rxjava) 10 | by [Thomas Nield](https://github.com/thomasnield). 11 | It is made in my native Russian language so this can be usefull for any russian-speaking programmer since there is no 12 | official translation for it. 13 | 14 | Собственно, это [конспект](https://github.com/rcd27/learning-rxjava-ru/blob/master/conspect.md) классной [книги](https://www.packtpub.com/application-development/learning-rxjava) об RxJava на русском. 15 | Под "конспектом" подразумевается вольный перевод (без официальных расшаркиваний и прочего) только тех частей, которые мне показались нетривиальными. Бóльшую часть книги охватил, всё-таки это ж эрыксджава. 16 | 17 | #### Contributing 18 | Если где-то что-то режет глаз, криво переведено, или совсем не донесло смысл, открывайте Issue, давайте пилить переводы вместе. 19 | -------------------------------------------------------------------------------- /cases.md: -------------------------------------------------------------------------------- 1 | Here are some examples by [Stephen D'Amico](https://github.com/sddamico) at [DroidCon 2017](https://www.youtube.com/watch?v=q4eK3VFhnA0&feature=youtu.be) 2 | 3 | #### Retrying requests 4 | - `retryWhen()` 5 | ```java 6 | PublishRelay retryRequest = PublishRelay.create(); 7 | 8 | getRequestObservable() 9 | .retryWhen(attempt -> retryRequest) 10 | .subscribe(viewModel -> ..) 11 | 12 | @OnClick(R.id.retry_button) 13 | public void onRetryClicked() { 14 | retryRequest.call(System.currentTimeMillis); 15 | } 16 | ``` 17 | 18 | #### Using `Response` with `share()` 19 | ```java 20 | interface Api { 21 | @GET("events") 22 | Observable> getEvents(); 23 | } 24 | 25 | Observable> eventsResponse = 26 | api.getEvents() 27 | .observeOn(AndroidSchedulers.mainThread()) 28 | .share(); 29 | 30 | eventsResponse 31 | .filter(r -> r.code == HTTP_403) 32 | .subscribe(this::handle403Response); 33 | 34 | eventsResponse 35 | .filter(r -> r.code == HTTP_401) 36 | .subscribe(this::handle401Response); 37 | 38 | eventsResponse 39 | .filter(r -> r.code == HTTP_500) 40 | .subscribe(this::handle500Response); 41 | ``` 42 | 43 | // TODO: checkout ContentLoadingProgressBar 44 | 45 | #### Showing loading (and the state in general) 46 | ```java 47 | enum RequestState { 48 | IDLE, LOADING, COMPLETE, ERROR 49 | } 50 | 51 | // Можно также использовать Subject 52 | // Создаём мост между императивным программированием и эрыксом 53 | BehaviourRelay state = 54 | BehaviourRelay.create(RequestState.IDLE); 55 | 56 | // Здесь обрабатываем ответы от сервера 57 | Observable.just(trigger) 58 | .doOnNext(() -> state.call(RequestState.LOADING)) 59 | .observeOn(Schedulers.io()) 60 | .flatMap(trigger -> api.getEvents()) 61 | .observeOn(AndroidSchedulers.mainThread()) 62 | .doOnError(t -> state.call(RequestState.ERROR)) 63 | .doOnComplete(() -> state.call(RequestState.COMPLETE)) 64 | .subscribe(events -> .. , error -> ..); 65 | 66 | // А вот тут вьюха подписывается собственно на стэйт 67 | state.subscribe(requestState -> { 68 | switch(requestState) { 69 | IDLE: 70 | break; 71 | LOADING: 72 | loadingIndicator.show(); 73 | errorView.hide(); 74 | break; 75 | COMPLETE: 76 | loadingIndicator.hide(); 77 | break; 78 | ERROR: 79 | .. 80 | break; 81 | } 82 | }); 83 | ``` 84 | 85 | - Разносим UI и запросы в сеть ещё дальше: 86 | ```java 87 | class ApiRequest { 88 | BehaviorRelay state = 89 | BehaviorRelay.create(RequestState.IDLE); 90 | BehaviorRelay response = BehaviorRelay.create(); 91 | BehaviorRelay errors = BehaviorRelay.create(); 92 | BehaviorRelay trigger = BehaviorRelay.create(); 93 | 94 | public ApiRequest() { 95 | trigger 96 | .doOnNext(() -> state.call(RequestState.LOADING)) 97 | .observeOn(Schedulers.io()) 98 | .flatMap(trigger -> api.getEvents()) 99 | .doOnError(t -> state.call(RequestState.ERROR)) 100 | .doOnError(errors) 101 | .onErrorResumeNext(Observable.empty()) 102 | .doOnNext(() -> state.call(RequestState.COMPLETE)) 103 | .subscribe(response); 104 | } 105 | 106 | void execute() { 107 | trigger.call(System.currentTimeMillis()); 108 | } 109 | } 110 | 111 | // где-то ближе к UI слою 112 | ApiRequest request = new ApiRequest(api.getEvents()); 113 | 114 | request.state.subscribe(state -> updateLoadingView(state)); 115 | request.state.subscribe(state -> updateLoadingNotification(state)); 116 | request.response.subscribe(eventsResponse -> showEvents(eventsResponse)) 117 | request.errors.subscribe(t -> showError(t)); 118 | 119 | request.execute(); 120 | ``` 121 | Вот как можно ещё обыграть: 122 | ```java 123 | BehaviorRelay state = BehaviorRelay.create(RequestState.IDLE); 124 | BehaviorRelay> response = 125 | BehaviorRelay.create(Optional.absent()); 126 | BehaviorRelay> errors = 127 | BehaviorRelay.create(Optional.absent()); 128 | 129 | class RequestViewModel { 130 | public final RequestState state; 131 | public final Optional response; 132 | public final Optional errors; 133 | 134 | RequestViewModel(..) {..} 135 | } 136 | 137 | // Теперь у нас есть одна подписка, которая тригериться, когда один из обзёрваблов эмитит (уточнить) 138 | Observable.combineLatest(state, response, errors, RequestViewModell::new) 139 | .subscribe(viewModel -> {..}); 140 | ``` 141 | А сейчас немного Котлина: 142 | ```kotlin 143 | sealed class RequestState { 144 | object Loading : RequestState() 145 | data class Complete(val response: EventsResponse) : RequestState() 146 | data class Error(val error: Throwable) : RequestState() 147 | } 148 | 149 | Observable.fromCallable({ api.getEvents().execute() }) 150 | .subscribeOn(..) 151 | .startWith(RequestState.Loading) 152 | .onErrorReturn { RequestState.Error(it) } 153 | .observeOn(..) 154 | .subscribe { requestState -> 155 | when (requestState) { 156 | is Idle -> clearView() 157 | is Loading -> showLoading() 158 | is Completed -> .. 159 | is Error -> showError(requestState.error) 160 | } 161 | } 162 | ``` 163 | 164 | #### Disposing 165 | https://github.com/trello/RxLifecycle 166 | 167 | > Tip: `Object onRetainNonConfigurationInstance()` - можно сохранить поле и получить его после пересоздания 168 | Activity с помощью вызова `getLastNonConfigurationInstance()` 169 | 170 | -------------------------------------------------------------------------------- /conspect.md: -------------------------------------------------------------------------------- 1 | ### Что это такое 2 | 3 | Это конспект-перевод книги Томаса Нилда "Learning RxJava", 2017 4 | 5 |
    6 | 7 |
8 | 9 | ----- 10 | 11 | ### Структура 12 | 13 | Для того, чтобы упростить навигацию по материалу, была сохранена оригинальная структура книги: все оглавления и заголовки остались в родном виде. 14 | 15 | - [Chapter 2: Observables and Subscribers](#observables-and-subscribers) 16 | - [The Observable](#observable) 17 | - How Observables work 18 | - Using Observable.create() 19 | - Using Observable.just() 20 | - [The Observer interface](#Интерфейс-observer) 21 | - Implementing and subscribing to an Observer 22 | - Shorthand Observers with lambdas 23 | - [Cold versus hot Observables](#cold-vs-hot) 24 | - Cold Observables 25 | - Hot Observables 26 | - ConnectableObservable 27 | - [Other Observable sources](#Другие-фабрики-для-создания-Observable) 28 | - Observable.range() 29 | - Observable.interval() 30 | - Observable.future() 31 | - Observable.empty() 32 | - Observable.never() 33 | - Observable.error() 34 | - Observable.defer() 35 | - Observable.fromCallable() 36 | - [Single, Completable, and Maybe](#single-completable-maybe) 37 | - Single 38 | - Maybe 39 | - Completable 40 | - [Disposing](#disposing) 41 | - Handling a Disposable within an Observer 42 | - Using CompositeDisposable 43 | - Handling Disposal with Observable.create() 44 | 45 | - [Chapter 3: Basic Operators](#basic-operators) 46 | - [Suppressing operators](#suppressing-operators) 47 | - filter() 48 | - take() 49 | - skip() 50 | - takeWhile() and skipWhile() 51 | - distinct() 52 | - distinctUntilChanged() 53 | - elementAt() 54 | - [Transforming operators](#transforming-operators) 55 | - map() 56 | - cast() 57 | - startWith() 58 | - defaultIfEmpty() 59 | - switchIfEmpty() 60 | - sorted() 61 | - delay() 62 | - repeat() 63 | - scan() 64 | - [Reducing operators](#reducing-operators) 65 | - count() 66 | - reduce() 67 | - all() 68 | - any() 69 | - contains() 70 | - [Collection operators](#collection-operators) 71 | - toList() 72 | - toSortedList() 73 | - toMap() and toMultiMap() 74 | - collect() 75 | - [Error recovery operators](#error-recovery-operators) 76 | - onErrorReturn() and onErrorReturnItem() 77 | - onErrorResumeNext() 78 | - retry() 79 | - [Action operators](#action-operators) 80 | - doOnNext(), doOnComplete(), and doOnError() 81 | - doOnSubscribe() and doOnDispose() 82 | - doOnSuccess() 83 | 84 | - [Chapter 4: Combining Observables](#combining-observables) 85 | - [Merging](#merging) 86 | - Observable.merge() and mergeWith() 87 | - flatMap() 88 | - [Concatenation](#concatenation) 89 | - Observable.concat() and concatWith() 90 | - concatMap() 91 | - [Ambiguous](#ambiguous) 92 | - [Zipping](#zipping) 93 | - [Combine latest](#combine-latest) 94 | - withLatestFrom() 95 | - [Grouping](#grouping) 96 | 97 | - [Chapter 5: Multicasting, Replaying, and Caching](#multicasting-replaying-and-caching) 98 | - [Understanding multicasting](#understanding-multicasting) 99 | - Multicasting with operators 100 | - When to multicast 101 | - [Automatic connection](#automatic-connection) 102 | - autoConnect() 103 | - refCount() and share() 104 | - [Replaying and caching](#replaying-and-caching) 105 | - Replaying 106 | - Caching 107 | - [Subjects](#subjects) 108 | - PublishSubject 109 | - When to use Subjects 110 | - When Subjects go wrong 111 | - Serializing Subjects 112 | - BehaviorSubject 113 | - ReplaySubject 114 | - AsyncSubject 115 | - UnicastSubject 116 | 117 | - [Chapter 6: Concurrency and Parallelization](#concurrency-and-parallelization) 118 | - Why concurrency is necessary 119 | - Concurrency in a nutshell 120 | - Understanding parallelization 121 | - [Introducing RxJava concurrency](#introducing-rxjava-concurrency) 122 | - Keeping an application alive 123 | - [Understanding Schedulers](#understanding-schedulers) 124 | - Computation 125 | - IO 126 | - New thread 127 | - Single 128 | - Trampoline 129 | - ExecutorService 130 | - Starting and shutting down Schedulers 131 | - [Understanding subscribeOn()](#understanding-subscribeon) 132 | - Nuances of subscribeOn() 133 | - [Understanding observeOn()](#understanding-observeon) 134 | - Using observeOn() for UI event threads 135 | - Nuances of observeOn() 136 | - [Parallelization](#parallelization) 137 | - [unsubscribeOn()](#unsubscribeon) 138 | 139 | - [Chapter 7: Switching, Throttling, Windowing, and Buffering](#switching-throttling-windowing-and-buffering) 140 | - [Buffering](#buffering) 141 | - Fixed-size buffering 142 | - Time-based buffering 143 | - Boundary-based buffering 144 | - [Windowing](#windowning) 145 | - Fixed-size windowing 146 | - Time-based windowing 147 | - Boundary-based windowing 148 | - [Throttling](#throttling) 149 | - throttleLast() / sample() 150 | - throttleFirst() 151 | - throttleWithTimeout() / debounce() 152 | - [Switching](#switching) 153 | - [Grouping keystrokes](#grouping-keystrokes) 154 | 155 | - [Chapter 8: Flowables and Backpressure](#flowables-and-backpressure) 156 | - [Understanding backpressure](#understanding-backpressure) 157 | - An example that needs backpressure 158 | - Introducing the Flowable 159 | - When to use Flowables and backpressure 160 | - Use an Observable If... 161 | - Use a Flowable If... 162 | - [Understanding the Flowable and Subscriber](#understanding-the-flowable-and-subscriber) 163 | The Subscriber 164 | - [Creating a Flowable](#creating-a-flowable) 165 | Using Flowable.create() and BackpressureStrategy 166 | Turning an Observable into a Flowable (and vice-versa) 167 | - [Using onBackpressureXXX() operators](#using-onbackpressurexxx-operators) 168 | - onBackPressureBuffer() 169 | - onBackPressureLatest() 170 | - onBackPressureDrop() 171 | - [Using Flowable.generate()] 172 | 173 | - [Chapter 9: Transformers and Custom Operators](#transformers-and-custom-operators) 174 | - [Transformers](#transformers) 175 | - ObservableTransformer 176 | - FlowableTransformer 177 | - Avoiding shared state with Transformers 178 | - [Using to() for fluent conversion](#using-to-for-fluent-conversation) 179 | - [Operators](#operators) 180 | - Implementing an ObservableOperator 181 | - FlowableOperator 182 | - [Custom Transformers and operators for Singles, Maybes, and Completables](#custom-transformers-and-operators-for-singles-maybes-and-completables) 183 | - [Using RxJava2-Extras and RxJava2Extensions](#using-rxjava2-extras-and-rxjava2extensions) 184 | 185 | - [Chapter 10: Testing and Debugging](#testing-and-debugging) 186 | - [Configuring JUnit](#configuring-junit) 187 | - [Blocking subscribers](#blocking-subscribers) 188 | - [Blocking operators](#blocking-operators) 189 | - blockingFirst() 190 | - blockingGet() 191 | - blockingLast() 192 | - blockingIterable() 193 | - blockingForEach() 194 | - blockingNext() 195 | - blockingLatest() 196 | - blockingMostRecent() 197 | - [Using TestObserver and TestSubscriber](#using-testobserver-and-testsubscriber) 198 | - [Manipulating time with the TestScheduler](#manipulating-time-with-the-testscheduler) 199 | - [Debugging RxJava code](#debugging-rxjava-code) 200 | 201 | - [Chapter 11: RxJava on Adroid](#rxjava-on-android) 202 | 203 | - [Chapter 12: Using RxJava for Kotlin New](#using-rxjava-for-kotlin-new) 204 | 205 | ----- 206 | 207 | ### Observables and Subscribers 208 | Фундаментальная идея в основе реактивного программирования: события - это данные, данные - это события. 209 | 210 | #### `Observable` 211 | 212 | - `onNext()` - передаёт каждый элемент вниз до `Observer`. 213 | - `onComplete()` - свидетельствует о том, что никаких `onNext()` больше не будет. 214 | - `onError()` - отправляет ошибку вниз по цепочке вплоть до самого `Observer`, где , как правило, определена обработка ошибок. Если не использован оператор `retry()` или один из [методов](https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators) вида `onErrorResumeNext()`, то по контракту больше никаких элементов не заэмитится. 215 | 216 | `Observable.create()` - фабрика, позволяющая создать `Observable` и обозначить эмиттер. Нужна для хуков вокруг нереактивных источников данных/событий. В первой **RxJava** это был `Observable.fromEmitter()`. Стоит обратить внимание, что `Observable` одновременно может эмитить _только один элемент_. `map()` и `filter()` возвращают новый `Observable`. 217 | 218 | Во второй **RxJava** нельзя эмиттить Null. 219 | 220 | #### Интерфейс `Observer` 221 | Методы `onNext()`, `onComplete()`, `onError()` определяют тип обзёрвера. В первой эрыксджаве это был `Subscriber`. Во второй `Subscriber` появляется только когда речь идёт о `Flowables`. Если при выпуске элементов в `Observable` произойдёт ошибка, то она упадёт в `Observer.onError()`. 222 | 223 | #### Функциональные интерфейсы в rxJava (аналогично Java8) 224 | 225 | Имя | single-abstract-method | Описание 226 | --------|---------|----------- 227 | `Action` | `run()` | Запускает операцию, похоже на `Runnable` 228 | `Callable` | `get()` | Возвращает элемент типа `T` 229 | `Consumer` | `accept()` | Что-то делает над `T`, ничего не возвращает 230 | `Function` | `apply()` | Принимает `T`, возвращает `R` 231 | `Predicate` | `test()` | Принимает `Т`, возвращает `boolean` 232 | `BiConsumer` | `accept()` | см.`Consumer` 233 | `BiFunction` | `apply()` | Принимает `T1`, `T2`, возвращает `R` 234 | `BiPredicate` | `test()` | см.`Predicate` 235 | `Function3` | `apply()` | Принимает три аргумента, возвращает `R` 236 | `BooleanSupplier` | `getAsBoolean()` | Возвращает булю 237 | `LongConsumer` | `accept()` | Что-то делает с входным лонгом и всё 238 | `IntFunction` | `apply()` | Принимает `int` и возвращает `T` 239 | 240 | #### Cold vs Hot 241 | Обсерваблы, которые эмитят конечные данные чаще всего _cold_ (отрабатывают тогда, когда на них подписались). Горячие похожи на _broadcast_, вещают всем подписчикам. 242 | Работает, как радио. Если пропустил трэк, его уже не услышишь. Горячие чаще всего отображают события, нежели какие-то данные. 243 | 244 | `ConnectableObservable` - полезная форма горячего обзёрвабла. Он берёт любой Observable и делает из него _hot_, чтобы он эмитил всем подписчикам одновременно. 245 | Просто вызови `publish()` у любого обзёрвабла и он вернёт `ConnectableObservable`. 246 | Но subscribe на такой обзёрвабл не вызовет эмиты. У него надо вызвать `connect()`, чтобы тот начал пулять. То есть можно подписать несколько на `ConnectableObservable`, потом ткнуть `source.connect()`, и только тогда все подписчики получат свои данные. Такая фиговина более известна, как _Multicasting_. 247 | 248 | #### Другие фабрики для создания Observable 249 | - `Observable.range()` - для создания последовательности интов. Создаётся до тех пор, пока не будет достигнут конечный инт. Все инты передаются через `onNext()`, что неудивительно. 250 | 251 | - `Observable.interval()` - _time-based_ обзёрвабл, который эмитит с заданным 252 | интервалом. Бесконечный. _Cold_. 253 | 254 | - `Observable.future()` - обёртка над `Future`. 255 | 256 | - `Observable.empty()` - ничего не эмитит и вызывает `onComplete()`. Вместо *null*'ов. 257 | 258 | - `Observable.never()` - похож на `empty()`, только никогда не вызывает `onComplete()`. 259 | 260 | - `Observable.error()` - вызывает `onError()`. 261 | 262 | - `Observable.defer()` - какая-то мощная фиговина, так как может создавать _separate state_(отдельное состояние) для каждого `Observable`. Ну например нам нужно подтягивать изменившееся состояние в обзёрвабл. 263 | 264 | - `Observable.fromCallable()` - если нам надо произвести какое-то вычисление или операцию, а затем заэмитить его/её, можно просто вызвать `Observable.just()`. 265 | Но иногда надо это проделать лениво. Также, если эта операция вызывает ошибку, лучше бы она её выкидывала только на момент выполнения чейна. 266 | Более того, если в `Observable.just()` случится ошибка, то она не будет передана в `Observer`. Короче говоря, если то, что вы оборачиваете в `Observable` может вызывать ошибку, то оборачивать надо в `Observable.fromCallable()`. 267 | 268 | #### Single, Completable, Maybe 269 | Есть три разных типа обзёрваблов, которые эмитят ноль или один элемент. 270 | 271 | - `Single` - эмитит, вот это поворот, один элемент. `onSuccess()` включает в себя `onNext()` и `onComplete()`. В лямбде, соответственно, ловится `onSuccess()` и `onError()`. 272 | 273 | - `Maybe` - если количество элементов от 0 до 1. 274 | 275 | - `Completable` - придуман для того, чтобы выполнять какое-то действие. Ничего не получает. Есть `onError()` и `onComplete()`. 276 | 277 | #### Disposing 278 | Когда мы вызываем `subscribe()`, создаётся поток событий и обрабатывает _emissions_ в цепочке. Для этого выделяются определённые ресурсы. Слава Богу, `Observable` высвобождает эти ресурсы как только отрабатывает `onComplete()`. Но в случае, если у нас бесконечный или турбо долгий(его выполнение занимает много времени) поток, нам может понадобится конкретный `dispose`. 279 | Короче, нельзя доверять **GC** чистку толстых потоков, надо диспозить самому, чтобы избежать мемори ликов 280 | `Disposable` - это связующее звено между `Observable` и активным `Observer`. Можно вызывать его `dispose()` для того, чтобы прекратить эмитить элементы и высвободить память, затраченную на их выпуск. 281 | 282 | ##### Disposable в обзёрвере 283 | В `onSubscribe(Disposable d)` у обзёрвера передан `Disposable`. Это нужно для того, чтобы обзёрвер мог контролировать подписку и у него был вариант отписаться в любой момент. `Disposable` передаётся по всей цепочке обзёрваблов. Вообще, передача `Observer`'a в `subscribe()` не вернёт `Disposable` (rxJava сама захэндлит это вот всё), но если очень надо, то можно юзануть `subscribeWith(Disposable)`, тем самым получив дефолтный `Disposable`. 284 | 285 | Использование `CompositeDisposable`. НужнО в случае, если у нас несколько подписок, а мы хотим манагить их (отписаться от всех разом, к примеру). 286 | 287 | ----- 288 | 289 | ### Basic Operators 290 | Тут важно вкурить, что операторы сами являются `Observer`'ом для своего `Observable` выше по цепочке. 291 | 292 | #### Suppressing operators 293 | Эти операторы просто тупо не вызывают `onNext()`, если не проходит какое-то условие. Соответственно, элемент не проходит вниз по цепочке. 294 | 295 | - `filter()` - принимает `Predicate` для обзёрвабла `T`. Каждый эмишн мапится в булю, которая говорит - подходит данный эмишн к условию или нет. Эмишены `False` дальше не проходят. Если вообще нет подходящих вариантов, то вернётся пустой `Observable`, ферштейн? 296 | 297 | - `take()` - у него несколько реализаций, тривиально. 298 | 299 | - `skip()` - противоположно `take()`. 300 | 301 | - `takeWhile()`, `skipWhile()` - принимает/пропускает, пока соответствует условию. 302 | 303 | - `takeUntil()`, `skipUntil()` - принимают другой `Observable` в качестве параметра. Принимают/пропускают, пока другой обзёрвабл продолжает эмитить. 304 | 305 | - `distinct()` - будет эмитить каждый уникальный элемент, но подавлять повторяющиеся. Сравнение работает на `hashcode()`/`equals()` выпускаемых объектов. Надо иметь в виду, что если если у нас дохрена уникальных элементов, то этот оператор будет есть память. Типа как если бы каждая подписка создавала `HashSet`, отслеживающий предыдущие выпущенные элементы. Можно также кинуть в `distinct()` лямбду, по которой будет отобран уникальный ключ. 306 | 307 | - `distinctUntilChanged()` - полезная штуковина. Эмитит элементы, если входящие значения изменились. То есть она игнорит последовательно повторяющиеся элементы. 308 | `2,2,3,3,3,1,1 -> 2,3,1`. Также принимает лямбду на вход, по которой собственно сравнивает элементы. 309 | 310 | - `elementAt()` - можно получить специфичный эмишн по его id'шнику (`long` от 0 до `Long.MAX_VALUE`). Возвращает `Maybe`. У него есть разные реализации: 311 | - `elementAtOrError()` - вернёт `Single` или `Error` (в случае, если по данному индексу нифига нет). 312 | - `singleElement()` - вернёт Observable, обёрнутый в Maybe 313 | - `firstElement()` и `lastElement()`. 314 | 315 | #### Transforming operators 316 | - `map()` - для заданного `Observable` меняет `T` на `R`, используя функцию `Function`. 317 | Конвертирует один-к-одному каждый эмишн. Если нужно конвертить один-к-нескольким эмишнов, то надо использовать `flatMap()` или `concatMap()`. 318 | 319 | - `cast()` - _map-like_ оператор, который кастует каждый эмишн к заданному типу. Некий такой брут-форс для приведения типов. Лучше правильно юзать дженерики, разумеется. 320 | 321 | - `startWith()` - позволяет нам впихнуть какой-нибудь эмишн, с которого начинаются другие эмишны. 322 | Например, у нас есть `Observable`,выпускающий элементы, которые мы хотим напечатать в менюшке. Можно использовать `startWith()`, чтобы сначала написать заголовок менюшки. 323 | ``` 324 | Observable.just("Чай", "Кофе", "Лимонад") 325 | .startWith("НАПИТКИ") 326 | .subscribe(System.out::println) 327 | 328 | >output: 329 | НАПИТКИ 330 | Чай 331 | Кофе 332 | Лимонад 333 | ``` 334 | Если нам нужно больше, чем один элемент для старта, можно бахнуть 335 | 336 | - `startWithArray()`, тогда отработают сначала элементы массива. Если нужно ждать выпуск всех эмишнов другого `Obsaervable`, стоит использовать `Observable.concat()` или `concatWith()`. 337 | 338 | - `defaultIfEmpty()` - подставляет дефолт, если пусто. Вот это да. 339 | 340 | - `switchIfEmpty()` - переключается на другой `Observable`, если предыдущий не заэмитил ни одного элемента. 341 | 342 | - `sorted()` - если у нас есть конечный `Observable`, который выпускает элементы `Comparable`, можно юзать этот оператор, чтобы сортировать эмишены. Под капотом он соберёт все эмишены, а потом перевыпустит их в заданном порядке. Если применить к бесконечному `Observable`, получим `OutOfMemory`, естесна. Можно подать `Comparator` в качестве аргумента. 343 | 344 | - `delay()` - собирает все элементы и выпускает затем один за одним с заданным интервалом. Так как оперирует на другом шедулере, то надо это иметь в виду и не тупить в посках выполненной операции. Она могла не успеть выполниться до завершения _main_-потока. Для применения продвинутого `delay()` можно подать в него другой `Observable`, и этот дилей отложит свои эмишены на время, пока данный обзёрвабл чё-нить не выпустит. Есть также такая штука, как `delaySubscription()`, которая откладывает подписку, а не выпуск эмишнов. 345 | 346 | - `repeat()` - повторит подписку после `onComplete()` заданное количество раз. Есть ещё `repeatUntil()`, который принимает `BooleanSupplier` и продолжает повторения до тех пор, пока супплаер не выдаст `true`. 347 | 348 | - `scan()` - _rolling_ аггрегатор. Аккумулирует каждый эмишн и добавляет его к следующему. 349 | 350 | ```java 351 | Observable.just(5, 3, 7, 10) 352 | .scan((accumulator, next) -> accumulator + next) 353 | .subscribe(System.out::println) 354 | 355 | >output: 5 8 15 25 356 | ``` 357 | 358 | Это не обязательно лепить для интеджеров, можно аккумулировать всё что угодно. Отличается от `reduce()` тем, что тот берёт один эмишн, когда отрабатывает `onComplete()`. Может использоваться в бесконечных обзёрваблах. 359 | 360 | #### Reducing operators 361 | Будут случаи, когда необходимо взять серии эмишенов и сконсолидировать их в один элемент (зачастую какой-нибудь `Single`). Для этого есть несколько операторов. Заметьте, что эти операторы работают только с конечным `Observable`, который вызывает `onComplete`. 362 | 363 | - `count()` - простейший оператор для объединения множества эмишенов. Он подсчитает количество элементов и выпустит `Single`, когда отработает `onComplete()`. Если нужно посчитать эмишены на бесконечном обзёрвабле, юзать `scan()`. 364 | 365 | - `reduce()` - синтаксически похож на `scan()`, но только выдаёт последнее саккумулированное значение. Может выдать `Single` или `Maybe` в зависимости от вашей имплементации. Например, если нужон общий интеджер: 366 | ```java 367 | Observable.just(5, 3, 7, 10, 2, 14) 368 | .reduce((total, next) -> total + next) 369 | .subscribe(System.out::println) 370 | 371 | >output: 41 372 | ``` 373 | 374 | - `all()` - эта хреновена проверяет, что все элементы подпадают под определённый критерий и возвращает `Single`. Как только один из элементов не совпадает, **сразу** возвращает `False`. 375 | >Не тупи и запомни: если вызвать `all()` к пустому обзёрваблу, он выдаст `True`. 376 | 377 | - `any()` - соответственно, если есть хотя бы один подходящий эмишн. 378 | >Кстати, если вызвать его у пустого обзёрвабла, то он выдаст `False`. 379 | 380 | - `contains()` - тут всё просто: вырнёт булю, если в эмишеннах есть данный элемент. Работает на `hashCode()`/`equals()`. 381 | 382 | #### Collection operators 383 | Аккумулируют все эмишены и собирают в одну коллекцию, типа листа или мапы. Затем выплёвывают эту коллекцию. Являются на самом деле ещё одной формой _reducing operators_, но достойны отдельного параграфа. Кстати да, не стоит злоупотреблять этими операторами, так как это может навредить вашей реактивщине: лучше обрабатывать ивенты один-за-другим, так же, как они эмитятся, нежели скидывать всё в кучу и потом разгребать её. Смысл эрыкса тогда? 384 | 385 | - `toList()` - собирает все эмишены из `Observable` в `List` и выдаёт его в виде `Single>`. После того, как `Observable` отстреливает `onComplete()`, лист пушится в обзёрвер. По дефолту, этот лист будет `ArrayList`'ом, можно передать _capacity_ и задать таким образом ограничение на количество элементов. 386 | 387 | - `toSortedList()` - собирает в сортированный лист (работает на `Comparator`'ах). Соответственно, можно передать компаратор в качестве аргумента. 388 | 389 | - `toMap()` - для заданного `Observable` соберёт в `Map`, где `K` - ключ, полученный из лямбды `Function`. 390 | 391 | ```java 392 | Observable.just("Раз", "Два", "Три") 393 | .toMap(s -> s.charAt(0)) 394 | .subscribe(System.out::println) 395 | 396 | >output: {Р=Раз, Д=Два, Т=Три} 397 | ``` 398 | 399 | Можно и посложнее смаппить, если в лямбду передать через запятую правило для `value`. 400 | 401 | ```java 402 | ... 403 | .toMap(s-> s.charAt(0), String::length) 404 | .subscribe(..) 405 | 406 | >output: {Р=3, Д=3, Т=3} 407 | ``` 408 | 409 | По дефолту `toMap()` использует `HashMap`, можно обеспечить `ConcurrentHashMap`: `.toMap(s-> s.charAt(0), String::length, ConcurrentHashMap::new)`. 410 | 411 | - `toMultiMap()` - в случае, если хэши совпадают, образует список из значений, соответствующих данному ключу. 412 | 413 | - `collect()` - нужен, чтобы собрать элементы в специфическую коллекцию: 414 | `.collect(HashSet::new, HashSet::add)` соберёт в `HashSet` и выдаст `Single`. Используйте `collect()` вместо `reduce()`, когда вы собираете эмишены в мутабельный объект, чтобы затем получить _sealed_ объект. 415 | Хороший пример с **guava**: 416 | 417 | ```java 418 | Observable.just("Оп", "Оп", "Опана") 419 | // собрали в билдер все элементы 420 | .collect(ImmutableList::builder, ImmutableList::add) 421 | // сбилдили неизменяемый гуавовский список 422 | .map(ImmutableList.Builder::build) 423 | .subscribe(..) 424 | ``` 425 | 426 | В общем `collect()` хорош для сбора элементов в любую "нестандартную" 427 | для rxjava коллекцию. 428 | 429 | #### Error recovery operators 430 | Так, ну мы уже знаем про метод `onError()`, который пронизывает всю цепочку от `Observable` до `Observer`. После его срабатываения, поток завершает выполнение и никаких эмишенов больше не происходит. Но иногда нам надо перехватить ошибку _до_ того, как она провалится до `Observer`'a и поток завершится с ошибкой. 431 | 432 | - `onErrorReturn()` и `onErrorReturnItem()` - когда нужно вернуть дефолтный элемент в случае ошибки. Принимает лямбду с `Throwable`'ом. Важно, где стоит в цепочке. Чтобы ловить ошибку, должен стоять после обзёрвабла, который выдал эту ошибку (логично, потому как каждый следующий член цепочки является `Observer`'ом предыдущего члена и `Observabl`'ом для следущего). 433 | 434 | - `onErrorResumeNext()` - аналогичен предыдущим, только принимает ещё другой `Observable` в качестве параметра (который эмитится на случай ошибки). Можно по красоте бахнуть например `.onErrorResumeNext(Observable.empty())` и вызвать тем самым `onComplete()` в цепочке, где произошла ошибка. Искать только её потом устанем (прим.автора). 435 | 436 | - `retry()` - переподписывается к `Observabl`'у в надежде, что ошибка рассосётся сама по себе. Есть несколько перегруженных методов. Можно, например, задать интом количество попыток. Можно пульнуть в него `Predicate` или `BiPredicate`, чтобы более точно описать случай, когда должен отработать `retry()`. `retryUntil()` будет пересабскрайбиться до тех пор, пока соотв. `BooleanSupplier` в лямбде выдаёт `false`. Есть также крутой `retryWhen()`. С его помощью можно применить композицию из задач для ретрая (к примеру, выставить `delay` для `retry`). 437 | 438 | #### Action operators 439 | Помогают при дебаге, улучшают понимание того, что собственно проиходит в чейне. Вставлять между целевым обзёрвером и его обзёрваблом (прим.автора). 440 | 441 | - `doOnNext()` - позволяет брать эмишены предыдущего `Observable`'а. Что-то типа мини-обзёрвабла в середине цепочки. Он никак не меняет эмишены. С помощью `doOnNext()` мы просто создаём _side-effect_ для каждого ивента в цепочке. В этом примере мы просто выведем все стринги в консоль до того, как они упадут в оператор `map()`: 442 | 443 | ```java 444 | Observable.just("One", "Four", "Twelve") 445 | .doOnNext(s -> System.out::println) 446 | .map(String::length) 447 | .subscribe(System.out::println) 448 | 449 | >output: One 3 Four 4 Twelve 6 450 | ``` 451 | 452 | Есть также `doAfterNext()`, который предпринимет заданное действие _ПОСЛЕ_ 453 | того, как эмишн ушёл дальше по цепочке. 454 | 455 | - `doOnComplete()` - срабатывает, когда чейн вызывает свой `onComplete()`. Может быть полезен в случае, когда нужно понять, какая часть цепочки отработала нормально и завершилась `onComplete()`. 456 | 457 | - `doOnError()` - соответственно, срабатывает, когда по цепочке пробрасывается ошибка. Опять же, полезно вставлять между операторами. Тут лучше с примером: 458 | 459 | ```java 460 | Observable.just(5, 2, 4, 0, 3, 2, 8) 461 | .doOnError(e -> System.out.println("Source failed") 462 | .map(i -> 10/i) 463 | .doOnError(e -> System.out.prinln("Division failed!") 464 | .subscribe(i -> System.out::println, 465 | e -> System.out.println("Observer gets error"); 466 | 467 | > output: 2 5 2 Division failed! Observer gets error 468 | ``` 469 | 470 | То есть "Source failed" не отработал, так как не было выпущено никаких ошибок из `.just(...)`. 471 | Есть также `doOnEach()`, где мы можем определить действие для всех трёх предыдущих операторов. Это типа как влепить `subscribe()` посередине чейна. 472 | 473 | - `doOnSubscribe()` - выпускает `Consumer` в момент, когда в чейне срабатывает подписка. Предоставляет доступ к `Disposable` на случай, если нам нужно вызвать `dispose()`. 474 | 475 | - `doOnDispose()` - позволяет предпринять какую-то операцию в момент, когда в чейне отработал `dispose()`. 476 | >Если нам нужно сделать что-то в любом случае, будь то `onComplete()`, `onError()` или `dispose`, нам понадобится `doFinally()`, который отрабатывает во всех трёх вариантах. 477 | 478 | - `doOnSuccess()` - как мы помним, `Maybe` и `Single` вызывают `onSuccess()`, у них нет никаких `onNext()`. Для них есть `doOnSuccess()`, который работает также, как `doOnNext()`. 479 | 480 | Втираем себе в виски понимание того, что **action** операторы ставятся между **observer** и **observable** и отрабатывают _ДО_ **observer**'овских методов. 481 | 482 | ------ 483 | 484 | ### Combining Observables 485 | 486 | #### Merging 487 | Распространённое действие по слиянию двух и более обзёрваблов `` в один `Observable`. 488 | 489 | - `Observable.merge()` - если у нас от двух до четырёх однотипных обзёрваблов, можно использовать этот оператор. Аналогично `source1.mergeWith(source2).subscribe(..)`. `Observable.merge()` и оператор `mergeWith()` подпишутся на все источники одновременно, но элементы будут эмититься в одном потоке. Если нам нужен поочерёдный(заданный) эмит элементов, то надо использовать `Observable.concat()`. Не стоит полагаться на порядок эмитов в случае с `merge()`, когда вам нужна какая-то конкретная последовательность элементов. Если такая необходимость всё-таки есть, юзать `concat()`. В случае, когда смёрджить надо больше четырёх обзёрваблов, использовать `Observable.mergeArray()`, который принимает _varargs_ `Observable[]`. Оказывается, можно подать также `Iterable>` в метод `merge()`, и он нормально отработает. Работает с бесконечными обзёрваблами. 490 | 491 | - `flatMap()`- вот на этом операторе надо остановиться подробнее. Производит динамический `Observable.merge()`: берёт каждый эмишн и мапит его в `Observable`. Затем он мёрджит эмишены получившихся `Observable`'ов в один поток. Простейшее практическое применение для `flatMap()` - смаппить один эмишн в несколько. 492 | К примеру, мы хотим заэмитить `char` из каждого `String` у `Observable`. Для такой задачи можно использовать` flatMap()`, которому передадим `Function>` в ламбду. Она смапит каждый стринг в `Observable`, который будет эмитить буквы. Надо обратить внимание, что получившийся `Observable` может эмитить любой элемент `R` только если он отличается от входного типа `T`. 493 | 494 | ```java 495 | Observable source = Observable.just("по", "буквам"); 496 | source.flatMap(s -> Observable.fromArray(s.split(""))) 497 | .subscribe(char -> System.out.print(char + " "); 498 | 499 | >output: п о б у к в а м 500 | ``` 501 | 502 | Мы разбили каждый стринг на `char`, обернули это в `Observable` и передали в `flatMap()`, который заэмитил все `char`'ы в один поток. 503 | `flatMap()` также принимает лямбду в виде `BiFunction`. Изначально заэмиченный тип `T` ассоциируется с `flat-map`'ленным значением `U` и оба эмитятся в значение `R`. 504 | 505 | ```java 506 | Observable source = Observable.just("Один", "Два") 507 | source.flatMap(s -> Observable.fromArray(s.split("")), 508 | (s,r) -> s + "-" + r) 509 | .subscribe(System.out::println); 510 | 511 | >output: 512 | Один-О 513 | Один-д 514 | Один-и 515 | Один-н 516 | Два-Д 517 | Два-в 518 | Два-а 519 | ``` 520 | 521 | Можно также использовать `flatMapIterable()`, который смапит все `T` эмишены в `Iterable` вместо `Observable`. Потом выпустит все значения `R` для каждого `Iterable`, позволяя пропустить оверхед с запаковыванием его в `Observable`. Есть варианты, которые мапят в `Single`, `Maybe` и `Completable`. 522 | 523 | #### Concatenation 524 | Похоже на слияние(_merging_), но с одним ньюансом: эмитит элементы каждого предоставленного обзёрвабла в определённом порядке. Выпуск не перейдёт к следующему обзёрваблу, пока текущий не стрельнёт `onComplete()`. Такой подход гарантирует, что объединяемые источники выпустят свои элементы в гарантированном порядке. Однако, это хреново, когда речь идёт о бесконечном обзёрвабле. Юзать объединение (_concatenation_), когда нужен чёткий порядок. 525 | 526 | - `Observable.concat()` - аналог `Observable.merge(`). Объеденит эмишены разных `Observable` и будет пулять их поочерёдно, переключаясь к следующему источнику только после того, как текущий вызовет `onComplete()`. В простом примере с двумя обзёрваблами, отрабатывает в принципе так же, как `Observable.merge()`, но мы уже вкурили, что в данном случае порядок гарантирован. 527 | Автор пишет, что надо не тупить и не concat'ить бесконечные обзёрваблы, мол, **RxJava** будет ждать `onComplete()`, которого по понятной причине не произойдёт. На крайняк, ставить такой _infinite_ `Observable` вторым в цепочке, чтобы он запускался после первого. (можно вывернуться, кинув `take()`, и спровоцировать этим вызов `onComplete()`). 528 | 529 | - `Observable.concatArray()` стригеррит выпуск каждого `Observable` в `Observable[]`. Обычный `Observable.concat()` также спокойно справляется с `Iterable>` и эмитит их по-очереди. 530 | 531 | - `concatMapEager()` подпишется на все полученные `Observable`, скэширует эмишены до тех пор, пока не придёт их черёд в очереди. 532 | 533 | - `concatMap()` - по аналогии с уже существующим `flatMap()`'ом, который мёрджит обзёрваблы, полученные из каждого эмишена, есть оператор для объединения таких вещей - `concatMap()`. Он предпочтительнее в случае, когда нам важен порядок объединения элементов. Каждый обзёрвабл мапится из эмишна до тех пор, пока тот не завершится. Только потом стартует следующий эмишн. Если источник выпускает больше обзёрваблов, чем `concatMap()` может обработать за промежуток времени, они ставятся в очередь. 534 | 535 | Предыдущие примеры `flatMap()`'а больше подходят для `concatMap()`'а, потому что в них важна очерёдность. И хотя результатом использования обоих операторов будет одно и то же, всё таки стоит правильно использовать каждый из них, чтобы потом не удивляться, почему порядок вдруг поплыл (прим.ред). Опять же, использовать `concatMap()` на бесконечных источниках тупо, потому что он работает через `onComplete()`. В таком случае надо брать `flatMap()`. 536 | 537 | #### Ambiguous 538 | После слияния(_merge_) и объединения(_concatenation_) надо бы пару слов сказать об _ambiguous_(хрен знает, яндекс.переводчик топит за "неоднозначный, двоякий"). 539 | 540 | - `Observable.amb()` принимает `Iterable>` и выпускает эмишены первого заэмитившего `Observable`. Остальные в свою очередь высвобождаются. 541 | Первым считается такой обзёрвабл, чьи эмишены проходят через данное звено в чейне(толкование автора конспекта). Это полезно, когда у вас несколько источников одних и тех же данных, поэтому вам пофиг откуда их брать, лишь бы побыстрее. 542 | 543 | #### Zipping 544 | Зиппинг позволяет взять несколько разных эмишенов и объеденить их в один. Типы входных эмишенов могут быть разные, но мы можем объеденить и их тоже. 545 | 546 | - Оператор `zip()` работает по принципу молнии: эмишн одного обзёрвабла ждёт эмишн другого, чтобы зазиповаться с ним. Если один обзёрвабл выпалил `onComplete()`, а у другого есть элементы к выпуску, они просто напросто дропнутся. 547 | Можно с таким же успехом использовать `zipWith()`. 548 | 549 | - В фабрику `Observable.zip()` вариант подать до девяти `Obsevable`'ов. Если надо больше, то использовать `zip.Array()`. 550 | 551 | >Тут надо иметь в виду, что, если один из источников эмитит медленнее, чем остальные, другие источники данных будут тупить и ждать отстающего. Если пофиг на конкретный эмишн и надо взять последний, то можно использовать `combineLatest()`(о нём чуть позже). 552 | 553 | #### Combine latest 554 | - `Observable.combineLatest()` - что-то похожее на `zip()`, только отрабатывает с самым последним элементом, выпущенным из любого другого источника. Он не блочит и не ставит в очередь эмишены, которым не нашлось пары, но кэширует их и зипует с последним. Проще говоря, когда один источник эмитит, результат зипуется с последним заэмиченным элементом другого источника. 555 | Крайне актуально, когда речь идёт о комбинировании **UI** _input_'ов, так как там не важны предыдущие инпуты пользователя, а нужен только последний. 556 | 557 | - `withLatestFrom(`) - немного отличается от `combineLatest()`. Он мапит каждый `T` элемент одного эмишена с последним элементом другого, но берёт из каждых эмишенов только _ОДИН_ элемент. В итоге получится, что повторяющихся значений не будет. 558 | 559 | #### Grouping 560 | Возможность группировать разные эмишены в отдельные `Observable` - мощнейший инструмент **rxJava**. 561 | 562 | - Для таких манёвров у неё есть оператор `groupBy()`, который принимает лямбду и маппит эмишн к соотв.ключу. Затем он возвращает `Observable>`, который эмитит специальный тип `GroupedObservable`. Такой обзёрвабл почти ничем не отличается от обычного, только у него есть значение ключа в качестве свойства. 563 | 564 | К примеру, у нас есть набор стрингов, которые мы хотим объединить по длине: 565 | ```java 566 | Observable source = Observable.just("Кот", "Пёс", "Змея","Либерал"); 567 | Observable> byLengths = 568 | source.groupBy(s -> s.lenght()); 569 | ``` 570 | Скорее всего мы захотим использовать `flatMap()` к каждому элементу `GroupedObservable`, а внутри этого флэтмапа попробуем собрать результаты в кучу. В таком случае на выходе получится `Single`, поэтому и использовать надо `flatMapSingle()`. Давайте вызовем `toList()`, чтобы выпустить эмишены в виде листов с элементами, сгруппированными по длине: 571 | 572 | ```java 573 | byLengths.flatMapSingle(grp -> grp.toList()) 574 | .subscribe(System.out::prinln); 575 | 576 | >output: 577 | [Кот, Пёс] 578 | [Змея] 579 | [Либерал] 580 | ``` 581 | 582 | На выходе у нас получились списки с элементами одинаковой длины. (вообще, либерал, конечено, должен был запоковаться вместе с змеёй, но **RxJava** далека от политики и срабатывает чётко, как автомат Калашникова). 583 | У `GroupedObservable` есть метод `getKey()`, возвращающий ключ, с которым ассоциируется этот обзёрвабл. 584 | 585 | ----- 586 | 587 | ### Multicasting, Replaying, and Caching 588 | В этой главе речь будет идти преимущественно о _multicasting_. _Replaying_ и _cashing_ - это на самом деле тоже про мультикаст. Будет и разбор `Subjects`, утилиты, предназначенной для _decoupling_ при мультикасте. Она должна быть использована с умом и в конкретных случаях. Также рассмотрим несколько вариаций _subjects_. 589 | 590 | #### Раскуриваем мультикастинг(_multicasting_) 591 | Ранее мы уже имели дело с `ConnectableObservable`, который эмитит элементы всем подписанным `Observer`'aм при вызове метода `connect()`. Такая идея объединения стримов называется мультикастом. 592 | Но есть некоторые ньюансы, когда речь заходит о применении операторов, которые могут опять создать отдельные стримы. 593 | 594 | ##### Мультикастинг с операторами 595 | Тут соль в том, _КОГДА_ консолидируются стримы. 596 | Например, если вызвать мультикастинг(с помощью `publish()`) до оператора `map()`, то стримы будут объеденены в один прокси `Observable` _до_ того, как будет применена мапа. 597 | 598 | ![](multicast-before-map.png) 599 | 600 | Если мы хотим, чтобы оператор `map()` не генерил нам разные стримы для каждого `Observer`'а, то `publish()` надо вызывать _ПОСЛЕ_ `map()`. 601 | 602 | ![](multicast-after-map.png) 603 | 604 | ##### Когда собственно нужен мультикаст 605 | В целом эта фича полезна для того, чтобы избежать ненужного повторения одной и той же работы, а также в тех случаях, когда разные `Observer`'ы используют одни и те же данные. Плюс ко всему это позволяет разгрузить CPU. 606 | Надо помнить, что мультикастинг создаёт горячий `ConnectableObservable`. Тут важно правильно вызвать `connect()`, чтобы ни один из подписчиков не проморгал данные. 607 | Рекомендуется держать ваши обзёрваблы холодными и вызывать `publish()` только когда нужно, чтобы они стали погорячее. 608 | Даже в случае с горячими обзерваблами (типа UI в андроиде), иногда не стоит лишний раз обвешивать их ненужными операторами. Это увеличивает стоимость операции, да и нафига, если у нас, к примеру, один только подписчик. Короче говоря: думать, прежде чем обмазывать всё `publish()`'ем. Вот если у нас несколько `Observer`'ов, то тут можно прикинуть, а не объединить ли их в один стрим. 609 | 610 | #### Automatic connection 611 | Вам стопудово когда-нибудь захочется вручную вызывать `connect()`, чтобы типа контролировать эмишены и всё такое. Но есть также варианты из-под капота (с которыми надо быть аккуратнее). 612 | 613 | - `autoConnect()` - у `ConnectableObservable` есть такой вот оператор. 614 | Возвращает `Observable`, который автоматически пульнёт `connect()` после определённого числа подписчиков. Нужен пример? Наврядли, тут всё ясно. 615 | Ради прикола напишу, что автор уточняет: этот оператор не очень полезен, когда мы не знаем точно число `Observer'ов`. Спасибо, чувак, мы это ценим. 616 | А вот это интересно: даже когда целевые обзёрверы отработали или высвободились, `autoConnect()` оставит свою подписку на источник. Если источник конечен и завершает своё выполнение, `autoConnect()` не подпишется на него снова, когда внизу чейна появится новый `Observer`. То есть вновь прибывшие обзёрверы проморгают эмишены. По дефолту, аргумент - 1. 617 | 618 | - `refCount() и share()` 619 | - `refCount()` похож на `autoconnect(1)`, который выстреливает сразу после того, как на него подписался первый обзёрвер. Но есть офигенно важное отличие: после того, как этот чейн отработает, он высвободится. И перезапустится, когда на него опять кто-нибудь подпишется. 620 | Он не сохраняет подписку на источник, когда у него больше нет обзёрверов. 621 | `refCount()` может быть полезен в случаях, когда надо объединить стримы для каких-то подписчиков, но высвободить эмишн, если вдруг их не осталось. Причём так, чтобы выпуск элементов стартанул вновь после того, как появятся новые подписчики. 622 | - `publish().refCount()` может быть заменён оператором `share()`. 623 | 624 | #### Replaying and caching 625 | Мультикастинг также позволяет кэшировать значения, которые делятся между несколькими `Observer`'ами. Повторение и кэширование данных - это фича мультикастинга. 626 | 627 | ##### Replaying 628 | Оператор `replay()` - мощная штука, когда речь идёт о необходимости сохранить ранее заэмиченные элементы и перевыпустить их, когда коннектится новый `Observer`. 629 | Он вернёт `ConnectableObservable`, который замультикастит эмишены, а также заэмитит ранее выпущенные данные в заданном скоупе (scope). 630 | 631 | Закэшированные эмишены выстреливают сразу же после того, как появился новый подписчик, затем будут выпущены текущие элементы. То есть все "опоздавшие" подписчики ничего не пропустят и получат все элементы с самого начала. 632 | 633 | Данная операция может быть несколько дороговата, так как `replay()` кэширует все эмишены. 634 | Если задать `bufferSize`, то кэшироваться будет ограниченное количество элементов. `bufferSize(2)` - и вновь подписавшийся получит последние два элемента. 635 | >Обратите внимание: в случае, если мы хотим сохранять кэш для значений в `replay()` даже если у нас нет подписчиков, надо использовать `replay()` в сочетании с `autoConnect()`, а не `refCount()`. Дело в том, что `refCount()` высвобождает и кэш и чейн в целом, поэтому новый подписчик инициирует срабатывание чейна с самого начала. 636 | 637 | `replay()` также принимает временнОе значение в качестве аргумента. Повторять за последние T времени (в буфере хранится то, что было выпущено за последние две секунды, например). 638 | 639 | ##### Caching 640 | Когда вы хотите кэшировать вообще гаддэмн всё, что было выпущено за продолжительный отрезок времени, и вам при этом не надо контролировать поведение подписчиков относительно источника `ConnectableObservable`, можно заюзать оператор `cache()`. 641 | 642 | Он подпишится к источнику вместе с первым обзёрвером и сохранит все значения. Это делает его не самым лучшим кандидатом к применению, когда речь идёт о бесконечных источниках. 643 | 644 | Как вариант - `cacheWithInitialCapacity()` с количеством элементов, которые надо кэшировать. Важно: не используйте `cache()`, если вам реально не надо сохранять все элементы. Вместо этого лучше использовать `replay()`, так как он позволяет более тонко выбрать стратегию высвобождения чейна и правильно установить ограничения на сохранение элементов. 645 | 646 | #### Subjects 647 | Прежде всего следует отметить, что у `Subject`'ов есть свои определённые _use-case_'ы, но новички часто усложняют и используют этот инструмент неправильно. 648 | Они представляют из себя одновременно и `Observer` и `Observable`, выступая в роли _proxy multicasting_ устройства (что-то типа _event bus_). Прежде, чем использовать `Subject`'ы, следует убедиться, что вы предприняли все остальные возможные меры. **Erik Meijer**, создатель **RxJava**, называет их "изменяемыми переменными в мире реактивного программирования". Как бы мы не стремились к _immutable_ переменным, без изменяемых переменных всё-равно не обойтись. Аналогичным образом получается и с `Subjects`, которые порой необходимы, чтобы объединить императивное программирование с реактивным. 649 | Прежде, чем объяснять, когда стоит использовать `Subjects`, а когда этого делать не следует, пройдёмся по тому, что они собственно делают. 650 | 651 | ##### PublishSubject 652 | Есть две абстрактные имплелемнтации `Subject`, которые реализуют одновременно и `Observable` и `Observer`. То есть мы можем вызвать `onNext()`, `onComplete()`, `onError()` у `Subject`'a, и он передаст эти ивенты вниз к `Observer`'ам. 653 | Самый простой пример `Subject` - `PublishSubject`, который бродкастит (_hotly_ выпускает элементы) своим обзёрверам. Другие `Subject` добавляют различные поведения к этой логике, в то время как `PublishSubject` можно считать своего рода ванилью. 654 | 655 | ```java 656 | Subject subject = PublishSubject.create(); 657 | 658 | subject.map(String::length) 659 | .subscribe(System.out::println); 660 | 661 | subject.onNext("Раз"); 662 | subject.onNext("Четыре"); 663 | subject.onNext("Двадцать"); 664 | 665 | >output: 666 | 3 667 | 6 668 | 8 669 | ``` 670 | 671 | Выглядит так, будто `Subject` - магический девайс, мост между императивщиной и реактивщиной. Так оно и есть. Теперь давайте посмотрим, когда его использовать, а когда лучше не стоит. 672 | 673 | ##### When to use Subjects 674 | Допустим, мы используем **DI** или ещё какой инструмент для инвертирования зависимостей. При таком подходе есть вероятность, что разные модули предоставляют разные `Observable`'ы, которые могут понадобиться нам в рамках одного стрима. Без `Subject`'а это может быть довольно сложным для реализации, так как нам придётся особым образом варить эти обзёрваблы. В обычной ситуации мы бы просто взяли `Observable.merge()` и были бы правы, но это не всегда прокатывает. В целом, `Subjects` помогают избежать жёсткого связывания. 675 | `Subject`'ы хорошо подходят для бесконечных _event-driven_(а это то же самое, что и _action-driven_) `Observables`. 676 | 677 | ##### When Subjects go wrong 678 | В связи с тем, что `Subject`'ы - горячие обзёрваблы, важно вовремя вызывать `subscribe()`. Делать это следует до того, как мы передаём что-то в `onNext()`. Иначе `Observer` прохлопает эмишены. 679 | Такое положение дел свидетельствует о том, что `Subject`'ы могут быть опасной приблудой, особенно, если вызов `onNext()` происходит где-нибудь хрен знает где. Реактивный подход подразумевает, что исходные `Observable` предоставляются хорошо определённым и предсказуемым источником. Более того, `Subject`'ы не имеют _public_ метода `dispose()` и поэтому не высвободят переданные в них ресурсы, когда внизу по чейну отработает `dispose()`. 680 | Намного лучше держать такие _data-driven_ источники холодными, а затем использовать мультикаст с помощью `publish()` или `replay()`, если вдруг понадобится, чтобы они стали горячими. 681 | 682 | >Если вам нужен `Subject`, кастаните его к `Observable` или лучше вообще не используйте его. Можно также сделать обёртку над `Subject`'ом, где методы будут передавать в него соответствующие ивенты. 683 | 684 | ##### Serializing Subjects 685 | Важно понимать, что все операции над `Subject`'ом, типа `onSubscribe()`, `onError()` и `onComplete()` не являются потокобезопасными! Если несколько потоков вызывают эти методы как им вздумается, выпускаемые элементы могут сломать контракт `Observable`, который требует, чтобы ивенты происходили поочерёдно. Если такое происходит, то хорошей практикой считается вызвать `toSerialized()` к `Subject`'у для того, чтобы запаковать его в безопасный засериализованный вариант сабжекта. Это обезопасит его выполнение в разных потоках и ничего не поломается: 686 | 687 | ```java 688 | Subject subject = PublishSubject.create().toSerialized(); 689 | ``` 690 | 691 | > У компилятора есть ограничения на такие манёвры и нужно указывать тип для метода `create()`. Такая штука не прокатит, если в цепочке после `create()` стоит более одного оператора. Произойдёт ошибка компиляции. 692 | 693 | ##### BehaviorSubject 694 | Ведёт себя примерно так же, как `PublishSubject`, только повторяет последний заэмиченный элемент для каждого вновь подписавшегося обзёрвера. Использование этого `Subject` равносильно использоанию `replay(1).autoConnect()` после `PublishSubject`'а. 695 | 696 | ##### ReplaySubject 697 | Эквивалент `PublishSubject`'а, дополненного оператором `cache()`. Он незамедлительно отлавливает эмишены вне зависимости, подписан на него кто-нибудь или нет, и кэширует их. Каждый вновь подписанный обзёрвер получит ранее выпущенные и закэшированные элементы. Очевидно, использовать надо с умом, когда речь идёт о каких-то больших объёмах данных. 698 | 699 | ##### AsyncSubject 700 | Пушит только последнее значение, которое получает перед тем, как отработает `onComplete()`. 701 | 702 | > В принципе повторяет поведение `CompletableFuture` из восьмой джавы: произведёт необходимое нам вычисление над выбранным элментом, как только он отработает. Можно эмитировать с помощью `takeLast(1).replay(1)`. Попробуйте сначала выполнить такой приём прежде, чем использовать `AsyncSubject`. 703 | 704 | ##### UnicastSubject 705 | Может оказаться полезным сабжектом, если нам надо поместить в буфер все эмишены в стриме до тех пор, пока на него кто-нибудь не подпишется, а затем выпалить все эмишены этому подписчику и освободить буфер. 706 | Будет работать только с одним `Observer`, когда тот подпишется. Выкинет ошибку, если вдруг появятся ещё какие-то подписчики. Это логично, так как буфер очищается сразу после того, как первый подписчик получает все выпущенные элементы. Следующим обзёрверам просто напросто нечего получать. Если вы хотите, чтобы остальные тоже получили свои эмишены, надо использовать `ReplaySubject`. Плюс данного сабжекта в том, что он очищает память, которая была использована для буферизации. 707 | 708 | ----- 709 | 710 | ### Concurrency and Parallelization 711 | 712 | > Для глубокого понимания Java concurrency автор советует книгу [Brian Goetz: Java Concurrency in Practise](https://www.amazon.com/Java-Concurrency-Practice-Brian-Goetz/dp/0321349601). 713 | 714 | Создание потоков - дорогая операция. Лучше _переиспользовать_ их и заставлять выполнять очередь из задач. 715 | 716 | #### Introducing RxJava Concurrency 717 | По дефолту, `Observable` выполняет работу в том же потоке, в котором был создан и подписан `Observer`. Почти во всех предыдущих примерах это был _main thread_, который запускался функцией `public static void main()`. 718 | Но были некоторые оговорки, к примеру, в случае с `Observable.interval()`. Этот обзёрвабл запустит поток вне _main thread_'а, который в свою очередь не будет ждать его выполнения. Такой обзёрвабл делает из нашей приложухи многопоточное приложение, так как речь уже идёт как минимум о двух _thread_'ах. 719 | 720 | Зачастую, _concurrency_ полезно, когда нужно выполнять какие-то жирные долгоиграющие операции. 721 | 722 | - `subscribeOn()` - позволяет задать конкретный `Scheduler` для обозначенного источника. Задача `Scheduler`'а - предоставить _thread_ для выполнения той или иной операции. Он хранит пул _thread_'ов. После вызова `onComplete()`, _thread_ будет отдан обратно его шедулеру, таким образом его можно будет переиспользовать где-нибудь в другом месте. 723 | 724 | `Observable`'ам безразлично на каком потоке они выполняются. **RxJava** неплохо справляется с многопоточностью даже в случаях, когда нам нужно скомбинировать результаты выполнения разных обзёрваблов из разных потоков. 725 | 726 | > Если нужно иметь дело с высоконагруженными `Observable`'ами, которые эмитят 10К и более элементов, то стоит работать с `Flowable`. 727 | 728 | #### Keeping an application alive 729 | Когда мы работаем с фрэймворками, которые поддерживают выполнение главного потока (Android, JavaFX, ещё там какие-нить), то мы не паримся насчёт того, живо наше приложение или нет. Эти фреймворки работают за нас. Ситауция обстоит несколько иначе, когда нам нужно работать из `main()`(или с тестами, прим.автора). Тут придётся заставлять главный поток спать, либо идти на ещё какие-нибудь ухищрения. Можно в принципе бесконечно усыпить его, подав `Long.MAX_VALUE` в `Thread.sleep()` (_main_ поток уснёт на 292 года). 730 | 731 | Есть способы поддержать жизнеспособность приложения ровно на столько, чтобы выполнились все операции в _subscription_. Brian Goetz писал про такие стандартные тулзы, как `CountDownLatch`, которые могут подождать выполнения двух подписок. Но намного проще заюзать блокирующие операторы(_blocking operators_) **RxJava**. 732 | 733 | Обчычно, _blocking operators_ используются в Unit-тестах. Могу породить анти-паттерны при бездумном использовании в продакшн-коде. 734 | 735 | #### Understanding Schedulers 736 | _Thread-pools_ являются коллекциями потоков. Эти потоки сохраняются и переиспользуются в соответствии с политикой пула. 737 | 738 | Некоторые пулы имеют фиксированное количество потоков (такие, как `computation()`), в то время, как другие динамически создают и уничтожают потоки по мере надобности. В стандартной джаве вы используете `ExecutorService` в качесестве _thread pool_'а. **RxJava** предоставляет свою абстракцию для таких нужд - `Scheduler`. Он определяет методы и правила, которые такой провайдер потоков, как `ExecutorService`, должен выполнять. 739 | 740 | Большинство стандартных шедулеров могут быть получены из статического класса-фабрики `Schedulers`. Когда отработает `onComplete()`, операция высвободит ресурсы и поток вернётся обратно в свой пул, где он может быть сохранён и переиспользован другим `Observer`'ом. 741 | 742 | ##### Computation 743 | Содержит фиксированное количество _thread_'ов, основанное на доступных процессорах для текущей сессии Java. Это делает данный шедулер более подходящим для выполнения вычислительных задач(таких, как математические, алгоритмические, сложные логические), которые могут занимать ядро чуть более чем полностью. К тому же, нет смысла выделять больше потоков, чем количество ядер, чтобы проделать такого рода работу. 744 | 745 | > Если не уверен, какой шедулер выбрать, пикай computation(), не прогадаешь. 746 | 747 | Такие операции, как `interval()`, `delay()`, `timer()`, `timeout()`, `buffer()`, `take()`, `skip()`, `takeWhile()`, `skipWhile()` и некоторые другие использует этот шедулер по умолчанию. 748 | 749 | ##### IO 750 | Задачи по вводу-выводу, такие, как запись и чтение в БД, запросы в сеть - менее затратны для **CPU** и чаще всего занимают сравнительно немного времени, чтобы выполниться. Это значит, что можно более свободно создавать _thread_'ы и `Schedulers.io()` отлично для этого подходит. Он будет обеспечивать работу стольких потоков, сколько нужно. Этот шедулер поддерживает динамическое увеличение/уменьшение пула потоков и их кэширование. 751 | 752 | > Каждый новый _subscription_ будет вызывать создание нового _thread_'а. 753 | 754 | ##### New thread 755 | `Schedulers.newThread()` вернёт `Scheduler`, у которого нет пула потоков. Он будет создавать новый поток для каждого нового `Observer` и уничтожать этот поток, как только тот отработает. Эта логика отличается от `Schedulers.io()` тем, что данный шедулер не будет пытаться закэшировать и сохранить потоки для переиспользования. 756 | 757 | Это может быть полезно, когды мы хотим создать поток и сразу уничтожить его после решения поставленной задачи. Использовать следует аккуратно, чтобы не крашнуть приложение. 758 | 759 | ##### Single 760 | Когда вы хотите обеспечить поочерёдное выполнение задачи на одном потоке, вам нужен этот шедулер. У него под капотом реализована однопоточная логика, что часто полезно для цикличных ивентов. Он может пригодиться, чтобы изолировать потоко-небезопасную работу. 761 | 762 | ```java 763 | Observable.just("We", "love", "Putin") 764 | .subscribeOn(Schedulers.single()); 765 | ``` 766 | 767 | ##### Trampoline 768 | Это интересный шедулер. Он про незамедлительное выполнение, но предотвращает случаи рекурсивной работы, когда задача шедулит другую задачу на том же самом потоке. Вместо того, чтобы выдать _stack overflow error_, этот шедулер позволит сначала завершиться текущей задаче, и только потом начнёт выполнение следующей. 769 | 770 | ##### ExecutorService 771 | Можно запилить свой собственный шедулер поверх стандартного `ExecutorService`. Такой финт ушами понадобится в случае, если надо более тонко контролировать потоки, их выполнение и вот это вот всё. К примеру, нам нужен шедулер, который будет использовать 20 _thread_'ов. Можно создать `ExecutorService` с заданным количеством потоков и подать его в `Schedulers.from()`: 772 | 773 | ```java 774 | int numberOfThreads = 20; 775 | ExecutorService executor = 776 | Executors.newFixedThreadPool(numberOfThreads); 777 | 778 | Scheduler scheduler = Schedulers.from(executor); 779 | 780 | Observable.just("Check", "this", "out") 781 | .subscribeOn(scheduler) 782 | .doFinally(executor::shutdown) 783 | .subscribe(System.out::println); 784 | ``` 785 | 786 | `ExecutorService` будет держать вашу приложуху живой до конца времён, поэтому надо вручную говорить ему о завершении. Вот почему в примере вызыван `executor::shutdown`. 787 | 788 | ##### Starting and shutting down Schedulers 789 | Каждый дефолтный шедулер лениво инстанциируется, когда вы в первый раз его используете. Можно высвободить `computation()`, `io()`, `newThread()`, `single()`, `io()` в любое время, вызвав метод `shutdown()` у каждого, или можно освободить сразу все через `Schedulers.shutdown()`. Это остановит все выполняющиеся потоки и запретит выполнение новых задач. Чтобы возобновить работу стандартных шедулеров, надо будет вызывать `Schedulers.start()`. 790 | 791 | > В десктопных и мобильных приложениях _start_ и _stop_ шедулеров не так актуален. 792 | 793 | #### Understanding subscribeOn() 794 | С помощью оператора `subscribeOn()` определяется исходный `Observable` и выбирается шедулер, который будет запускать операции на одном из своих потоков. Эмишены будут сделаны _прямиком в этом потоке аж до самого_ `Observer`'а (если не указан `observeOn`). В случае, если для источника никакого шедулера _ещё_ не указано, будет выбран тот, который мы задали в `subscribeOn()`(имеется в виду, что ранее нигде не был задан `subscribeOn`). Может быть поставлен в любом месте в чейне, **RxJava** сама найдёт исходный обзёрвабл(пробежится вверх по цепочке) и закрепит его работу за указанным потоком. Для ясности рекомендуется ставить как можно ближе к источнику. 795 | 796 | Наличие нескольких `Observer`'ов приведёт к тому, что эмишены будут произведены в разных потоках, и каждый из них получит свои данные в отдельном потоке(ну или будет ждать, пока соотв.поток освободится и переключится на него). 797 | 798 | > В каком именно потоке `Observer` получает свои ништяки можно понять с помощью стандартного `Thread.currentThread()` и т.п. 799 | 800 | Если вдруг нам понадобилось, чтобы всех обслуживал один поток, то следует применить мультикаст. Тут важно, чтобы оператор `subscribeOn()` стоял _ДО_ мультикаста. 801 | 802 | ##### Nuances of `subscribeOn()` 803 | Надо знать, что некоторым обзёрваблам пофиг на шедулер, который вы задаёте в `subscribeOn()`. К примеру, `Observable.interval()` будет использовать `Schedulers.computation()`. Однако можно передать ему шедулер в качестве третьего аргумента. 804 | 805 | ```java 806 | Observable.interval(1, TimeUnit.SECONDS, Schedulers.newThread()); 807 | ``` 808 | 809 | Ещё один ньюанс: если вы задали(ну а вдруг) множество `subscribeOn`'ов в чейне, то выиграет тот, что ближе к источнику(обзёрваблу). 810 | 811 | В заключении автор отмечает, что `subscribeOn()`, в целом, определяет, какой шедулер будет использован обзёрваблом для своих эмишенов. 812 | 813 | #### Understanding `observeOn()` 814 | Как уже было сказано, `subscribeOn()` определяет, в каком потоке будут выпущены элементы из `Observable`'а. А вот для того, чтобы перехватить эти элементы и перенаправить их на другой поток, вплоть до конечного `Observer`'а, нам понадобится `observeOn()`. 815 | 816 | В отличие от `subscribeOn`, которому пофиг, где его ставят в чейне, с `observeOn` _"просто поставлю его здесь, вдруг прокатит"_ сработает, только если удача сегодня на вашей стороне. Выполнение всех процессов, стоящих выше по цепочке, он оставит на дефолтном шедулере (либо на том, который указан в `subscribeOn()`), а все последующие операции будут проведены уже на другом `Scheduler`'е. 817 | 818 | ```java 819 | Observable.just("blablabla") 820 | .subscribeOn(Schedulers.io()) 821 | .flatMap(..) 822 | .observeOn(Schedulers.computation()) 823 | .filter(..) 824 | .subscribe(..); 825 | ``` 826 | 827 | В примере выше `flatMap()` отработает на `Schedulers.io()`, а вот `filter()` будет запушен уже в `Schedulers.computation()`. 828 | 829 | Можно применять для того, чтобы производить разные операции на различных шедулерах, тем самым лучше задействуя CPU. Достигается путём многократного использования `observeOn()`в чейне. 830 | 831 | ##### Using `observeOn()` for UI event threads 832 | Когда дело доходит до того, чтобы пилить приложения с UI выясняется, что для этого самого UI выделяют отдельный поток, в котором собственно и происходят изменения в интерфейсе. Пользователю совсем не в кайф смотреть, что приложение фризится по нажатию на кнопки (как этого добиться, мы с вами прекрасно знаем). 833 | 834 | Спасибо **RxJava**, тут она нас буквально спасает. С её помощью можно любой UI-event переключить в другой поток, проделать все необходимые вычисления и выдать обратно. Для разных платформ были придуманы специальные шедулеры (_RxAndroid_, _RxJavaFX_, RxSwing_). 835 | 836 | > В примере про _JavaFX_ автор говорит о том, что в случае с UI источниками `subscribeOn()` не имеет никакого воздействия на чейн, надо проверить. 837 | 838 | ```java 839 | JavaFxObservable.actionEventsOf(refreshButton) 840 | .observeOn(Schedulers.io()) 841 | .flatMapSingle(a -> 842 | Observable.fromArray(/* запрос в сеть*/) 843 | .observeOn(JavaFxScheduler.platform()) 844 | .subscribe(list -> /*обновить данные в View*/); 845 | ``` 846 | 847 | В данном примере, после нажатия на кнопку, ивент переключится на IO шедулер, получит из сети свои данные и пульнёт их обратно в JavaFX'овский поток. 848 | 849 | ##### Nuances of obseveOn() 850 | Существуют детали насчёт этого оператора, о которых лучше б знать, особенно, когда дело доходит до производительности, страдающей из-за отсутствия _backpressure_ (разговор об этом будет в главе _Flowables and Backpressure_). 851 | 852 | Допустим у нас есть две операции А и Б. Про операторы пока думать не будем, сейчас не о том. Если между ними нет никакого `observeOn()`, чейн будет передавать эмишены строго один-в-единицу-времени от источника до операции А,потом до операции Б, а затем до `Observer`'а. Даже если обозначен `subscribeOn()`, чейн не будет выпускать новый эмишн, пока текущий не долетит до `Observer`'а. 853 | 854 | Такое положение дел меняется, когда мы добавляем `observeOn()`. Поставим его между операцией А и Б. В таком случае чейн не будет дожидаться, пока эмишн долетит до обзёрвера, а выпустит новый эмишн сразу после того, как текущий долетит до оператора `observeOn()`. Это фактически значит, что источник эмишнов и операция А могут производить элементы быстрее, чем операция Б способна обработать. Классическая ситуация - производитель выпускает больше, чем потребитель может съесть. В таком случае эмишены будут застревать в `observeOn()`, образуя тем самым очередь, что приведёт к проблемам с памятью. 855 | 856 | Вот почему надо использовать `Flowable`, когда надо иметь дело с большим количеством элементов. Этот тип обзёрваблов поддерживает `Backpressure`, которая общается с ним и говорит притормозить в таких случаях. 857 | 858 | #### Parallelization 859 | Параллельным вычислением можно назвать любую многопоточную систему, но в рамках изучения **RxJava** под этим понятием мы будем иметь в виду одновременную обработку нескольких эмишенов для заданного `Observable`. Если обзёрвабл эмитит 1000 элементов, логично было бы обрабатывать за одну единицу времени 8 элементов вместо одного. Это существенно ускорит работу. Но мы ведь с вами помним, что для поддержки многопоточности, **RxJava** эмитит по одному элементу (если бы мы попытались запушить сразу восемь, то всё бы к чертям поломалось). 860 | 861 | Тут вроде как вырисовываются некие ограничения, но **RxJava** на самом деле даёт самым умным, ловким и смелым парням тулзы и средства, чтобы строить реально крутые многопоточные приложухи. Действительно, мы не можем одновременно пушить много элементов в рамках одного `Observable`, но никто не мешает нам иметь много обзёрваблов, каждый из которых будет работать на своём потоке и эмитить элементы вместе с остальными! Секрет достижения _parallelization_ таится в операторе `flatMap()`. 862 | 863 | ```java 864 | Observable.range(1, 10) 865 | .map(i -> /*что-то делается за секунду*/) 866 | .subscribe(..); 867 | ``` 868 | 869 | Выполнение этого примера займёт (сколько бы вы думали!?) 10 секунд, потому как каждый эмишн обрабатывается один-за-другим. 870 | 871 | `flatMap()` смёрджит несколько обзёрваблов, полученных из эмишенов, даже если они _concurrent_. Давайте обернём каждый инт в `Observable.just()`, подпишемся на правильном шедулере и посмотрим, что будет: 872 | 873 | ```java 874 | Observable.range(1, 10) 875 | .flatMap(i -> Observable.just(i) 876 | .subscribeOn(Schedulers.computation()) 877 | .map(i2 -> /*что-то делается за секунду*/)) 878 | .subscribe(..) 879 | ``` 880 | 881 | А будет то, что теперь этот чейн выполнится значительно быстрее, чем за 10 секунд(_computation_ шедулер раскидает задачи по ядрам, так что скорость выполнения будет быстрее кратно количеству ядер). Мы реализовали мать его _concurrency_! 882 | 883 | Что же мы сделали, давайте разберём. Мы обернули каждый выпущенный инт в `Observable`, сказали выполнятся каждому из них на _computation_ шедулере, а затем предприняли какую-то операцию, которая выполняется за секунду. 884 | 885 | > `flatMap()` выпустит эмишены в один поток, что соответствует контракту `Observable`, который требует, чтобы эмишены оставались сериализоваными. Ловкость флэтмапа в том, что он не проделывает никакой лишней работы с многопоточностью и ничего не блокирует для того, чтобы выполнять подобные операции. Если поток уже пушит элементы из `flatMap()`'а вниз по цепочке к `Observer`'у, любые другие потоки, которые ждут своей очереди, просто напросто заэмитят свои данные в этот исполняемый поток. 886 | 887 | Не факт, что приведённый пример является оптимальным решением. Создание обзёрвабла для каждого выпущенного ранее элемента может привести к нежелательному оверхеду. Существуют более изящные способы работы с параллельными вычислениями. Если мы хотим избежать создание обзёрваблов для эмишенов, можно разбить исходный `Observable` на несколько, а потом объединить их с помощью `flatMap()`. У автора, например, восьмиядерный проц. Почему бы не разбить источник на восемь частей? 888 | 889 | Это можно реализовать с помощью `groupBy()`. Если у нас 8 ядер, то можно использовать значения от 0 до 7 в качестве ключа. Это позволит нам создать 8 `GroupedObservables`, которые разделят эмишены на 8 потоков. `GroupedObservable` не обязательно связаны с `subscribeOn()`, поэтому автор реализовывает многопоточность с помощью `observeOn()`. Так как с количеством потоков мы определились, то можно заюзать `newThread()` шедулер, но мы впилим обычный _io()_: 890 | 891 | ```java 892 | int coreCount = Runtime.getRuntime().availableProcessors(); 893 | AtomicInteger assigner = new AtomicInteger(0); 894 | Observable.range(1,10) 895 | .groupBy(i -> assigner.incrementAndGet() % coreCount) 896 | .flatMap(grp -> grp.observeOn(Schedulers.io()) 897 | .map(i2 -> /*какая-то работа*/)) 898 | .subscribe(..) 899 | ``` 900 | 901 | Каждый эмишн будет сгруппирован по номеру(ранее энкриментированному). Как только число достигнет семи, группировка опять начнёт с 0. Это гарантирует, что эмишены будут распределены самым оптимальным образом. 902 | 903 | > Все изменения переменных, находящихся за пределами чейна, должны быть проделаны потокобезопасно. Именно поэтому мы используем `AtomicInteger`, который предоставляет соотв.методы. 904 | 905 | ##### `unsubscribeOn()` 906 | Последний оператор про многопоточность. Иногда бывает так, что ликвидация `Observable`'а - достаточно дорогая операция (зависит от природы этого обзёрвабла). 907 | 908 | Это может привести к тому, что поток, который вызывает `dispose()`, затупит и будет высвобождаться дольше, чем моментально. Такой затуп, в свою очередь, приведёт к нежелательным фризам(ну а вдруг у нас `dispose()` вызван из UI-потока) и мало ли ещё чему, за что мы потом получим по шее. На помощь приходит `unsubscribeOn()`, в котором вариант задать нужный нам шедулер. 909 | 910 | > Можно ставить в любом месте в чейне. 911 | 912 | Что бы мы делали без заботливого **Thomas**'а **Nield**'а(автора книги), который не советует пихать этот оператор везде, где только можно! Спасибо, Томас, мы учтём, что это полезно только для толстых и затратных операций! 913 | 914 | > Можно ставить несколько в чейне. Всё, что обозначено вверх от оператора(вплоть до другого `unsubscribeOn()`), будет `dispose`иться с указанным в нём шедулером. 915 | 916 | #### Небольшие итоги 917 | Вкратце, что было в главе: 918 | 919 | - `subscribeOn()` определяет шедулер для вышестоящего `Observable`, в котором тот будет пушить свои эмишены. 920 | - `observeOn()` поменяет _thread_ эмишенов на поток из другого шедулера. 921 | - `unsubscribeOn()` - для случаев, когда высвобождение потока - боль. 922 | 923 | ----- 924 | 925 | ### Switching, Throttling, Windowing, and Buffering 926 | Нередко возникают ситуации, когда обзёрвабл эмитит быстрее, чем `Observer` может обработать. В основном это происходит, когда мы впиливаем _concurrency_, и различные операторы начинают работать в разных `Scheduler`'ах. Проблемы начинаются там, где эмишены собираются в очереди на выполнение в медленных операторах. 927 | 928 | Лучшим решением таких ботлнеков (от англ. _bottleneck_ - горлышко бутылки) будет использование `Flowable`, которые не сильно отличаются от `Observable`, только в части поддержки `backpressure`. Но, во-первых, не каждый источник подвержен _backpressure_(например, `Observable.interval()`), а во-вторых - об этом позже в отдельной главе. 929 | 930 | Есть инструменты, позволяющие бороться с повышенной частотой эмитов. Об них и будет говориться в этой главе. 931 | 932 | #### Buffering 933 | Оператор `buffer()` соберёт эмишены в пачку и выпустит каждую пачку в виде листа или другой коллекции. Скоуп (граница) может быть задан в виде фиксированного размера для буфера, временного окна или отрезков из эмишенов, определённых другим `Observable`. 934 | 935 | ##### Fixed-size buffering 936 | Самая простая версия `buffer()`'а. Принимает в качестве аргумента `count` - размер конечной пачки эмишенов. Если надо собрать в пачки по восемь штук, к примеру, тупо пишем `.buffer(8)`. 937 | 938 | Вторым аргументом принимает коллекцию, в которую мы хотим собирать элементы. `buffer(8, HashSet::new)`. 939 | 940 | Чтобы сделать свой код чуточку повеселее, можно передать инт в качестве второго аргумента. Это скажет `buffer()`'у, какой элемент надо пропускать, прежде чем включать буферизацию: `buffer(8, 3)` будет пропускать каждый третий элемент. Если обозначить `skip`<`buffer size`, то получится что-то вроде аккумулирующего буффера, который будет отображать последние элементы типа: `.buffer(2, 1)` : [1,2], [2,3], [3,4] и т.д. 941 | 942 | ##### Time-based buffering 943 | Аналогично обычному `buffer()`, только принимает TimeUnit'ы в качестве аргументов. Единственное, о чём стоит упомянуть: буффер, работающий на времени, оперирует на _computation_ шедулере. 944 | 945 | ##### Boundary-based buffering 946 | Самый мощный `buffer()`, который принимает другой `Observable` в качестве аргумента, описывающего границы буферизации. Когда этот обзёрвабл что-то эмитит, `buffer()` воспринимает это как сигнал к буферизации. Нагляднее показать это на примере: 947 | 948 | ```java 949 | Observable cutOffs = Observable.interval(1, TimeUnit.SECONDS); 950 | 951 | Observable.interval(300, TimeUnit.MILLISECONDS) 952 | .map(i -> (i+1) * 300) 953 | .buffer(cutOffs) 954 | .subscribe(System.out::prinln); 955 | 956 | >output: 957 | [300, 600, 900] 958 | [1200, 1500, 1800] 959 | [2100, 2400, 2700] 960 | [3000, 3300, 3600, 3900] 961 | ``` 962 | 963 | #### Windowing 964 | Оператор `window()` чень похож на `buffer()`, за одним только исключением - он складывает элементы в другие обзёрваблы, а не коллекции. На выходе получается `Observable>`, который эмитит обзёрваблы. Каждый такой обзёрвабл закэширует эмишены для каждого скоупа, а затем выдаст их, как только на него подпишутся. Это позволяет работать с эмишенами сразу же, как только они становятся доступны (что выгоднее, чем ждать, пока сформируются и заэмитятся коллекции). 965 | Этот оператор удобно использовать, когда нужно проделать какие-то трансформации над пачками элементов. 966 | 967 | ##### Fixed-size windowing 968 | В нижеприведённом примере мы возмём пачку стрингов, применим оператор `window()`, чтобы провести эмишены через буфер. Только теперь они будут обёрнуты в другие `Observable`'ы, с которыми мы можем что-нибудь проделать в реактивном стиле. 969 | 970 | ```java 971 | Observable.range(1, 50) 972 | .window(8) 973 | .flatMapSingle(obs -> obs.reduce("", (total, next) -> 974 | total + (total.equals("") ? "" : "|") + next)) 975 | .subscribe(System.out::println) 976 | 977 | >output: 978 | 1|2|3|4|5|6|7|8 979 | 9|10|11|12|13|14|15|16 980 | 17|18|19|20|21|22|23|24 981 | 25|26|27|28|29|30|31|32 982 | 33|34|35|36|37|38|39|40 983 | 41|42|43|44|45|46|47|48 984 | 49|50 985 | ``` 986 | 987 | Принимает значение `skip` так же, как и предыдущий оператор `buffer()`. 988 | 989 | ##### Time-based windowing 990 | Самые догадливые поняли, что этот вариант основан на времени, идём дальше. 991 | 992 | ##### Boundary-based windowing 993 | см. [`buffer()`](#boundary-based-buffering), который принимает другой обзёрвабл. Аналогично. 994 | 995 | #### Throttling 996 | В отличие от операторов `buffer()` и `window()`, которые собирают эмишены в пачки и выпускают дальше, `throttle()` напротив, пропускает какие-то из элементов. Это полезно, когда частый выпуск эмишенов нежелателен или не несёт никакой пользы. 997 | 998 | Чтобы лучше понять принцип действия, возьмём в качестве примера три обзёрвабла `Observable.interval()`. Первый будет эмитить каждые 100 миллисекунд, второй - каждые 300 и третий - 2000. Возьмём десять элементов из первого, три из второно и два из третьего. Для того, чтобы создать источник, который будет быстро эмитить элементы с чередующимися интервалам, используем `Observable.concat()`: 999 | 1000 | ```java 1001 | Observable source1 = Observable.interval(100, TimeUnit.MILLISECONDS) 1002 | .map(i -> (i + 1) * 100) //спамили в затраченное время 1003 | .map(i -> "SOURCE 1: + i) 1004 | .take(10); 1005 | 1006 | Observable source2 = Observable.interval(300, TimeUnit.MILLISECONDS) 1007 | .map(i -> (i + 1) * 300) 1008 | .map(i -> "SOURCE 2: + i) 1009 | .take(3); 1010 | 1011 | Observable source3 = Observable.interval(2000, TimeUnit.MILLISECONDS) 1012 | .map(i -> (i + 1) * 2000) 1013 | .map(i -> "SOURCE 3: + i) 1014 | .take(3); 1015 | 1016 | Observable.concat(source1, source2, source3) 1017 | .subscribe(System.out::println); 1018 | 1019 | /*не забыли усыпить главный поток*/ 1020 | 1021 | >output: 1022 | SOURCE 1: 100 1023 | SOURCE 1: 200 1024 | SOURCE 1: 300 1025 | ... 1026 | SOURCE 1: 1000 1027 | SOURCE 2: 300 1028 | SOURCE 2: 600 1029 | SOURCE 2: 900 1030 | SOURCE 3: 2000 1031 | SOURCE 3: 4000 1032 | ``` 1033 | 1034 | Сначала первый обзёрвабл быстро пушит свои 10 эмишенов за секунду, потом отрабатывает второй и затем третий кое-как отдаёт свои элементы за 4 секунды. Давайте юзанём `throttle()`, чтобы взять лишь некоторые из этих элементов и заигнорить остальные. 1035 | 1036 | ##### throttleLast() / sample() 1037 | `throttleLast()`(идентичен `sample()`) выпустит последний элемент, выпущенный за задданый период времени. Выпускать получившиеся элементы будет с той же частотой. 1038 | 1039 | ```java 1040 | Observable.concat(source1, source2, source3) 1041 | .throttleLast(1, TimeUnit.SECONDS) 1042 | .subscribe(Sustem.out::println); 1043 | 1044 | >output: 1045 | SOURCE 1: 900 1046 | SOURCE 2: 900 1047 | SOURCE 3: 2000 1048 | ``` 1049 | 1050 | ##### throttleFirst() 1051 | Ну парни, не буду расжёвывать. 1052 | 1053 | ##### throttleWithTimeout() / debounce() 1054 | Чтобы описать прицип работы этого оператора, автор приводит в пример сцену из типичного голливудского боевика. Пока по главному герою выпускают рожок, тот стоит за урытием и ничего не предпринимает. Спустя некоторого затишья, он высовывается и начинает палить в ответ. То есть эти операторы ждут, пока источник перестанет выпускать эмишены, и начинают работать только спустя некоторое время "затишья". 1055 | 1056 | ``` 1057 | Observable.concat(source1, source2, source3) 1058 | .throttleWithTimeout(1, TimeUnit.SECONDS) 1059 | .subscribe(System.out::prinln); 1060 | 1061 | >output: 1062 | SOURCE 2: 900 1063 | SOURCE 3: 2000 1064 | SOURCE 3: 4000 1065 | ``` 1066 | 1067 | 900 эмишн из второго источника был последним элементом перед тем, как стартанул третий. Он попал в консольный принт потому, что пауза между эмишенами, последующая за этим элементом, была больше секунды. Аналогичным образом отработали элементы 2000 и 4000. 1068 | 1069 | > То есть мы тем самым говорим чейну: "Чувак, подожди, пока какое-то время ничего не будет происходить и возьми последний элемент, который был выпущен перед этим затишьем" (прим.автора) 1070 | 1071 | `debounce()`(==`throttleWithTimeout()`) - эффективный способ обработать излишне производительные источники (такие, как тупые юзеры, клацающие по кнопкам со скоростью бешенной обезьяны). Единственный недостаток: эти операторы задержат нужный нам эмишн на определённое время. Особо нетерпеливые пользователи заметят некоторую задержку, нам же надо убедиться, что эти говнюки перестали что-то вводить, верно? Так вот, чтобы избежать такой задержки, можно использовать `switchMap()`, о котором речь пойдёт чуть позже. 1072 | 1073 | #### Switching 1074 | Есть такая мощная приблуда, как `switchMap()`. Её использование очень похоже на работу с `flatMap()`, но с некоторыми оговорками. Она будет эмитить _последний_ `Observable` из последнего эмишена и высвобождать выполнение всех остальных. Другими словами, `switchMap()` позволяет вам отменять выпуск `Observable`'ов и переключиться на новый, предотвращая тем самым ненужные вычисления. 1075 | 1076 | Допустим, у нас есть обзёрвабл, который эмитит пять стрингов. Причём делает это так, что каждый стринг выстреливает с какой-то задержкой. 1077 | 1078 | ```java 1079 | Observable items = Observable.just("Раз", "Два", "Три", "Четыре", "Пять"); 1080 | 1081 | // задержим каждый стринг, чтобы показать какие-то вычисления 1082 | Observable processedStrings = items.concatMap(s -> 1083 | Observable.just(s) 1084 | .delay(/*рандомное число*/, 1085 | TimeUnit.MILLISECONDS)); 1086 | 1087 | processedStrings.subscribe(System.out::println); 1088 | 1089 | >output: 1090 | Раз 1091 | Два 1092 | Три 1093 | Четыре 1094 | Пять 1095 | ``` 1096 | 1097 | Такой пример отработает по времени так, как ему вздумается. 1098 | А нам, допустим, надо повторять эту работу каждые 5 секунд. При чём так, чтобы все элементы, выпущенные позднее - дропались (и у них при этом отработал `dispose()`). Это довольно просто сделать с помощью `switchMap()`. Создадим обзёрвабл, который эмитит каждые пять секунд. Затем, используем на него `switchMap()` к обзёрваблу, который хотим обработать (в данном случае `processedStrings`). Каждые пять секунд эмишн, идущий в `switchMap()` высвободит выполняемый на данный момент `Observable`, к которому он мапится. 1099 | 1100 | ```java 1101 | // продолжение предыдущего примера 1102 | Observable.interval(5, TimeUnit.SECONDS) 1103 | .switchMap(i -> processedStrings.doOnDispose(() -> /*напечатали в консоль*/) 1104 | .subscribe(/*напечатали в консоль*/); 1105 | ``` 1106 | 1107 | В итоге в консоли каждые пять секунд будет написано сообщение о том, что такой-то `processedStrings` отработал `dispose()`. Помимо этого туда будут печататься наши "Раз", "Два", "Три", "Четыре" в зависимости от того, кто из них успел заэмититься за эти пять секунд. После каждого сообщения о `dispose()` счёт будет начинаться сначала. 1108 | 1109 | Ешё раз, `switchMap()` полезен, когда надо отсечь выполнение ненужной работы. Это чаще всего полезно при работе с UI, где быстрые действия юзера создают бессмысленные запросы в сеть/базу и т.п. Для того, чтобы отменить такие запросы и заменить из на выполнение новой задачи, используйте `switchMap()`. 1110 | 1111 | > К примеру, ввод в EditText порождает запрос в сеть. В отличие от `debounce()`, который привнесёт некоторый затуп по отправлению запроса в сеть, `switchMap()` просто отменит предыдущие запросы (рассуждения автора конспекта) 1112 | 1113 | Для того, чтобы `switchMap()` работал эффективно, надо проследить, чтобы поток, который пушит в него элементы, не был занят в самом операторе. Это значит, что вам стоит использовать `observeOn()` или `subscribeOn` внутри оператора `switchMap()`. Если операции в свитчмапе дорогие, и `dispose()` занимает много времени, то неплохо бы вспомнить об `unsubscribeOn()`. 1114 | 1115 | Прикольный трюк: чтобы предотвратить выполнение какой-то бесконечной или очень затратной работы без старта новой - вернуть `Observable.empty()`. Автор книги приводит классный пример секундомера на `JavaFX`: 1116 | 1117 | ```java 1118 | ... 1119 | ToggleButton startStopButton = new ToggleButton(); 1120 | 1121 | // мультикаст для toggleButton'овского состояния true/false 1122 | Observable selectedStates = JavaFxObservable.valuesOf( 1123 | startStopButton.selectedProperty()) 1124 | .publish() 1125 | .autoconnect(2); 1126 | 1127 | // Если использовать `switchMap()` с состоянием кнопки, это будет говорить 1128 | // чейну, когда запускать `Observable.interval()`, а когда применять к нему 1129 | // `dispose()`, переключаясь при этом на эмишн пустого обзёрвабла. 1130 | selectedStates.switchMap(selected -> { 1131 | if (selected) { 1132 | return Observable.interval(1, 1133 | TimeUnit.MILLISECONDS); 1134 | } else { 1135 | return Observable.empty(); 1136 | }) 1137 | .observeOn(JavaFxScheduler.platform()) 1138 | .map(Object::toString) 1139 | .subscribe(label::setText()); 1140 | 1141 | // Изменим текст кнопки в зависимости от её состояния 1142 | selectedStates.subscribe(selected -> startStopButton.setText( 1143 | selected ? "STOP" : "START")); 1144 | ... 1145 | ``` 1146 | 1147 | Кусок кода описывает примерно такое приложение-секундомер: 1148 | 1149 | ![](start-stop.png) 1150 | 1151 | Нажатие кнопки будет запускать и останавливать секундомер, который отображается в миллисекундах. Обратите внимение, что `ToggleButton` эмитит `Boolean` `True`/`False` через обзёрвабл `selectedStates`. Мы применяем к этому обзёрваблу мультикаст для того, чтобы предотвратить дублирование `JavaFX`'овских `listener`'ов, ну и потому, что у нас два `Observer`'а. Первый обзёрвер использует `switchMap()`'у, которая будет переключаться между `Observable.interval()` и `Observable.empty()` в зависимости от значения `Boolean`. Так как `Observable.interval()` работает на _computation_ шедулере, мы должны вернуть результаты его работы обратно в мэйн поток `JavaFX`, для этого обозначили правильный `observeOn()`. Второй `Observer` просто меняет название кнопки на "START"/"STOP". 1152 | 1153 | #### Grouping keystrokes 1154 | В завершении этой главы мы попробуем применить всё, что в ней было освещено на примере с нажатием клавиш. Достаточно распространённый кейс (к примеру, юзер вводит что-то в поиск и нам надо сразу же переключиться на элемент с наиболее совпадающим именем). Задача: сгруппировать эти нажатия в `String`'и без всякой задержки. 1155 | 1156 | В качестве примера опять будет взят `JavaFX` с `RxJavaFX`. 1157 | 1158 | ```java 1159 | ... 1160 | Label typedTextLabel = new Label(""); 1161 | 1162 | // Мультикаст, применённый к нажимаемым клавишам 1163 | Observable typedLetters = JavaFxObservable.eventsOf( 1164 | scene, KeyEvent.KEY_TYPED) 1165 | .map(KeyEvent::getCharacter) 1166 | .share(); 1167 | 1168 | // Сигнализирование того, что 300 мс ничего не происходило 1169 | Observable restSignal = typedLetters.throttleWithTimeout( 1170 | 300, TimeUnit.MILLISECONDS) 1171 | .startWith(""); 1172 | 1173 | // Теперь триггерим `switchMap()` на каждый период затишья для того, чтобы 1174 | // объединить ранее нажатые клавиши в один стринг с помощью `scan()` 1175 | restSignal.switchMap(s -> typedLetters.scan("", (rolling, next) -> 1176 | rolling + next)) 1177 | .observeOn(JavaFxScheduler.platfomr()) 1178 | .subscribe(s -> typedTextLabel.setText(s)); 1179 | ... 1180 | ``` 1181 | 1182 | Когда мы что-то печатаем, `Label` отображает саккумулированный `String`. Обратите внимание: если втечение 300 мс ничего не происходит, ввод сбрасывается и эмититься новый `scan()` с высвобождением предыдущего. Новый скан начинается с `""`. Такой приём может быть очень полезен в случае, когда нужно мгновенно искать что-то по запросу, выдавая предположения с автокомплитом. 1183 | 1184 | Разберём чуть подробнее, как это работает. У нас есть один `Observable`, который эмитит нажатие клавиш. К нему применён мультикаст, так как этот обзёрвабл использован два раза. Сначала он нужен для создания другого `Observable`, сигнализируещого о том, что вот уже 300 мс ничего не вводится с клавиатуры. Затем мы используем к этому сигнализирующему обзёрваблу `switchMap()`, передавая в него всё тот же клавишный обзёрвабл - так мы бесконечно группируем результативные нажатия в стринги с помощью `scan()` до тех пор, пока он не за`dispose()`'ится спустя 300 мс безактивности. Затем выстрелит новый `scan()`. 1185 | 1186 | #### Summary 1187 | Короче говоря `switching, throttling, windowing and buffering` нужны в тех случаях, когда _backpressure_ не работает. В частности, когда мы имеем дело с юзерским вводом. 1188 | 1189 | ----- 1190 | 1191 | ### Flowables and Backpressure 1192 | В предыдущей главе мы разобрали каким образом можно перехаватить быстро выпускаемые элементы и объеденить/пропустить их. Но для некоторых случаев избыточного выпуска эмишенов лучшим решением будет притормозить сам выпуск до той степени, чтобы обзёрвер успевал их обрабатывать. Такая возможность более известна, как _backpressure_ или _flow control_. Реализована в `Flowable`. 1193 | 1194 | #### Understanding backpressure 1195 | По ходу книги автор не раз подчёркивал, что природа `Observable`'а основана на пушах. Если убрать всякую многопоточность, то всё, что останется - это синхронные пуши элементов, один-за-другим, от `Observable` к `Observer`'у. Это происходит в сериализованном и последовательном _flow_(потоке). 1196 | 1197 | ##### An example that needs backpressure 1198 | Как только добавляются всякие `observeOn()`'ы, параллельные выполнения и прочие операторы типа `delay()`, наш чейн становится _асинхронным_. Это значит, что источники эмишенов и их потребители работают на разных потоках, а следовательно, их работа может быть не согласована в части количества произведённых/обработанных эмишенов. Проще говоря, обзёрваблы могут фигачить больше, чем надо. Происходит это потому, что как только эмишн упал в новый шедулер, изначальный `Observer` более не отвечает за то, что он дойдёт до обзёрвера. Более того, источник будет пушить элементы даже если предыдущие не дошли до пункта назначения. 1199 | 1200 | > Я не стал переносить приведённый автором пример, там всё просто. Обзёрвабл, который эмитит кучу интов. Всё это сыпется в обзёрвер, где печатается в консоль. Без `observeOn()` всё вроде-бы норм, но с ним получается так, что в консоль печатается медленнее, чем эмитит обзёрвабл. 1201 | 1202 | ##### Introducing the Flowable 1203 | Вместо того, чтобы лепить костыли с ядерными джавовскими семафорами, просто возьмём `Flowable` (спасибо, **RxJava**). Это такой вариант `Observable`, в котором реализован _backpressure_. 1204 | 1205 | > На `Flowable` подписывается не `Observer`, а `Subscriber`. Такая цепочка `Flowable`-`Subscriber` демонстрирует `pull` логику, вместо `push`. То есть это сабскрайбер говорит "Ок, чувак, давай свои элементы", а не источник, стреляющий их по принципу "Смотри, чё у меня есть, на на на". 1206 | 1207 | `Flowable` - нормальный такой инструмент, который чётко сработает из-коробки. Нам не нужно определять какие-то особые политики для _backpressure_ или задавать непонятные параметры. Но мы можем создать свой `Flowable`, если очень надо. 1208 | 1209 | Теперь пробежимся по местам, в которых собственно надо его использовать. 1210 | 1211 | ##### When to use Flowables and backpressure 1212 | Крайне важно вкуривать, когда использовать `Flowable` вместо `Observable`. Из-за того, что он может выкинуть `OutOfMemoryError`, `MissingBackpressureException`, использование `Flowable` может стать болью. Особенно, если источник, получающий эмишены, не имеет никакого _backpressure_ протокола. Более того, `Flowable` добавляет некоторый оверхед к нашим операциям и срабатывает не так быстро, как `Observable`. 1213 | 1214 | ##### Use an Observable If... 1215 | Используйте `Observable`, если: 1216 | - Вы ожидаете, что за свою жизнь обзёрвабл выпустит несколько эмишенов (менее 10К). Либо, если выпуски эмишенов преривистые и не жирные. В противном случае, когда ожидается овердохрена элементов, над которыми будут производиться пипец-какие-мать-их-операции, лучше взять `Flowable`. 1217 | - Ваши операции вполне себе синхронны и вы пользуетесь простеньким `subscribeOn()` вначале чейна (при таком раскладе, как мы помним, процесс остаётся синхронным и его работа происходит в одном потоке от начала и до конца). Однако, ситуация меняется, когда мы зиппуем/комбинируем несколько стримов на разных потоках, или используем всякие там `observeOn()`, `interval()` с `delay()`ями. В таких случаях наши чейны перестают быть синхронными и может так оказаться, что использование `Flowable` - предпочтительнее. 1218 | - Вы хотите заэмитить действия пользователя, такие как: нажатия на кнопки, выбор элементов, много чего эти чудики ещё могут сделать с нашим приложением. К сожалению, мы не можем сказать юзерам, чтобы они притормозили, поэтому использование `Flowable` является сомнительным мероприятием. 1219 | 1220 | ##### Use a Flowable If... 1221 | Используйте `Flowable`, если: 1222 | - Вы имеете дело с реально большим количеством элементов, и есть вероятность того, что эти элементы будут обрабатываться асинхронно. 1223 | - Вы хотите эмитить из IO операций, которые поддерживают _blocking_. Можно без труда контролировать источники данных, которые поочерёдно их перебирают (к примеру `ResultSet` в JDBC или файлы) - мы можем приостановить или возобновить их работу тогда, когда нам это нужно. Запросы в сеть и `Streaming` API'шки обычно могут контролировать точное количество возвращаемых данных, поэтому легко сочетаются с _backpressure_. 1224 | 1225 | В первой эрыксджаве `Observable` поддерживал _backpressure_. Причиной, по которой их разделили на два отдельных класса, явилась разница в использовании. 1226 | 1227 | Можно с лёгкостью переключаться между этими двумя источниками, но надо держать ухо в остро - у такого применения есть свои узкие места. 1228 | 1229 | #### Understanding the Flowable and Subscriber 1230 | Большинство фабрик из `Observable` применимы и к `Flowable`. 1231 | 1232 | А что там с `Flowable.interval()`, который вроде как должен эмитить с заданной периодичностью? Может ли к нему быть применён _backpressure_? По идее, если тормознуть выпуск его элементов, то они перестанут быть привязанными к временным отрезкам, что потеряет всякий смысл. Именно поэтому, `Flowable.interval()` - один из немногих случаев, который приводит к `MissingBackpressureException`, когда вниз по чейну кому-то вдруг понадобилось притормозить. Для того, чтобы с этим бороться, есть свои операторы, но о них позже. 1233 | 1234 | ##### The Subscriber 1235 | Как уже было отмечено, в качестве подписчика `Flowable` исползует `Subscriber`'а, а не `Observer`'a. Если передать в `subscribe()` лямбду (а не `Subscriber`), то он вернёт `Subscription` вместо `Disposable`. Для того, чтобы высвободить сабскрипшн, надо вызывать `cancel()`. В методе `onSubscribe()` есть возможность обратиться к текущему `Subscription`'у и вызвать у него `request()`(обозначает, что подписчик готов получать элементы). 1236 | 1237 | Самый быстрый способ создать `Subscriber` - передать лямбду в `subscribe()`. Всё как с `Observable`'ом. 1238 | 1239 | Можно, разумеется, запилить своего `Subscriber`'а с блэкджеком и шл*хами. Придётся только имплементить `onNext()`, `onError()` и `onComplete()`(`onSubscribe` у него тоже есть). Принцип будет немного отичаться от создания кастомного обзёрвера, так как нам надо будет `request()`'ить эмишены у `Subscription`'а. 1240 | 1241 | Простейший способ заимплементить `Subscriber`'а - сунуть в `request()` значение `Long.MAX_VALUE`, что буквально скажет сабскрипшену: "Чувак, а ну ка давай сюда всё, что у тебя там есть!". Даже если операторы будут запрашивать свой собственный _backpressure_, никакого _backpressure_ между последним оператором и `Subscriber`'ом не будет. В принципе это норм, так как вышестоящие операторы по-своему справятся с _flow_. 1242 | 1243 | Если вы хотите, чтобы ваш кастомный `Subscriber` контролировал _backpressure_ каким-то особенным образом, то придётся микроконтроллить вызовы `request()`. Завязать их на какой-нибудь `AtomicInteger` или тому подобное. 1244 | 1245 | > Вообще, по ходу конспекта куча примеров была опущена, потому что простыня большая, а смысл на поверхности (иногда и потому, что лень переписывать, как в этот раз). 1246 | 1247 | Когда пилите кастомный `Subscriber`, помните, что `request()` не поднимается по всему чейну до самого `Flowable`, но только до первого оператора. 1248 | 1249 | #### Creating a Flowable 1250 | Ранее в книге был приведён пример создания своего собственного `Observable`. 1251 | 1252 | ```java 1253 | Observable customObservable = Observable.create(emitter -> { 1254 | for(int i=0; i<=100; i++) { 1255 | if (emitter.isDisposed()) { 1256 | return; 1257 | } 1258 | emitter.onNext(i); 1259 | } 1260 | emitter.onComplete(); 1261 | }); 1262 | ``` 1263 | 1264 | Обзёрвабл заэмитит 100 элементов и вызовет `onComplete()`. Он может быть остановлен с помощью `dispose()`, вызванного у полученного из `subscribe()` `Disposable`'а. 1265 | 1266 | Создание `Flowable` с помощью фабрики `Flowable.create()` будет чуть сложнее, потому как надо продумать различные ситауции, в которых надо приостановить выпуск элементов. Обычного цикла `for` будет недостаточно. Есть утилиты, упрощающие создание кастомных `Flowable`. 1267 | 1268 | ##### Using Flowable.create() and BackpressureStrategy 1269 | При создании `Flowable` с помощью `Flowable.create()` нам необходимо указать `BackpressureStrategy` в качестве второго аргумента. Сам по себе этот `enum` ничего магического не сделает. Он всего лишь описывает способ, с которым должен отработать _backpressure_. 1270 | 1271 | Тут мы используем `BackpressureStrategy.BUFFER` для того, чтобы поместить эмишены в буфер, как только стрельнёт _backpressure_: 1272 | 1273 | ```java 1274 | Flowable source = Flowable.create(emitter -> 1275 | for(int i=0; i<=100; i++) { 1276 | if (emitter.isCancelled()) { 1277 | return; 1278 | } 1279 | emitter.onNext(i); 1280 | }, BackpressureStrategy.BUFFER); 1281 | ``` 1282 | 1283 | Такое решение не является оптимальным, так как эмишены будут держаться в бесконечной очереди, и есть вероятность хапнуть `OutOfMemoryError`. Но так мы хотя бы избежим `MissingBackpressureException`. 1284 | 1285 | Есть пять стратегий для работы с _backpressure_: 1286 | 1287 | BackPressureStratgy | Описание 1288 | -----|----- 1289 | MISSING | Стратегия отсутствует. Нижестоящий чейн должен сам справляться с _backpressure overflow_. Полезно в сочетании с операторами `onBackpressureXXX()` 1290 | ERROR | Сигнализирует о `MissingBackpressireException` в момент, когда чейн не может справляться с эмишенами. 1291 | BUFFER | Ставит эмишены в очередь до тех пор, пока `Subscriber` не будет опять в состоянии их принимать. Может вызвать `OutOfMemoryError`. 1292 | DROP | Если чейн не способен принимать элементы, они будут дропаться до тех пор, пока потребитель не очухается. 1293 | LATEST | Как только хвост чейна вновь сможет обрабатывать эмишены, он получит последний из них 1294 | 1295 | ##### Turning an Observable into a Flowable (and vice-versa) 1296 | Превращаем `Observable` в `Flowable` и наоборот. 1297 | 1298 | Есть способ применить стратегию _backpressure_ к источнику, который изначально этого не поддерживает. Можно с лёгкостью конвертировать обзёрвабл в `Flowable` - достаточно ткнуть `toFlowable()`, который принимает аргумент `BackpressureStrategy`. Изначальный `Observable` не будет ничего знать о _backpressure_, поэтому продолжит пушить свои элементы настолько быстро, насколько он вообще может. А в это время `toFlowable()` отработает, как фильтрующий прокси для нежелательных эмишеннов. 1299 | 1300 | ```java 1301 | Observable source = Observable.range(1, 1000); 1302 | 1303 | source.toFlowable(BackpressureStrategy.BUFFER) 1304 | .observeOn(Schedulers.io()) 1305 | .subscribe(..); 1306 | ``` 1307 | 1308 | > Ещё раз: такая стратегия может привести к `OutOfMemoryError`. В реальном мире лучше использовать `Flowable.range()` с самого начала. Но иногда может получится так, что придётся иметь дело с `Observable`. 1309 | 1310 | У `Flowable` тоже есть метод, делающий из него `Observable` - `toObservable()`. Незаменимая штука, когда надо использовать в чейне с обзёрваблами. 1311 | 1312 | ```java 1313 | Flowable integers = Flowable.range(1, 1000) 1314 | .subscribeOn(Schedulers.computation()); 1315 | 1316 | Observable.just("Раз", "Два", "Три") 1317 | .flatMap(s -> integers.map(i -> i + "-" + s).toObservable()) 1318 | .subscribe(..); 1319 | ``` 1320 | 1321 | Даже после того, как мы обернули фловабл в `Observable`, он продолжит поддерживать _backpressure_. Но с тех пор, как он стал обзёрваблом, чейн запросит у него `Long.MAX_VALUE` элементов. Такой приём подходит, если ниже по цепочке нет никакого _concurrency_, и не проделывается никакой большой работы. 1322 | 1323 | В общем и целом - если решили использовать `Flowable`, то стоит его и придерживаться. 1324 | 1325 | #### Using onBackpressureXXX() operators 1326 | Если мы создали `Flowable`, у которого нет никакой стратегии по работе с _backpressure_, можно применить таковую с помощью операторов типа `onBackPressureЧтоТоТам()`. Например, нельзя замедлить эмишены у `Flowable.interval()`, так как этот источник основан на времени. Но можно впилить что-то типа прокси между ним и `Subscriber`'ом. 1327 | 1328 | Иногда `Flowable` могут быть созданы с `BackpressureStrategy.MISSING` затем, чтобы задать эту стратегию позже. 1329 | 1330 | ##### onBackPressureBuffer() 1331 | Данный оператор возьмёт существующий `Flowable`, у которого нет стратегии, и применит к нему `BackpressureStrategy.BUFFER` начиная от собственно оператора и далее по цепочке. 1332 | 1333 | У этого метода есть ряд перегруженных вариантов, смотрите JavaDoc. Одним из аргументов может быть вместимость(`capacity`). Этот аргумент задаёт максимальное количество элементов, которое может быть помещено в буфер. С помощью `onOverflowAction` описывается действие, выстреливающее в случае, когда достигнут максимум вместимости, и места в буфере больше нет. Вы также можете обозначить `BackpressureOverflowStrategy`, которая скажет чейну как себя вести в данном случае. 1334 | 1335 | BackpressureOverflowStrategy | Описание 1336 | -----|----- 1337 | ERROR | Просто выбрасывает ошибку в момент, когда достигнут `capacity` 1338 | DROP_OLDEST | Выкидывает самое старое значение в буфере, чтобы освободить место для более нового элемента 1339 | DROP_LATEST | Дропает более новые значения 1340 | 1341 | ```java 1342 | Flowable.interval(1, TimeUnit.MILLISECONDS) 1343 | .onBackPressureBuffer(10, 1344 | () -> System.out.println("oberflow!), 1345 | BackpressureOverflowStrategy.DROP_LATEST) 1346 | .observeOn(Schedulers.io()) 1347 | .subscribe(i -> { 1348 | sleep(5); 1349 | System.out.prinln(i); 1350 | }); 1351 | 1352 | >output: 1353 | ... 1354 | overflow! 1355 | overflow! 1356 | 135 1357 | overflow! 1358 | overflow! 1359 | overflow! 1360 | 136 1361 | overflow! 1362 | ... 1363 | 492 1364 | overflow! 1365 | ... 1366 | ``` 1367 | 1368 | Между 136 и 492 большой разрыв и там всё утыкано "overflow!". Это потому, что эмишены были дропнуты из очереди в соответствии со стратегией. Очередь была заполнена и ждала, пока потребитель освободится. Поэтому вновь приходившие эмишены выбпасывались. 1369 | 1370 | ##### onBackPressureLatest() 1371 | Такой же, как и `onBackPressureBuffer()`, только оставляет себе последний выпущенный элемент. Все остальные элементы, заэмиченные в период, пока потребитель был занят, дропаются. 1372 | 1373 | ##### onBackPressureDrop() 1374 | Данный оператор просто игнорирует эмишены, если `Subscriber` уже занят. Полезно, когда эмишены признаются лишними (ну, к примеру, какой-то процесс уже запущен, а запрос на его запуск продолжает сыпаться в чейн). Можно передать `onDrop` лямбду, которая скажет, что же бл*дь делать в такой нелёгкой ситуации. Таким образом, `onBackPressureDrop()` не выстраивает элементы в очередь, пока потребитель занят. Он их нафиг выкидывает. По-нашему. 1375 | 1376 | #### Using Flowable.generate() 1377 | Использование стандартной фабрики для создания `Flowable` избавит нас от головняков с придумыванием стратегий для обработки _backpressure_. Однако, в случае, если мы создаём свои кастомные источники эмишенов, `Flowable.create()` или `onBackPressureXXX()` - компромис между простотой создания и обработкой запросов на _backpressure_. Они являются быстрым и эффективным способом кэширования или игнора ненужных эмишенов, но такое поведение не всегда желательно. Иногда всё же следует запилить свой велосипед. 1378 | 1379 | **RxJava** предоставляет достаточно удобный, хорошо абстрагированный инструмент для создания таких `Flowable`, которые будут реагировать на _backpressure_ сообразно источникам - `Flowable.generate()`. Он принимает `Consumer>` и лямбду, котороая перегрузит `onNext()`, `onComplete()` и `onError()` для того, чтобы передать в них ивенты каждый раз, когда элемент запрошен из источника. 1380 | 1381 | Перед тем, как использовать `Flowable.generate()`, попробуйте сначала сделать ваш источник `Iterable` и передать его в `Flowable.fromIterable()`. Этот оператор уважает _backpressure_ и с ним во многом будет проще. В противном случае, если вы всё-таки решили пойти во все тяжкие, дёргайте `Flowable.generate()`. 1382 | 1383 | Самый простой вариант `Flowable.generate()` принимает `Consumer>` и подразумевает, что нет никакого стэйта между выпуском элементов. Такая хрень может быть использована для создания генератора рандомных чисел, который с нежностью поддерживает _backpressure_: 1384 | 1385 | ```java 1386 | /* честно, влом было переписывать эту дичь, см.книгу, стр.253 */ 1387 | ``` 1388 | 1389 | ----- 1390 | 1391 | ### Transformers and Custom Operators 1392 | В **RxJava** можно запилить собственные операторы с помощью методов `compose()` и `lift()`, оба есть и в `Observable` и `Flowable`. Чаще всего, желание собрать собственный велосипед будет посещать вас, когда вам вдруг захочется привнести какую-то новую логику в уже существующий оператор. В особенных случаях воспаления мозга, проявляется навязчивая идея сваять с нуля свой собственный оператор. В этой главе речь будет идти о том, как: 1393 | 1394 | - Сочетать новые операторы с уже существующими, используя `compose()` и `Transformers`. 1395 | - Использовать `to()` 1396 | - Имплементить свои собственные костыли благодаря `lift()` 1397 | - Понять, что такое `RxJava2-Extras` и `RxJava2Extensions` 1398 | 1399 | #### Transformers 1400 | Работая с эриксджавой, вы наверняка захотите переиспользовать какие-то куски `Observable` или `Flowable`. Хорошие разрабы находят способы переиспользования ранее написанного кода, и **RxJava** способствует такому подходу. Для этого в ней есть такие штуки, как `ObservableTransformer` и `FlowableTransformer`, которые можно подать в `compose()`. 1401 | 1402 | ##### ObservableTransformer 1403 | Мы ранее использовали пример с Google Guava `ImmutableList`'ом, когда собирали в него значения: 1404 | 1405 | ```java 1406 | ... 1407 | .collect(ImmutableList::builder, ImmutableList::add) 1408 | .map(ImmutableList.Builder::build) 1409 | ... 1410 | ``` 1411 | 1412 | Логика, описанная в этих двух строчках, может понадобиться где-нибудь в другом месте. Чтобы не копипастить втупую, вспомним, что мы программисты и скомпонуем эти две операции. 1413 | 1414 | Спецом для таких дел был релизован `ObservableTransformer`, который принимает `Observable` в `apply()` и пробрасывает вниз по чейну другой `Observable`. В нашей имплементации мы можем вернуть чейн , который добавляет все необходимые операторы к изначальному `OBservable`'у. 1415 | 1416 | Для примера с гуавовским листом, мы возьмём дженерик `T` для данного `Obsevable`, а `R`'ом будет `ImmutableList`, заэмиченный из `Observable>`. Поместим эту логику в `ObservableTransformer>`: 1417 | 1418 | ```java 1419 | public static ObservableTransormer> toImmutableList() { 1420 | return new ObservableTransformer>() { 1421 | @Override 1422 | public ObservableSource> 1423 | apply(Observable upstream) { 1424 | 1425 | return upstream.collect(ImmutableList::builder, 1426 | ImmutableList.Builder::add) 1427 | .map(ImmutableList.Builder::build) 1428 | .toObservable(); // переводим Single в обзёрвабл 1429 | } 1430 | }; 1431 | } 1432 | ``` 1433 | 1434 | Так как `collect()` возвращает сингл, надо перевести его в `Observable` - сигнатура метода обязывает. 1435 | 1436 | С лямбдой выглядит немного симпатичнее: 1437 | 1438 | ```java 1439 | public static ObservableTransformer> toImmutableList() { 1440 | return upstream -> upstream.collect(ImmutableList::builder, 1441 | ImmutableList.Builder::add) 1442 | .map(ImmutableList::build) 1443 | .toObservable(); 1444 | } 1445 | ``` 1446 | 1447 | Для того, чтобы заюзать такой трансформер, надо передать его в `compose()`. Теперь его логику можно переиспользовать, шах и мат, грёбаные копипастеры! 1448 | 1449 | Не стремайтесь хранить `Transformers` в фабричных классах, это норм. `compose(GuavaTransformers.toImmutableList())` - и камон. 1450 | 1451 | > Для данного примера `toImmutableList()` можно было реализовать в виде переиспользуемого синглтона, так как он не принимает никаких параматров. 1452 | 1453 | Пример с стрингами: 1454 | 1455 | ```java 1456 | public static void main(String[] args) { 1457 | Observable.just("Вариант1", "Вариант2", "Вариант3") 1458 | .compose(joinToString("/")) 1459 | .subscribe(System.out::println); 1460 | } 1461 | 1462 | public static ObservableTransformer 1463 | joinToString(String separator) { 1464 | return upstream -> upstream.collect(StringBuilder::new, 1465 | (b, s) -> { 1466 | if (b.length() == 0) { 1467 | b.append(s); 1468 | } else { 1469 | b.append(sepparator) 1470 | .append(s); 1471 | } 1472 | }) 1473 | .map(Stringbuilder::toString) 1474 | .toObservable(); 1475 | } 1476 | 1477 | >output: 1478 | Вариант1/Вариант2/Вариант3 1479 | ``` 1480 | 1481 | ##### FlowableTransformer 1482 | Аналогично `ObservableTransformer`, только нужно организовать поддержку _backpressure_. В предыдущем примере просто меняем на `Flowable` и `toObservable()` -> `toFlowable()`. 1483 | 1484 | ##### Avoiding shared state with Transformers 1485 | Избегайте передачи стэйта в трансформеры. 1486 | 1487 | Хороший способ выстрелить себе в ногу - передавать какое-то состояние в трансформеры. Любите сайд-эффекты, нетестируемость кода и дебажные брэйкпоинты? Как можно чаще передавайте стэйт в `Transformers`! 1488 | 1489 | Если нет, то избегайте передачи _mutable_ переменных в разные _subscriptions_. Если всё-таки надо держать какой-то стэйт в чейне, то можно использовать `.defer()`, который создаст новую переменную для каждого подписчика: 1490 | 1491 | ```java 1492 | static ObservableTransformer> withIndex() { 1493 | return upstream -> Observable.defer( () -> { 1494 | AtomicInteger indexer = new AtomicInteger(-1); 1495 | 1496 | return upstream.map(v -> new IndexedValue(indexer.incrementAndGet(), 1497 | v)); 1498 | }); 1499 | } 1500 | ``` 1501 | 1502 | В целом избавьтесь от мыслей о том, чтобы добавлять в реактивщину изменяемые состояния и сайд-эффекты. Лучше не шарить _mutable_ объекты между подписчиками, только если бизнес не требует писать что-то, что может в любой момент сломаться. 1503 | 1504 | #### Using to() for fluent conversion 1505 | Оператор `to()` нужен для того, чтобы конвертить rx-ные штуки в что-то нереактивное. Принимает `Function, R>` 1506 | 1507 | Кусок из примера про `JavaFX`: 1508 | ```java 1509 | Binding binding = Observable.interval(1, TimeUnit.SECONDS) 1510 | .map(i -> i.toString()) 1511 | .observeOn(JavaFxScheduler.platform()) 1512 | .to(JavaFxObserver::toBinding); 1513 | 1514 | // И теперь можем пользоваться в рамках JavaFX 1515 | label.textProperty().bind(binding); 1516 | ``` 1517 | #### Operators 1518 | На случай, если вдруг кто столкнулся с тем, что `Transformer`'ов не хватает, есть `ObservableOperator` и `FlowableOperator`, с помощью которых можно запилить свои собственные операторы. 1519 | 1520 | Но автор всё-таки советует сначала изучить возможности из-под-капотной **RxJava**. По его мнению даже поход на Stackoverflow и изучение вопроса - лучше, чем костылить свой велосипед. Комьюнити у эрыксджавы достаточно себе большое и отзывчивое. 1521 | 1522 | > Обратите внимние, в `RxJava2Extensions` (David Karnok) и `RxJava2-Extras` (Dave Moten) есть крутые трансформеры и операторы. Посмотрите эти либы, прежде чем обеспечивать себе rx-геморрой. 1523 | 1524 | ##### Implementing an ObservableOperator 1525 | Автор столько раз советовал не делать этого, что я решил пропустить всё, что связано с созданием своих операторов. Если кто упарывается по таким вещам, смотрите источник, мужики, ну его в жопу. 1526 | 1527 | ##### FlowableOperator 1528 | /* пропущено */ 1529 | 1530 | #### Custom Transformers and operators for Singles, Maybes, and Completables 1531 | Тут идея в том, что всё то же самое есть и для `Single`, `Maybe` и `Completable`. Если вдруг у них чего-то не хватает, переведите источник в `toObservable()` или `toFlowable()`. Создание трансформеров тоже не сильно отличается, просто возвращаемый тип меняется в соотв. с требованиями. 1532 | 1533 | #### Using RxJava2-Extras and RxJava2Extensions 1534 | Если вы заинтересованы в том, чтобы добавить крутости стандартной эриксджаве, то рекомендуем ознакомиться с [RxJava2-Extras](https://github.com/davidmoten/rxjava2-extras) и [RxJava2Extensions](https://github.com/akarnokd/RxJava2Extensions). 1535 | 1536 | Вот, например,`toListWhile()` и `collectWhile()` - пара полезных операторов. Будут складывать в коллекцию элементы, соответствующие определённым условиям. 1537 | 1538 | Стоит потратить своё время на эти две либы, чтобы не изобретать то, что уже есть. Автору, вот например, нравится `cache()`, который может самоочищаться и переподписываться на эмишены. 1539 | 1540 | ----- 1541 | 1542 | ### Testing and Debugging 1543 | Тестирование реактивщины на первый взгляд может показаться непростым. Это так потому, что **RxJava** - про поведение, а не про состояние. И всё же с этим можно что-то поделать. Будем использовать **JUnit**. 1544 | 1545 | Также в этой главе будет выделено место для дебага. Он перестаёт быть таким однозначным при использовании эриксджавы: стэктрэйсы менее информативны и не совсем понятно, куда ставить брэйкпоинты. Но при правильном подходе всё будет норм. Появление проблем становится довольно линейным и их поиск не составляет большого труда. 1546 | 1547 | Чё будет: 1548 | 1549 | - `blockingSubscribe()` 1550 | - блокирующие операторы 1551 | - `TestObserver` и `TestSubdcriber` 1552 | - `TestScheduler` 1553 | - стратегии дебага 1554 | 1555 | #### Configuring JUnit 1556 | 1557 | /* Подозреваю, что подключить JUnit - не проблема, пропускаю */ 1558 | 1559 | #### Blocking subscribers 1560 | Почти во всех примерах в книге были использованы искусственные способы замораживания главного потока. Делалось это для того, чтобы rx-чейн успевал отработать до того, как приложуха завершит своё выполнение. 1561 | 1562 | В случае с юнит-тестами часто выходит так, что один тест должен выполниться прежде, чем стартанёт другой. Чтобы избежать путаницы с разными потоками, надо блокировать выполнение теста перед тем, как отработает `Observable` или `Flowable`. 1563 | 1564 | ```java 1565 | AtomicInteger hitCount = new AtomicInteger(); 1566 | 1567 | Observable source = Observable.interval(1, TimeUnit.SECONDS) 1568 | .take(5); 1569 | 1570 | source.blockingSubscribe(i -> hitcount.incrementAndGet()); 1571 | 1572 | assertTrue(hitcount.get() == 5); 1573 | ``` 1574 | 1575 | `blockingSubscribe()` - самый простой оператор для тестирования вашей реактивной логики. Однако, есть более элегантные инструменты. 1576 | 1577 | > Убедитесь, что ваш чейн не является бесконечным, прежде чем впиливать `blockingSubscribe()`. Также не рекомендуется использовать его в боевом коде - это может негативно сказаться на реактивности вашей приложухи. 1578 | 1579 | #### Blocking operators 1580 | Есть целый набор операторов, которые ещё не обсуждались. Так называемые _blocking operators_(блокирующие операторы) - прокси между реактивным миром и скучной императивщиной. Они блокируют выполнение thread'a до тех пор, пока не отработает rx-чейн. 1581 | 1582 | Использование блокирующих операторов в коде порождает анти-паттерны, так что атата. 1583 | 1584 | Для полноценного тестирования также понадобятся `TestSubscriber` и `TestObserver`, о которых будет сказано позже. 1585 | 1586 | ##### blockingFirst() 1587 | Используется вместо `subscribe()` или `blockingSubscribe()` и возвращает конкретный элемент `T`, а не `Observable`. Блокирует поток до тех пор, пока чейн не вернёт первый элемент. Бросает ошибку, если до него не докатилось ни одного эмишена. Аналогично работает и `blockingSingle()`. 1588 | 1589 | ##### blockingGet() 1590 | У `Maybe` и `Single` нет `blockingFirst()`, потому как они подразумевают 0 <= количество элементов <= 1. Логично, что подобный оператор будет возвращать _единственный_ элемент. Называется он `blockingGet()`. 1591 | 1592 | ##### blockingLast() 1593 | /* Д - дедукция */ 1594 | 1595 | ##### blockingIterable() 1596 | Интересный блокирующий оператор, который возвращает эмишены в виде `Iterable`. Итератор, предоставленный этим `Iterable` будет блокировать итерирующий поток до тех пор, пока не будет доступен следующий элемент. Итерация завершится `onComplete()`. 1597 | 1598 | ```java 1599 | Observable source = Observable.just("Vote", "for", "Grudinin"); 1600 | 1601 | Iterable allWithLengthFour = source.filter(s -> s.length() == 4) 1602 | .blockingIterable(); 1603 | 1604 | for (String s: allWithLengthFour) { 1605 | assertTrue(s.length() == 4); 1606 | } 1607 | ``` 1608 | 1609 | `blockingIterable()` поставит в очередь все полученные элементы до тех пор, пока `Iterator` не сможет их обработать. Может привести к `OutOfMemoryException`. 1610 | 1611 | Может быть полезным для работы с работы с джавовскими стримами(гляньте [RxJava2Jdk8Interop](https://github.com/akarnokd/RxJava2Jdk8Interop)) или котлиновскими _sequence_. 1612 | 1613 | ##### blockingForEach() 1614 | Блокирует поток, вызывающий чейн, для каждого элемента: он будет ждать, пока выстрелит каждый эмишн. Так, для предыдущего примера можно написать: 1615 | 1616 | ``` 1617 | source.filter(s -> s.length() == 4) 1618 | .blockingForEach(s -> assertTrue(s.length() == 4)); 1619 | ``` 1620 | 1621 | ##### blockingNext() 1622 | Вернёт `Iterable` и будет блочить метод `next()` у каждого запроса к итератору. Эмишены перед первым и после последнего вызовов `next()` будут проигнорированы. 1623 | 1624 | ##### blockingLatest() 1625 | Запрашивает последний выпущенный элемент. 1626 | 1627 | ##### blockingMostRecent() 1628 | Какая-то дичь ненужная. 1629 | 1630 | #### Using TestObserver and TestSubscriber 1631 | Настоящие rx-ниндзи используют всю силу реактивщины, даже в тестировании. Им недостаточно просто заблочить исполняющий поток до тех пор, пока не отработает чейн. Они хотят прописать логику для `onNext()`, `onError()`, `onComplete()`. 1632 | 1633 | Они познали `TestObserver` и `TestSubscriber` - их тестовые rx-сюрикены. Первый для `Observable` источников, второй - для `Flowable`. 1634 | 1635 | Методы `TestObserver`: 1636 | 1637 | Метод | Описание 1638 | -----|----- 1639 | `assertNotSubscribed()` | Проверка на отсутствие подписчиков 1640 | `subscribe()` | Обычный `subscribe()` 1641 | `assertSubscribed()` | Проверка на наличие подписчиков 1642 | `awaitTerminalEvent()` | Блокировка и ожидание, пока отработает `Observable` 1643 | `assertComplete()` | Отработал ли `onComplete()` 1644 | `assertNoErrors()` | Не было ли ошибок 1645 | `assertValueCount(N)` | Проверка на `количество полученных значений` == `N` 1646 | `assertValues(0L, 1L, 2L)` | Полученные значения: `OL`, `1L`, `2L` 1647 | 1648 | Это немногие методы из арсенала тестового обзёрвера. Большинство из них возвращает `TestObserver`, поэтому можно составлять целые тестовые чейны. 1649 | 1650 | > Использование `TestObserver` и `TestSubscriber` предпочтительнее использования блокирующих операторов - тестирование проходит с учётом жизненного цикла обзёрваблов и всего спектра ивентов, которые могут произойти в чейне. 1651 | 1652 | #### Manipulating time with the TestScheduler 1653 | Если у нас много чейнов, основанных на времени, то их тестирование с помощью блокирующих операторов может стать слишком долгим. `TestScheduler` придуман для того, чтобы симмулировать нужные временные отрезки. С его помощью можно как бы промотать вперёд выполнение чейна, чтобы узнать результат его работы. 1654 | 1655 | В примере ниже у нас есть обзёрвабл, который эмитит 90 элементов за 90 минут. Вместо того, чтобы ждать его выполнение полтора часа, мы используем необходимый тестовый обзёрвер: 1656 | 1657 | ```java 1658 | TestScheduler testScheduler = new TestScheduler(); 1659 | 1660 | TestObserver testObserver = new TestObserver<>(); 1661 | 1662 | // Скажем нашему обзёрваблу, чтобы он работал на правильном шедулере 1663 | Observable minuteTicker = Observable.interval(1, TimeUnit.MINUTES, 1664 | testScheduler); 1665 | 1666 | minuteTicker.subscribe(testObserver); 1667 | 1668 | // Мотанём шедулер вперёд 1669 | testScheduler.advanceTimeBy(90, TimeUnit.MINUTES); 1670 | 1671 | // Тест пройдёт нормально 1672 | testObserver.assertValueCount(90); 1673 | ``` 1674 | 1675 | Есть ещё такой оператор, как `advanceTimeTo()`, который прыгает к точному времени с момента подписки. 1676 | 1677 | Тестовый шедулер не является потокобезопасным, поэтому не стоит его юзать, когда вам нужна реальная многопоточность. При использовании каких-то сложных чейнов, которые работают там-сям, используйте `RxJavaPlugins.setComputationScheduler()` и аналогичные ему методы для того, чтобы инжектить нужный вам шедулер. 1678 | 1679 | #### Debugging RxJava code 1680 | Для дебага достаточно придерживаться определённого подхода, чтобы достаточно быстро и безболезненно находить косяки в чейнах. Используйте `doOnNext()` сразу после источника эмишенов, чтобы понимать, какие элементы были выпущены дельше в чейн, а какие привели к ошибке. Постепенное продвижение `doOnNext()` вниз по цепочке рано или поздно даст свои плоды, и вы нащупаете проблемный код. 1681 | 1682 | В IDEA брэйкпоинты отрабатывают и в лябмда-варажениях, поэтому никто не мешает дебажить ими. 1683 | 1684 | ----- 1685 | 1686 | ### RxJava on Adroid 1687 | Автор приводит достаточно простые примеры по использованию эрыксджавы в Android, поэтому решено пропустить эту главу, в ней ничего особо интересного нет. Разве что перечисление Rx-библиотек для андроида: 1688 | 1689 | - [SqlBrite](https://github.com/square/sqlbrite) - rx-враппер для SQL 1690 | - [RxLocation](https://github.com/patloew/RxLocation) - Reactive Location API 1691 | - [rx-preferences](https://github.com/f2prateek/rx-preferences) - Reactive Shared Preferences 1692 | - [RxFit](https://github.com/patloew/RxFit) - 0o 1693 | - [RxWear](https://github.com/patloew/RxWear) 1694 | - [ReactiveNetwork](https://github.com/pwittchen/ReactiveNetwork) - реактивно слушает сеть 1695 | - [ReactiveBeacons](https://github.com/pwittchen/ReactiveBeacons) - про **Bluetooth Low Energy**. 1696 | 1697 | Ну ещё автор предупреждает о том, что надо особенное внимение уделить контролю жизненного цикла обзёрваблов, высвобождать их и прочее. В `onPause()` - диспозить, подписываться заново в `onResume()`. Неплохо диспозить и в `onDestroy()` тоже. 1698 | 1699 | Не стоит забывать о мультикасте, когда нужно создать несколько слушателей для одного и того же источника. Делаешь много `Observer`'ов для одного и того же ивента - юзай мультикастинг. 1700 | 1701 | ----- 1702 | 1703 | ### Using RxJava for Kotlin New 1704 | /* Конпект этой главы откладывается до момента, когда я буду пересаживаться на котёл */ 1705 | -------------------------------------------------------------------------------- /improvement-thoughts: -------------------------------------------------------------------------------- 1 | @EmptyPublicConstructor // <- MVP 2 | @NewInstance(WalletViewHolder.WalletViewObject.class) // 2nd target 3 | public class WalletDetailsFragment extends BaseFragment implements DetailsView { 4 | 5 | private static final String WALLET = "WALLET"; 6 | @SuppressWarnings("WeakerAccess") 7 | @InjectPresenter 8 | DetailsPresenter presenter; 9 | private ViewGroup actionsContainer; 10 | private String currentCurrency; 11 | 12 | public WalletDetailsFragment() { // to avoid this (simply generate) 13 | // Empty constructor. 14 | } 15 | 16 | public static WalletDetailsFragment newInstance( // and this 17 | @NonNull WalletViewHolder.WalletViewObject wallet) { 18 | 19 | Bundle data = new Bundle(); 20 | data.putSerializable(WALLET, wallet); 21 | WalletDetailsFragment fragment = new WalletDetailsFragment(); 22 | fragment.setArguments(data); 23 | 24 | return fragment; 25 | } 26 | ... 27 | } 28 | -------------------------------------------------------------------------------- /learning-rxjava.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcd27/learning-rxjava-ru/6a269a7628acf42646e0d9436fc85ec0751851aa/learning-rxjava.jpg -------------------------------------------------------------------------------- /multicast-after-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcd27/learning-rxjava-ru/6a269a7628acf42646e0d9436fc85ec0751851aa/multicast-after-map.png -------------------------------------------------------------------------------- /multicast-before-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcd27/learning-rxjava-ru/6a269a7628acf42646e0d9436fc85ec0751851aa/multicast-before-map.png -------------------------------------------------------------------------------- /start-stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcd27/learning-rxjava-ru/6a269a7628acf42646e0d9436fc85ec0751851aa/start-stop.png --------------------------------------------------------------------------------