Реактивное программирование — один из общих принципом программирования, о котором большинство разработчиков, вероятно, слышит в тот или иной момент времени. Хотя ресурсы для реализации разнятся для разных языков (RxJava, RxAndroid, RxJS, RxSwift), философия остается той же.
Реактивное программирование — это парадигма, ориентированная на данные, имеющая дело с распространением изменений.
Другими словами: «Реактивное программирование — это программирование с использованием асинхронных потоков данных». То есть, когда вы назначаете x = y + z, и когда значения y и z меняются, это должно автоматически отражаться на x. Это можно сделать, наблюдая за значениями y и z.
Реактивное программирование с RxJava/RxAndroid
RxJava — это реализация принципов реактивного программирования для создания асинхронных программ, основанных на событиях, путем наблюдения за потоками/последовательностями. RxAndroid — это оболочка на RxJava, предоставляющая больше служебных классов, специфичных для Android.
В большинстве приложений должна происходить какая-то операция в фоновом режиме (например, сетевые вызовы) на основе некоторых интерактивных или неинтерактивных событий или для отображения последних данных пользователю на основе взаимодействия.
На Android мы можем делать это разными способами:
- AsyncTasks (способ взаимодействия с фоновым потоком и основным потоком пользовательского интерфейса в Android)
- Broadcast/Receiver (обычно используется для длительных фоновых процессов)
- EventBus (отправка уведомления любому классу, который подписался на прослушивание определенного вида событий)
Есть еще несколько способов сделать это, но все они имеют некоторые проблемы:
- накопление функций в конечном итоге превращает код в неуправляемый беспорядок
- написание тестовых примеров становится утомительным
- нужна обработка потоков для вычислений
Более того, при различных бизнес-требованиях объединение нескольких источников данных в один для получения необходимых унифицированных данных становится более сложной задачей. У нас есть экраны, подпитываемые несколькими источниками данных, в которых необходимо получать уведомления и обновлять их каждый раз, когда происходит обновление в отдельном потоке данных.
Здесь слишком много вариантов и выбора, но ни один из них не является оптимальным.
Реактивность в помощь
С RxAndroid мы переместили все наши источники данных в потоки. Это помогло нам сократить наш шаблонный код, улучшить поддерживаемость и повысить тестируемость нашего приложения. Как?
В библиотеках Rx каждый источник данных представляет собой поток. Используя нашу Dependency Injection (Dagger для Android), мы используем тот же репозиторий. Таким образом, когда данные обновляются, все экраны наблюдения получают обновление без каких-либо проблем.
Стриминг — это дзен!
Углубленный Rx
Подписки
Observable (поток, наблюдаемое) — это то, на что вы подписываетесь, чтобы слушать генерируемые события, но он, естественно, бесполезен, пока вы не подпишетесь на него.
После того, как вы подписались на Observable, вы получаете Subscription (подписку). Подписка будет прослушивать элементы из Наблюдаемого , пока не получит пометку “Complete”.
Observable.range(2,5) .subscribe ({ println(onNext:$it) }, { print(onError:it.localizedMessage) })
Удаление после завершения
С большой силой приходит большая ответственность, — Человек-паук
Хотя Observable могут быть мощным инструментом для разработчиков, они также создают возможность возникновения утечек памяти, если мы продолжаем наблюдать за потоком после того, как область его родительского объекта уничтожена. Вот почему с помощью Rx каждый Observable возвращает объект Disposable, который мы должны удалить, когда объект, содержащий наблюдаемый объект, уничтожен.
С помощью RxAndroid вы можете добавить Observable к одноразовому пакету/контейнеру, который поддерживает подписку активной до тех пор, пока поток не будет удален или завершен.
Мы используем библиотеку Uber AutoDisposable, которая информирует подписчика о жизненном цикле Android и автоматически удаляет подписку, когда она больше не нужна.
Переключение между потоками
Еще одна положительная сторона RxAndroid заключается в том, что вы можете подписаться на стримы в другом фоновом потоке, но наблюдать и выполнять последующие операции в основном потоке пользовательского интерфейса.
RxJava предоставляет несколько готовых реализаций пула потоков.
- io() — для выполнения любых операций ввода-вывода, таких как база данных и сеть
- computation() — для выполнения любой интенсивной вычислительной работы
RxAndroid расширяет эту функциональность до определенной реализации UI-потока в Android.
- AndroidSchedulers.mainThread() — поток пользовательского интерфейса для обновления компонентов пользовательского интерфейса
Observable.range(2,5) .observeOn(Schedulers.computation()) .subscribeOn(AndroidSchedulers.mainThread()) .subscribe ({ println("onNext:$it") }, { print(it.localizedMessage) })
Объяснение кода:
- .subscribeOn(Schedulers.io()): заставляет Observable ожидать и осуществлять вычисления в ThreadPool, который предназначен для ввода-вывода (Schedulers.io()).
- .observeOn(AndroidSchedulers.mainThread()): заставляет подписчика выполнять свой результат в основном потоке пользовательского интерфейса Android. Это необходимо, если кто-то хочет что-либо изменить в пользовательском интерфейсе Android.
- Второй аргумент .subscribe() — вводит обработчик ошибок, который почти всегда должен присутствовать.
Есть много других сложных комбинаций, которых мы легко можем достичь с помощью RxJava:
- Объединение нескольких потоков данных в цепочку (Observable.map (…,…,))
- Объединение двух или более источников данных (Observable.merge (…,…,))
- Сглаживание и форматирование унифицированных данных (Observable.flatMap {})
- Объединение двух или более источников данных и получение последних данных от их объединения. (Observable.combineLatest (…,…,))
Если вы хотите визуализировать, как реактивность работает с простой функцией, посмотрите это.
Rx дает безграничные возможности. Чем больше мы узнаем, тем больше мы открываем новых способов уменьшить наш boilerplate код и сделать его читабельным и тестируемым. Он не привязан к языку или платформе.
Советы
Несколько советов, которые могут быть полезны (ошибки, которые мы пережили в первое время).
1. Всегда используйте обработчик ошибок
Если вы не обработали ошибку при подписке, вы можете столкнуться с трудностями при трассировки стека. Существуют дополнительные библиотеки, которые помогут вам в этом, но это просто хорошая практика — обработка ошибок.
2. Управление жизненным циклом
Всегда привязывайте Observable к Активити или Фрагментам. Часто вы будете обнаруживать, что ваша подписка на наблюдаемое длится и дальше в фоновом потоке, даже если пользователь уже покинул связанную Активити/Фрагмент. Вот почему рекомендуется избавиться от Observable, когда Активити/Фрагмент завершены, или использовать библиотеку AutoDispose от Uber, чтобы связать наблюдение с областью действия Активити или Фрагмента.
Реактивное программирование: будущее в Android
- Мы изучаем потоки Kotlin Coroutine и потоки Coroutine.
- Однако для более сложной бизнес-логики мы по-прежнему используем RxJava из-за простоты его использования и поддержки кода. У реактивного программирования экспоненциальная кривая обучения, но по мере того, как вы овладеете им, сложные проблемы можно будет решать легче и быстрее.