Это репозиторий с реальными полезными примерами использования RxJava с Android. Обычно он находится в постоянном состоянии «В разработке» (WIP).
Я также рассказывал об Learning Rx, используя многие примеры, перечисленные в этом репозитории.
using
)Общим требованием является выгрузка длительных тяжелых операций ввода-вывода в фоновый поток (поток, не относящийся к пользовательскому интерфейсу) и по завершении передача результатов обратно в пользовательский/основной поток. Это демонстрация того, как можно выгрузить длительные операции в фоновый поток. После завершения операции мы возобновляем работу в основном потоке. Все используют RxJava! Думайте об этом как о замене AsyncTasks.
Длительная операция моделируется блокирующим вызовом Thread.sleep (поскольку это делается в фоновом потоке, наш пользовательский интерфейс никогда не прерывается).
Чтобы по-настоящему увидеть этот пример сияющим. Нажмите кнопку несколько раз и посмотрите, как нажатие кнопки (операция пользовательского интерфейса) никогда не блокируется, поскольку длительная операция выполняется только в фоновом режиме.
Это демонстрация того, как события могут накапливаться с помощью операции «буфер».
Предоставляется кнопка, и мы накапливаем количество кликов по этой кнопке за определенный промежуток времени, а затем выдаем окончательные результаты.
Если вы нажмете кнопку один раз, вы получите сообщение о том, что кнопка была нажата один раз. Если вы нажмете на нее 5 раз подряд в течение 2 секунд, вы получите один журнал, в котором будет указано, что вы нажали эту кнопку 5 раз (против 5 отдельных журналов, в которых написано «Нажатие кнопки один раз»).
Примечание:
Если вы ищете более надежное решение, которое накапливает «непрерывные» нажатия, а не просто количество нажатий за определенный промежуток времени, посмотрите демонстрацию EventBus, где используется комбинация операторов publish
и buffer
. Для более подробного объяснения вы также можете просмотреть эту публикацию в блоге.
Это демонстрация того, как можно проглотить события так, чтобы учитывалось только последнее из них. Типичным примером этого являются окна результатов мгновенного поиска. Когда вы вводите слово «Брюс Ли», вы не хотите выполнять поиск по B, Br, Bru, Брюс, Брюс, Брюс L ... и т. д. Вместо этого разумно подождите пару минут, убедитесь, что пользователь закончил набирать слово целиком, а затем произнес один-единственный призыв «Брюс Ли».
Когда вы вводите текст в поле ввода, он не будет выдавать сообщения журнала при каждом изменении входного символа, а скорее выберет последнее созданное событие (т. е. ввод) и зарегистрирует его.
Это метод debounce/throttleWithTimeout в RxJava.
Retrofit от Square — это замечательная библиотека, которая упрощает работу в сети (даже если вы еще не перешли на RxJava, вам действительно стоит ее попробовать). С RxJava он работает еще лучше, и это примеры использования API GitHub, взятые прямо из выступления полубога-разработчика Android Джейка Уортона на Netflix. Посмотреть выступление можно по этой ссылке. Кстати, моей мотивацией использовать RxJava стало посещение этого доклада на Netflix.
(Примечание: вы, скорее всего, довольно быстро достигнете квоты API GitHub, поэтому отправьте токен OAuth в качестве параметра, если вы хотите часто запускать эти примеры).
Автоматическое обновление представлений — это очень крутая штука. Если вы раньше имели дело с Angular JS, то у них есть довольно изящная концепция под названием «двусторонняя привязка данных», поэтому, когда элемент HTML привязан к объекту модели/сущности, он постоянно «слушает» изменения в этой сущности и автоматически обновляет свое состояние на основе модели. Используя технику, описанную в этом примере, вы потенциально можете с легкостью использовать такой шаблон, как модель представления представления.
Хотя приведенный здесь пример довольно элементарный, техника, используемая для достижения двойной привязки с использованием Publish Subject
гораздо более интересна.
Это пример опроса с использованием планировщиков RxJava. Это полезно в тех случаях, когда вы хотите постоянно опрашивать сервер и, возможно, получать новые данные. Сетевой вызов «моделируется», поэтому он вызывает задержку перед возвратом результирующей строки.
Для этого есть два варианта:
Второй пример, по сути, представляет собой вариант экспоненциальной отсрочки.
Вместо использования RetryWithDelay здесь мы используем RepeatWithDelay. Чтобы понять разницу между «Повторить (Когда)» и «Повторить (Когда)», я бы предложил фантастический пост Дэна на эту тему.
Альтернативный подход к отложенному опросу без использования repeatWhen
заключается в использовании цепочек вложенных наблюдаемых задержки. См. startExecutingWithExponentialBackoffDelay в примере ExponentialBackOffFragment.
Экспоненциальная отсрочка — это стратегия, при которой на основе обратной связи от определенного результата мы изменяем скорость процесса (обычно уменьшая количество повторов или увеличивая время ожидания перед повторной попыткой или повторным выполнением определенного процесса).
Концепция становится более понятной на примерах. RxJava делает (относительно) простой реализацию такой стратегии. Спасибо Майку за идею.
Допустим, у вас сбой в сети. Разумной стратегией было бы НЕ повторять сетевой вызов каждую секунду. Вместо этого было бы разумно (нет... элегантно!) повторить попытку с увеличивающейся задержкой. Итак, вы пытаетесь в секунду 1 выполнить сетевой вызов, не так ли? попробуйте через 10 секунд... отрицательно? попробуйте через 20 секунд, куки нет? попробуйте через 1 минуту. Если эта штука по-прежнему не работает, вам придется отказаться от сети, йо!
Мы моделируем это поведение с помощью RxJava с оператором retryWhen
.
Фрагмент кода RetryWithDelay
:
Также обратите внимание на пример опроса, где мы используем очень похожий механизм экспоненциального отката.
Другой вариант стратегии экспоненциальной отсрочки — выполнение операции заданное количество раз, но с интервалами с задержкой. Таким образом, вы выполняете определенную операцию через 1 секунду, затем выполняете ее снова через 10 секунд, затем выполняете операцию через 20 секунд. После 3-х раз вы прекращаете выполнение.
Имитировать такое поведение на самом деле намного проще, чем предыдущий механизм повтора. Для этого вы можете использовать вариант оператора delay
.
.combineLatest
).Спасибо Дэну Лью за то, что он подал мне эту идею во фрагментированном подкасте — эпизод №4 (около отметки 4:30).
.combineLatest
позволяет вам компактно отслеживать состояние нескольких наблюдаемых одновременно в одном месте. В продемонстрированном примере показано, как можно использовать .combineLatest
для проверки базовой формы. Чтобы эта форма считалась «действительной», необходимо ввести 3 основных параметра (адрес электронной почты, пароль и номер). Форма станет действительной (текст ниже станет синим :P), как только все введенные данные станут действительными. Если это не так, отображается ошибка относительно недопустимых входных данных.
У нас есть 3 независимых наблюдаемых, которые отслеживают изменения текста/ввода для каждого поля формы (RxAndroid WidgetObservable
пригодится для отслеживания изменений текста). После того, как изменение события замечено со всех трех входных данных, результат «объединяется» и форма оценивается на достоверность.
Обратите внимание, что функция Func3
, проверяющая достоверность, срабатывает только после того, как ВСЕ 3 входа получили событие изменения текста.
Ценность этого метода становится более очевидной, когда в форме увеличивается количество полей ввода. Обработка его в противном случае с помощью набора логических значений делает код загроможденным и трудным для понимания. Но при использовании .combineLatest
вся эта логика концентрируется в красивом компактном блоке кода (я все еще использую логические значения, но это должно было сделать пример более читабельным).
У нас есть два исходных Observable: дисковый (быстрый) кеш и сетевой (свежий) вызов. Обычно дисковый Observable работает намного быстрее, чем сетевой Observable. Но чтобы продемонстрировать работу, мы также использовали поддельный «более медленный» дисковый кэш, просто чтобы посмотреть, как ведут себя операторы.
Это демонстрируется с помощью 4 методик:
.concat
.concatEager
.merge
.publish
селектор + слияние + takeUntilВероятно, в конечном итоге вы захотите использовать 4-ю технику, но интересно пройтись по развитию техник и понять, почему.
concat
это здорово. Он извлекает информацию из первого Observable (в нашем случае из дискового кеша), а затем из последующего сетевого Observable. Поскольку дисковый кеш предположительно работает быстрее, все выглядит хорошо, дисковый кеш загружается быстро, и как только сетевой вызов завершается, мы заменяем «свежие» результаты.
Проблема с concat
заключается в том, что последующий наблюдаемый даже не запускается до тех пор, пока не завершится первый наблюдаемый. Это может быть проблемой. Мы хотим, чтобы все наблюдаемые запускались одновременно, но давали ожидаемые результаты. К счастью, RxJava представила concatEager
, который делает именно это. Он запускает оба наблюдаемых, но буферизует результат последнего до тех пор, пока не завершится первый Observable. Это вполне рабочий вариант.
Однако иногда вам просто хочется немедленно начать показывать результаты. Если предположить, что первому наблюдаемому (по какой-то странной причине) требуется очень много времени для прохождения всех своих элементов, даже если первые несколько элементов из второго наблюдаемого перешли по сети, они будут принудительно поставлены в очередь. Вы не обязательно хотите «ждать» любого Observable. В таких ситуациях мы могли бы использовать оператор merge
. Он чередует элементы по мере их отправки. Это прекрасно работает и начинает выдавать результаты, как только они появляются.
Как и в случае с оператором concat
, если ваш первый Observable всегда быстрее второго Observable, у вас не возникнет никаких проблем. Однако проблема со merge
заключается в следующем: если по какой-то странной причине элемент выдается из кеша или более медленного наблюдаемого после более нового/свежего наблюдаемого, он перезапишет более новый контент. Нажмите кнопку «ОБЪЕДИНИТЬ (МЕДЛЕННЫЙ ДИСК)» в примере, чтобы увидеть эту проблему в действии. Взносы @JakeWharton и @swankjesse равны 0! В реальном мире это может быть плохо, поскольку это будет означать, что свежие данные будут заменены устаревшими данными на диске.
Чтобы решить эту проблему, вы можете использовать слияние в сочетании с очень изящным оператором publish
, который принимает «селектор». Я написал об этом использовании в своем блоге, но хочу поблагодарить джедая Дж. У. за напоминание об этой технике. Мы publish
наблюдаемый объект сети и предоставляем ему селектор, который начинает генерировать данные из дискового кэша до тех пор, пока наблюдаемый объект сети не начнет генерировать данные. Как только наблюдаемый сетевой объект начинает излучать, он игнорирует все результаты наблюдаемого на диске. Это идеально и решает любые проблемы, которые могут у нас возникнуть.
Раньше я использовал оператор merge
, но решил проблему перезаписи результатов, отслеживая «resultAge». Если вам интересно увидеть эту старую реализацию, посмотрите старый пример PseudoCacheMergeFragment
.
Это очень простой и понятный пример, который показывает, как использовать операторы timer
, interval
и delay
RxJava для обработки множества случаев, когда вы хотите запускать задачу через определенные промежутки времени. По сути, скажите НЕТ Android TimerTask
.
Кейсы, продемонстрированные здесь:
Есть сопутствующие сообщения в блоге, которые гораздо лучше объясняют детали этой демонстрации:
Распространенный вопрос, который задают при использовании RxJava в Android: «Как мне возобновить работу наблюдаемого, если происходит изменение конфигурации (ротация активности, изменение языкового стандарта и т. д.)?».
В этом примере показана одна стратегия, а именно. используя сохраненные фрагменты. Я начал использовать сохраненные фрагменты как «рабочие фрагменты» после прочтения этой фантастической статьи Алекса Локвуда некоторое время назад.
Нажмите кнопку «Пуск» и поверните экран, как душе угодно; вы увидите, что наблюдаемое продолжается с того места, где оно остановилось.
Есть определенные особенности «горячего» источника, наблюдаемого в этом примере. Посмотрите мой пост в блоге, где я объясняю особенности.
С тех пор я переписал этот пример, используя альтернативный подход. Хотя подход ConnectedObservable
работал, он входит в сферу «многоадресной рассылки», которая может быть сложной (потокобезопасность, .refcount и т. д.). С другой стороны, предметы гораздо проще. Вы можете увидеть его переписанным с использованием Subject
здесь.
Я написал еще одну публикацию в блоге о том, как думать о предметах, где я углубляюсь в некоторые подробности.
Volley — еще одна сетевая библиотека, представленная Google на конференции IO '13. Добрый гражданин GitHub предоставил этот пример, чтобы мы знали, как интегрировать Volley с RxJava.
Здесь я использую простое использование темы. Честно говоря, если ваши элементы еще не передаются через Observable
(например, через Retrofit или сетевой запрос), нет веской причины использовать Rx и усложнять ситуацию.
В этом примере номер страницы в основном отправляется теме, а тема обрабатывает добавление элементов. Обратите внимание на использование concatMap
и возврат Observable<List>
из _itemsFromNetworkCall
.
Для интереса я также включил пример PaginationAutoFragment
, который «автоматически разбивается на страницы» без необходимости нажимать кнопку. Если вы поняли, как работает предыдущий пример, вам будет легко следовать этому примеру.
Вот еще несколько причудливых реализаций (хотя мне понравилось их читать, я не стал использовать их для своего реального приложения, потому что лично я не думаю, что это необходимо):
Приведенная ниже ascii-диаграмма с размахом выражает цель нашего следующего примера. f1,f2,f3,f4,f5 — это, по сути, сетевые вызовы, которые при выполнении возвращают результат, необходимый для будущих вычислений.
(flatmap)
f1 ___________________ f3 _______
(flatmap) | (zip)
f2 ___________________ f4 _______| ___________ final output
|
____________ f5 _______|
Код для этого примера уже написал некий Mr.skehlet в инете. Перейдем к сути кода. Он написан на чистом Java (6), поэтому вполне понятен, если вы поняли предыдущие примеры. Я выложу это здесь снова, когда позволит время или у меня закончатся другие убедительные примеры.
Это простой пример, демонстрирующий использование оператора .timeout
. Кнопка 1 завершит задачу до истечения ограничения по времени ожидания, а кнопка 2 вызовет ошибку времени ожидания.
Обратите внимание, как мы можем предоставить собственный Observable, который указывает, как реагировать на исключение тайм-аута.
using
) using
оператор относительно менее известен и, как известно, сложен для Google. Это красивый API, который помогает настроить (дорогостоящий) ресурс, использовать его, а затем аккуратно утилизировать.
Преимущество этого оператора заключается в том, что он предоставляет механизм для использования потенциально дорогостоящих ресурсов в строго ограниченной области. с помощью -> настройка, использование и удаление. Подумайте о соединениях с БД (например, экземплярах Realm), соединениях сокетов, блокировках потоков и т. д.
Многоадресная рассылка в Rx похожа на темное искусство. Не так уж много людей знают, как справиться с этим без беспокойства. Этот пример объединяет двух подписчиков (в виде кнопок) и позволяет вам добавлять/удалять подписчиков в разные моменты времени и видеть, как разные операторы ведут себя в этих обстоятельствах.
Исходное наблюдение — это наблюдаемое по таймеру ( interval
), и причина, по которой он был выбран, заключалась в том, чтобы намеренно выбрать непрерывную наблюдаемую, чтобы вы могли проверить/подтвердить, произойдет ли утечка в вашем эксперименте с многоадресной рассылкой.
Я также подробно рассказал о многоадресной передаче на 360 | Andev. Если у вас есть желание и время, я настоятельно рекомендую сначала посмотреть этот доклад (в частности, сегмент перестановки операторов многоадресной рассылки), а затем возиться с приведенным здесь примером.
Все приведенные здесь примеры были перенесены для использования RxJava 2.X.
В некоторых случаях мы используем библиотеку Interop Дэвида Карнока, поскольку некоторые библиотеки, такие как RxBindings, RxRelays, RxJava-Math и т. д., еще не портированы на 2.x.
Я стараюсь, чтобы примеры не были слишком надуманными, а отражали реальный вариант использования. Если у вас есть похожие полезные примеры, демонстрирующие использование RxJava, смело отправляйте запрос на включение.
Я тоже занимаюсь RxJava, поэтому, если вы чувствуете, что есть лучший способ выполнить один из упомянутых выше примеров, откройте проблему, объясняющую, как это сделать. Еще лучше, отправьте запрос на включение.
Потоки Rx — грязное дело. Чтобы помочь, этот проект использует инструменты YourKit для анализа.
YourKit поддерживает проекты с открытым исходным кодом с помощью инновационных и интеллектуальных инструментов для мониторинга и профилирования приложений Java. YourKit является создателем профилировщика Java YourKit.
Лицензия Apache версии 2.0 («Лицензия»). Вы можете получить копию Лицензии по адресу:
http://www.apache.org/licenses/LICENSE-2.0
Если это не требуется действующим законодательством или не согласовано в письменной форме, программное обеспечение, распространяемое по Лицензии, распространяется на условиях «КАК ЕСТЬ», БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ ИЛИ УСЛОВИЙ, явных или подразумеваемых. См. Лицензию для определения конкретного языка, регулирующего разрешения и ограничения в рамках Лицензии.
Вы соглашаетесь, что все материалы, вносимые в этот репозиторий в виде исправлений, запросов на включение, новых примеров и т. д., подчиняются вышеупомянутой лицензии.