Использование наблюдаемого шаблона в Android
Когда я изучал потоки и реактивное программирование, я узнал о RxJava, но отложил его изучение, потому что это казалось сложным.
В конце концов я решил попробовать, создав приложение, в котором я легко нашел шаблон Observable, и поэтому я решил создать клон стандартного приложения часов Android.
Приложение
Я назвал проект «ClockClone
». Моя цель состояла в том, чтобы определить все варианты использования стандартного приложения часов, где шаблон наблюдателя был применим, а затем воссоздать его самостоятельно с помощью RxJava, чтобы я мог изучить его.
Чтобы все было просто и понятно, я не стал делать приложение полным клоном — не весь функционал был воспроизведен. Среди прочего, на самом деле не работают будильники и нет полноэкранных уведомлений.
Вариант использования
Основные функции приложения основаны на четырех вкладках на главном экране приложения «Часы»:
- Будильники — установка будильников и управление ими. Будет только постоянное хранение данных о тревогах, и никакие тревоги фактически не будут запланированы.
- Мировые часы — просмотр времени и погодных условий в крупных городах мира.
- Секундомер — запустить секундомер. Он также может показывать промежуточное время и время прохождения круга.
- Таймер — запуск простого таймера обратного отсчета.
Я буду обсуждать только те части приложения, которые относятся к RxJava.
Код
Весь код можно найти на Github здесь. Код в этой статье сокращен для краткости.
RxJava
RxJava — это Java-реализация ReactiveX API. Чтобы процитировать их домашнюю страницу:
ReactiveX — это сочетание лучших идей
шаблона Observer, шаблона Iterator и функционального программирования.
Все операции в ReactiveX вращаются вокруг использования Observables. Они функционируют аналогично Java Futures, но созданы для работы с последовательностями асинхронных значений, а не с отдельными.
Наблюдаемые объекты генерируют три основных типа событий: onNext, onComplete и onError. Последовательность Observable состоит из нуля или более событий onNext и завершается либо событием onComplete, либо событием onError, но не обоими одновременно.
ReactiveX поставляется с большим набором операторов, которые можно использовать для самых разных действий с Observables, таких как преобразования, фильтрация, комбинирование Observables, агрегатные операции и т. д. Я использовал только некоторые из них в приложении, о которых я расскажу ниже. .
Для приложения я использую RxJava 3, в частности версию 3.0.0
. Полный список версий можно найти на странице релизов на Github.
Предположения
Вы знакомы со следующим:
- Шаблон наблюдателя
- Модернизация
- Комната
- Архитектура приложения для Android
- Платформа параллелизма Java
- Кинжал
Используемые библиотеки
- RxJava
- RxAndroid
- Комната — включает адаптер RxJava
- Жизненный цикл — включает Reactive Streams для работы с RxJava.
- Ретрофит
- Дооснащение адаптера RxJava
Секундомер
Наблюдаемые данные, необходимые для секундомера, включают количество времени, прошедшее в миллисекундах с момента запуска — время выполнения, количество времени, прошедшее в миллисекундах с тех пор, как пользователь в последний раз щелкнул «круг» — время круга, состояние (OFF
, RUNNING
, PAUSED
) — в основном для пользовательского интерфейса и уведомлений, а также для списка времени разделения и круга, который будет обновляться каждый раз, когда нажимается «разделение» или «круг». Придумав это, я обнаружил, что для ручной генерации событий необходим особый вид Observable.
Предметы
Субъекты выступают как наблюдатели данных и как Observables одновременно. Предметы бывают четырех видов:
- PublishSubject: отправляет значения в Observer с точки подписки. Все значения, отправленные до подписки, никогда не будут получены.
- ReplaySubject: воспроизводит поток с самого начала, а также выдает новые значения.
- BehaviorSubject: то же, что и PublishSubject, но выдает самое последнее значение до того, как была сделана подписка. Хороший пример — поток с начальным состоянием.
- AsyncSubject: отправляет в Observer только последнее значение. Это значение генерируется только тогда, когда субъект вызывает
onComplete()
.
Я выбрал BehaviorSubject, чтобы пользовательский интерфейс мог определить последнее состояние Observable при его перезапуске из фона или при изменении конфигурации.
Чтобы отслеживать время и отправлять обновления через фиксированные промежутки времени, я использовал ScheduledThreadPoolExecutor. Это лучший вариант, чем обработчик, поскольку обработчики выполняются в основном (UI) потоке.
Таймер
Таймер работает очень похоже на секундомер. Наблюдаемые данные включают оставшееся время в миллисекундах и состояние (OFF
, COUNTING
или PAUSED
). Я также использовал BehaviorSubject и ScheduledThreadPoolExecutor.
Мировое время
Главной дополнительной функцией Мировых часов является возможность видеть текущее время во всех крупных городах мира. Вторая подфункция — это возможность видеть сводные текущие погодные условия этих городов.
Наблюдаемые данные включают таймер для обновления отображаемого текущего времени, погодных условий и списка городов. Отмечу, что у меня были трудности с работой над World Clock — я продолжал менять структуру кода, пока не нашел структуру классов, которая работала хорошо.
Центральным элементом данных для этой функции является идентификатор часового пояса. Это выглядит так:
Africa/Lagos
Его можно использовать для получения текущего времени в крупных городах, а также погодных условий. Я использовал данные из TZDB для создания списка часовых поясов и сохранил его в ресурсах приложения в виде CSV-файла.
Текущее время получается из DateFormat
, который использует идентификатор часового пояса, чтобы отформатировать его соответствующим образом для пользовательского интерфейса. Таймер срабатывает каждую минуту в нулевую секунду, чтобы запустить обновление пользовательского интерфейса.
Погодные условия получаются из сетевых запросов, о которых я вскоре расскажу. Список городов, сохраненных пользователем, хранится в виде простой строки, разделенной запятыми.
Я использовал SharedPreferences
, потому что он может сигнализировать наблюдателям всякий раз, когда было сделано изменение, и потому что база данных была бы излишней.
Погодные условия
Я решил использовать AccuWeather, чтобы иметь возможность работать с Retrofit и опробовать адаптер вызовов RxJava.
Создание клиентского интерфейса с адаптером выполняется вызовом addCallAdapterFactory()
объекта Builder
.
Планировщик может быть опционально настроен для использования адаптером. Планировщики используются для многопоточности. Проверьте здесь для получения дополнительной информации.
Наблюдаемые за одним событием
Стандартный Observable создан для работы с последовательностями асинхронных задач, но сетевой запрос — это всего лишь одна задача. Хотя можно использовать Observable с Retrofit, в RxJava есть специальные типы, созданные для обработки разовых событий.
- Одиночный: излучает либо
onSuccess
, либоonError
. onSuccess выдается с одним элементом. - Completable: выдает либо
onCompleted
, либоonError
, но ни один элемент не выдается с onCompleted. - Возможно: любое из событий
onSuccess
,onComplete
илиonError
будет сгенерировано.onSuccess
есть один элемент.
Отмена подписки
Работая над погодными условиями, я понял, что важно иметь возможность отменять сетевые запросы, если приложение закрывается до их завершения.
Сетевые запросы являются потенциальным источником утечек памяти, что, в свою очередь, может привести к сбою. В ReactiveX Subscribers — это специальные типы, представляющие подписку на Observable, но в RxJava они известны как Disposables.
Disposables будут использоваться для отмены запросов при необходимости. У них есть метод dispose()
, который отменяет подписку на Observables.
Observable возвращает объект Disposable через вызов subscribe()
. CompositeDisposables
помогают избавиться от набора Disposables одновременно, аналогично операции forEach для стандартной коллекции.
Обновлять каждую минуту
Наблюдаемый интервал генерирует событие с бесполезным элементом каждую минуту, чтобы сигнализировать пользовательскому интерфейсу об обновлении текущего времени. Чтобы обновлять список каждую минуту, я использовал оператор combineLatest()
.
будильники
Я решил использовать базу данных для отслеживания аварийных сигналов с целью их просмотра, изменения и удаления, и поэтому использовал Room с адаптером RxJava.
Из этой статьи я узнал, что Observable, Maybe, Single и Completable можно использовать в качестве возвращаемых типов для метода Room DAO. Кроме того, можно использовать Flowable
.
Flowable — это Observable с поддержкой противодавления, когда Observable отправляет события быстрее, чем Observer может их обработать. Observable можно преобразовать в Flowable, используя toFlowable()
с BackpressureStrategy в качестве параметра. Типы одиночных событий, такие как Может быть и Один раз, не используют стратегию в качестве параметра конверсии. Я использую только BackpressureStrategy.BUFFER
в своем коде. Перейдите по ссылкам, чтобы узнать больше о Противодавлении и Растекающихся материалах.
Преобразование в LiveData
Все типы Observable могут быть преобразованы в LiveData, чтобы их можно было использовать с ViewModels. Их нужно преобразовать в Flowable, с помощью которого они передаются методу LiveDataReactiveStreams.fromPublisher()
.
Одна вещь, которую я не реализовал в LiveData, — это обработка ошибок. Событие ошибки от Observable приведет к тому, что LiveData вызовет исключение RuntimeException, что приведет к сбою приложения.
Ожидается, что вы преобразуете свои возможные ошибки в значимые объекты состояния пользовательского интерфейса, прежде чем передавать их в LiveData. Посмотрите эту реализацию из примера Google.
Заключение
У меня есть некоторые мысли о RxJava. Это мощная библиотека с большим потенциалом, и мне кажется, что я едва касался поверхности для ClockClone
. Тем не менее, я научился использовать его с пользой для шаблона Observable на Android.
Мне также нравится тот факт, что он не является эксклюзивным для разработки Android, поэтому он может пригодиться в других областях. Я рад, что наконец научился этому. Надеюсь, вы тоже чему-то научились.