Лучшие практики BoundService + LiveData + ViewModel в новой рекомендованной архитектуре Android

Я много думал о том, где разместить службы Android в новой архитектуре, рекомендованной Android. Я придумал много возможных решений, но не могу определиться, какое из них лучше.

Я провел много исследований и не смог найти ни полезного руководства, ни учебника. Единственный совет, который я нашел о том, где разместить Службу в архитектуре моего приложения, - это от @JoseAlcerreca Средний пост

В идеале ViewModels не должны ничего знать об Android. Это улучшает тестируемость, безопасность от утечек и модульность. Общее практическое правило - убедиться, что в ваших ViewModels нет импорта android. * (С такими исключениями, как android.arch. *). То же самое и с ведущими.

В соответствии с этим я должен разместить свои службы Android в верхней части иерархии компонентов архитектуры, на том же уровне, что и мои действия и фрагменты. Это связано с тем, что службы Android являются частью платформы Android, поэтому ViewModels не должны о них знать.

Теперь я кратко объясню свой сценарий, но только для того, чтобы сделать панораму более четкой, а не потому, что мне нужен ответ для этого конкретного сценария.

  • У меня есть приложение для Android, в котором есть MainActivity с множеством фрагментов, все они связаны вместе в BottomNavBar.
  • У меня есть BluetoothService, привязанный к myActivity и одному из его фрагментов (потому что я хочу, чтобы у этой службы был тот же жизненный цикл, что и у Activty, но я также хочу взаимодействовать с ней непосредственно из моего фрагмента).
  • The fragment interacts with the BluetoothService to get two types of information:
    • Information about the state of the Bluetooth connection. Doesn't need to be persisted.
    • Данные, поступающие с устройства Bluetooth (в данном случае это весы, то есть вес и состав тела). Необходимо настойчиво.

Вот 3 разные архитектуры, о которых я могу думать:

LiveData внутри AndroidService  LiveData внутри Android Service Arch

  • LiveData с состоянием подключения и измерениями веса, поступающими с устройства Bluetooth, находятся внутри службы Bluetooth.
  • Фрагмент может запускать операции в BluetoothService (например, scanDevices)
  • Фрагмент наблюдает за LiveData о состоянии подключения и соответствующим образом адаптирует пользовательский интерфейс (например, активирует кнопку, если состояние подключено).
  • Фрагмент наблюдает за LiveData новых измерений веса. Если новое измерение веса поступает от BluetoothDevice, Fragment затем сообщает своей собственной ViewModel о необходимости сохранения новых данных. Это делается через класс репозитория.

Общая модель просмотра между фрагментом и AndroidService  Общая арка модели просмотра

  • Фрагмент может запускать операции в BluetoothService (например, scanDevices)
  • BluetoothService обновляет связанные с Bluetooth LiveData в общей ViewModel.
  • Фрагмент наблюдает за LiveData в своей собственной ViewModel.

Модель просмотра службы  Сервис ViewMOdel arch

  • Фрагмент может запускать операции в BluetoothService (например, scanDevices)
  • BluetoothService обновляет связанные с Bluetooth LiveData в своей собственной ViewModel.
  • Фрагмент наблюдает за LiveData в своей собственной ViewModel и ViewModel BluetoothService.

Я почти уверен, что мне следует разместить их поверх архитектуры и рассматривать их так же, как Activity / Fragment, потому что BoundServices являются частью Android Framework, они управляются ОС Android и привязаны к другим Activity и фрагментам. В этом случае я не знаю, как лучше всего взаимодействовать с LiveData, ViewModels и Activity / Fragments.

Кто-то может подумать, что их следует рассматривать как источник данных (поскольку в моем случае он получает данные со шкалы с помощью Bluetooth), но я не думаю, что это хорошая идея, из-за всего того, что я сказал в предыдущем абзаце. и особенно из-за того, что здесь написано:

Избегайте обозначения точек входа вашего приложения, таких как действия, службы и приемники широковещательных сообщений, в качестве источников данных. Вместо этого они должны координировать свои действия только с другими компонентами для получения подмножества данных, относящихся к этой точке входа. Каждый компонент приложения довольно недолговечен, в зависимости от взаимодействия пользователя со своим устройством и общего текущего состояния системы.

Итак, наконец, мой вопрос:

Где мы должны разместить наши (привязанные) службы Android и как они связаны с другими архитектурными компонентами? Какой из этих вариантов - хороший подход?


comment
Вы можете считать свою службу «компонентом, учитывающим жизненный цикл» в MVVM. Как? У вас уже есть привязанная служба, привяжите ее к * владельцу жизненного цикла * (в вашем случае активность и один фрагмент, к которому вы привязываетесь) и во время любого возобновления или запуска события вашего наблюдателя жизненного цикла (вы обслуживаете здесь) , позвоните или сообщите об изменении данных своему владельцу жизненного цикла. так что все, что вам нужно, это реализовать LifecycleObserver интерфейс.   -  person Jeel Vankhede    schedule 29.11.2018
comment
@JeelVankhede, это более приятный способ управления привязкой и отключением моей службы, который я не рассматривал, спасибо! Однако я до сих пор не могу понять, как это в конечном итоге будет работать в связи с моей проблемой ViewModel и LiveData. Вы бы поместили связанные с Ble LiveData во ViewModel фрагмента? Как уведомить об изменениях между ними? Поскольку данные недоступны в onStart или onResume, они собираются между ними.   -  person dglozano    schedule 29.11.2018
comment
@MartinZeitler, ссылаясь на другие сайты, часто бывает полезно указать, что перекрестная публикация не одобряется   -  person gnat    schedule 30.11.2018
comment
Я голосую за то, чтобы закрыть этот вопрос как не по теме, потому что он принадлежит softwareengineering.stackexchange.com   -  person Martin Zeitler    schedule 30.11.2018
comment
@gnat просто так подумал, потому что я связался с другим ответом там, в комментариях к моему отозванному ответу ... и поскольку этот вопрос не имеет прямого отношения к коду, он, похоже, не по теме. там он может получить даже лучшие ответы, чем здесь. пока это еще не кросс-пост, но его следует перенести.   -  person Martin Zeitler    schedule 30.11.2018


Ответы (5)


Обновлено:

Получив предложение от @Ibrahim Disouki (спасибо за это), я копнул глубже и обнаружил кое-что интересное! Вот предыстория.

O.P. ищет решение, Где стоит сервисный компонент Android Framework с учетом компонентов архитектуры Android. Итак, вот готовое решение (SDK).

Он находится на том же уровне, что и Activity/Fragment. Как? Если вы вместо этого расширяете класс обслуживания, начните расширять LifecycleService. Причина этого проста: раньше нам приходилось полагаться на жизненный цикл Activity / Fragment, чтобы получать обновления / выполнять некоторые контекстные операции в Сервисе. Но сейчас это не так.

LifecycleService теперь имеет свой собственный реестр / обслуживающий персонал жизненного цикла под названием ServiceLifecycleDispatcher, который заботится о жизненном цикле службы, что также делает его LifecycleOwner.

Это оставляет нас в состоянии, что отныне вы можете иметь от ViewModel до LifecycleService, выполняющих операции для себя, и если вы следуете правильной архитектуре приложения и наличие шаблона репозитория оставляет вас единственным источником истины!

В контексте O.P. LifecycleService теперь может иметь возможность поддерживать ViewModel для выполнения бизнес-логики, относящейся к уровню репозитория, а позже и другой компонент, учитывающий жизненный цикл, например Activity / Fragment, также может потреблять / повторно использовать тот же ViewModel для выполнения своих конкретных операций.

Обратите внимание, что при этом вы получаете два разных LifecycleOwners (Activity & LifecycleServie), что означает, что вы не можете совместно использовать модели представления между LifecycleService и другими компонентами, поддерживающими жизненный цикл. Если вам не нравится подход, будьте добры со старым подходом обратного вызова, имеющим обратные вызовы для Activity / Fragment из службы, когда данные готовы к обслуживанию и т. Д.


Obselete:

(предлагаю не читать)

На мой взгляд, Служба должна быть на том же уровне, что и Активность / Фрагмент, потому что это компонент Framework, а не < strong> MVVM. но из-за этого Сервис не реализует LifecycleOwner и это компонент Android Framework, его не следует рассматривать как источник данных, поскольку он может быть точкой входа в приложение.

Итак, дилемма заключается в том, что иногда (в вашем случае) Служба действует как источник данных, который предоставляет данные из некоторой длительной задачи в пользовательский интерфейс.

Так что же должно быть в Архитектурном компоненте Android? Я думаю, вы можете рассматривать его как LifecycleObserver. . потому что независимо от того, что вы делаете в фоновом режиме, вам необходимо учитывать жизненный цикл LifecycleOwner.

Почему? потому что обычно мы привязываем его к LifecycleOwner (Activity / Fragments) и для выполнения длительных задач вне пользовательского интерфейса. Таким образом, его можно рассматривать как LifecycleObserver. Таким образом, мы сделали нашу Службу компонентом, учитывающим жизненный цикл!


Как это можно реализовать?

  1. Возьмите свой класс обслуживания и реализуйте для него интерфейс LifecycleObserver.

  2. Когда вы привязываете свою службу к Activity/Fragment, во время подключения службы вашего класса обслуживания добавьте службу в свою деятельность как LifecycleObserver, вызвав метод getLifecycle().addObserver(service class obj)

  3. Теперь возьмите интерфейс в классе обслуживания, чтобы обеспечить обратный вызов от службы к вашему пользовательскому интерфейсу, и каждый раз, когда ваши данные изменяются, проверьте, есть ли в вашей службе хотя бы событие жизненного цикла create или resume, чтобы обеспечить обратный вызов.

Таким образом, нам не потребуется LiveData для обновления из службы и даже ViewModel (Зачем нам это нужно для обслуживания? Нам не нужны изменения конфигурации, чтобы выжить в течение жизненного цикла службы. И основная задача для ВМ состоит в том, чтобы содержать данные между жизненными циклами).


Боковое примечание: если вы считаете, что у вас есть длительные фоновые операции, подумайте об использовании WorkManager. После использования этой библиотеки вы почувствуете, что службы должны быть помечены как устаревшие! (Просто случайная мысль)

person Jeel Vankhede    schedule 30.11.2018
comment
Спасибо, пока что это лучший ответ, я думаю :) Однако пока не буду отмечать его как правильный. Это правда, что служба отвечает за получение некоторых, но она также отслеживает статус соединения с устройством. Фрагмент включает / отключает некоторые кнопки в пользовательском интерфейсе в зависимости от статуса подключения. Это то, что, например, должно выдержать поворот экрана. Следовательно, BluetoothService сохраняет это состояние подключения в объекте LiveData, и фрагмент наблюдает за ним, чтобы реагировать соответствующим образом. Как бы вы с этим справились? - person dglozano; 30.11.2018
comment
Затем о другом типе информации, которую обрабатывает BluetoothService, о получении данных, я согласен с тем, что вы говорите :) Чтобы уточнить, вы предлагаете следующее: Activity имеет ссылку на службу и запускает в ней различные операции, а затем Служба не имеет ссылки на действие, но вместо этого сообщает о прибытии нового набора данных с использованием шаблона наблюдателя? Это правильно? - person dglozano; 30.11.2018
comment
Да, для вашей LiveData цели используйте тот же ViewModel вашего Activity / Fragment и общайтесь с ним, используя обратные вызовы интерфейса к вашей службе и обратно. Для этой цели вы можете использовать карту трансформации в liveata. - person Jeel Vankhede; 30.11.2018
comment
Я понимаю вашу точку зрения. И каковы будут плюсы и минусы этого (действие / фрагмент запускает операции для службы, ответ службы с обратными вызовами, действие / фрагмент обновляет связанные LiveData в своей собственной модели представления) с использованием общей модели представления между службой и действием / фрагментом ? Таким образом, данные поступают не из Сервиса в Activity, а оттуда в ViewModel, а напрямую из Сервиса в ViewModel. - person dglozano; 30.11.2018
comment
Нет, с моей точки зрения, не используйте общую виртуальную машину или LiveData для обслуживания. Мы сделали наш сервис lifecycleObserver, что означает, что теперь он знает об активности / фрагменте. Итак, используйте прямой обратный вызов, уважая состояния lifecycleOwner. Теперь не нужно использовать посредника для обслуживания. - person Jeel Vankhede; 02.12.2018
comment
В настоящее время в службе внедряется проверка LifecycleService LifecycleService, но теперь возникает вопрос как создать модель общего представления между службой и другим компонентом Android, например Activity или Fragment? @JeelVankhede - person Ibrahim Disouki; 27.06.2020
comment
@IbrahimDisouki Честно говоря, я ответил на это давно, но теперь, когда вы говорите, что эта услуга LifecycleOwner, что означает, что мой ответ не имеет смысла в этом случае, мне нужно подумать еще раз и придумать решение. Все, что я могу сказать, это то, что есть только один способ обмениваться данными - это следовать обычным коммуникациям, которые мы использовали в прошлом между любым действием и сервисом. - person Jeel Vankhede; 27.06.2020
comment
Есть ли у кого-нибудь рабочий пример кода этого ответа? - person Emi Raz; 01.07.2020
comment
@JeelVankhede, не могли бы вы помочь мне инициализировать мою ViewModel. Мне нужен ViewModelStore или ViewModelStoreOwner для инициализации ViewModel с помощью ViewModel.Factory. Но расширение LifecycleService не предоставляет ViewModelStore. Мне нужно это реализовать? Если да, то какой должна быть переопределенная функция? - person Mihodi Lushan; 23.09.2020
comment
Вы должны ссылаться на новое решение прямо в верхней части ответа, чтобы люди не тратили свое время на старое, уже не актуальное решение и вместо этого использовали новый подход. - person Emil S.; 14.10.2020
comment
@EmilS. Приносим извинения за неудобства, обновил пост. Спасибо за ваш отзыв. - person Jeel Vankhede; 15.10.2020
comment
@JeelVankhede Я упустил суть. Почему вы говорите о жизненном цикле, когда проблема в том, как передавать данные в действия / фрагменты? Привязанная служба существует до тех пор, пока существует клиент, который может с ней взаимодействовать, поэтому достаточно управлять привязкой и отменять привязку в соответствующем состоянии жизненного цикла активности / фрагмента. Тем не менее, проблема OP остается, и заключается в том, как правильно управлять данными между службами и действиями / фрагментами. - person blow; 03.04.2021
comment
@blow Либо ты запутался, приятель, прочитав мой ответ, либо ты, возможно, еще не прочитал вопрос. Прошло много времени с тех пор, как я ответил на этот вопрос, и O.P. в основном искал помощи в понимании того, где лучше всего размещать сервисы в шаблоне MVVM. В этом посте никогда не было проблемы с передачей данных / связью, поскольку O.P. уже знает, как с этим бороться. Мой ответ явно был попыткой прийти к какому-либо выводу о шаблоне архитектуры для сервисов в Android. Если у вас есть лучшие мысли, дайте мне знать, что мы всегда можем обсудить и договориться о чем-то. - person Jeel Vankhede; 05.04.2021

Один из способов избежать прямого контакта со службой Android, сохранив при этом возможность ее использовать, - это объект интерфейса. Это часть «I» для разделения интерфейсов в аббревиатуре SOLID. Вот небольшой пример:

public interface MyFriendlyInterface {
    public boolean cleanMethodToAchieveBusinessFunctionality();
    public boolean anotherCleanMethod();
}

public class MyInterfaceObject implements MyFriendlyInterface {
    public boolean cleanMethodToAchieveBusinessFunctionality() {
        BluetoothObject obj = android.Bluetooth.nastySubroutine();
        android.Bluetooth.nastySubroutineTwo(obj);
    }

    public boolean anotherCleanMethod() {
        android.Bluetooth.anotherMethodYourPresentersAndViewModelsShouldntSee();
    }
}

public class MyViewModel {
    private MyFriendlyInterface _myInterfaceObject;

    public MyViewModel() {
        _myInterfaceObject = new MyInterfaceObject();
        _myInterfaceObject.cleanMethodToAchieveBusinessFunctionality();
    }
}

Учитывая вышеупомянутую парадигму, вы можете разместить свои услуги в пакете, который находится за пределами ваших пакетов, содержащих код POJO. Не существует "правильного" места для размещения ваших сервисов, но есть определенно НЕПРАВИЛЬНЫЕ места для их размещения (например, там, где идет ваш код POJO).

person Grant Park    schedule 18.01.2019

Что, если мы привязываем / отвязываем службу от активности или нескольких действий, как обычно в onStart / onStop, тогда у нас будет одноэлементный экземпляр, содержащий диспетчер, связанный с Bluetooth (я использую nordic lib для диспетчера ble). Этот экземпляр находится в обслуживании, поэтому мы можем отключиться, например, когда служба будет уничтожена, потому что пользовательский интерфейс не связан с ней, и повторно подключиться к ble при создании службы. Мы также внедряем этот одноэлементный элемент управления ble Manager в модель просмотра, чтобы упростить взаимодействие и прослушивание данных с помощью liveata или rx или аналогичных реактивных данных, предоставляемых диспетчером ble, например, для состояния соединения. Таким образом, мы можем взаимодействовать из модели просмотра с ble, подписываться на характеристики и т. Д., А сервис должен обеспечивать область действия, которая может выжить в нескольких действиях и в основном знает, когда подключаться или отключаться. Я пробовал этот подход в своем приложении, и пока он работает нормально.

Пример проекта https://github.com/uberchilly/BoundServiceMVVM

person uberchilly    schedule 07.06.2020
comment
Мне это решение кажется интересным, но что делать, если никто не запускает сервис за вас? По сути, вы зависите только от менеджера ble, но он работает только в том случае, если вы запускаете определенную службу. Откуда это известно действиям / фрагментам? - person blow; 03.04.2021

На мой взгляд, использовать LiveData в сервисе удобно

    class OneBreathModeTimerService : Service() {
        var longestHoldTime = MutableLiveData<Int>().apply { value = 0 }
        ...
    }

Затем во фрагменте

     override fun onCreate(savedInstanceState: Bundle?) {
         mServiceConnection = object : ServiceConnection {

            override fun onServiceConnected(name: ComponentName, binder: IBinder) {
                mOneBreathModeService = (binder as OneBreathModeTimerService.MyBinder).service

                mOneBreathModeService!!.longestHoldTime.observe(this@OneBreathModeFragment, androidx.lifecycle.Observer {
                    binding.tvBestTime.text = "Best $it"
                })
            }

            override fun onServiceDisconnected(name: ComponentName) {}
     }

Я не профессионал в LiveData, но что может быть плохого в таком подходе?

person brucemax    schedule 18.11.2019

Как насчет такого отношения к вашей службе?

введите здесь описание изображения

person M-WaJeEh    schedule 27.11.2018
comment
Я думаю, что OP упомянул требование, чтобы служба была привязана к Activity, чтобы получить жизненный цикл Activity ... OP, вероятно, не хочет убивать сканирование батареи и наблюдение за bluetooth, если это не актуально. - person MidasLefko; 27.11.2018
comment
Хммм, это была моя первая мысль. Однако мне кажется, что BluetoothService не похож на веб-сервис. Это компонент Android с собственным жизненным циклом, управляемый ОС Android. Также необходимо начать передачу контекста. Так что я не думаю, что это может быть там внизу. Более того, какова будет ответственность источника данных Ble? Кроме того, если вы посмотрите на архитектуру сверху вниз, то чем глубже вы войдете, тем дальше вы окажетесь от Android, пока вы не оставите его полностью, когда дойдете до SQL или своего API. С BluetoothService этого не происходит, и это кажется странным. - person dglozano; 27.11.2018
comment
Для параллелизма, на мой взгляд, эквивалентом SQLite или веб-сервиса в моем сценарии был бы GattServer внутри моего Scale. Взаимодействие с ним осуществляется службой Bluetooth, поэтому в этом смысле это эквивалент DAO. Так что все, казалось, имело смысл, пока я не прочитал пост Хосе Альсерреки. - person dglozano; 27.11.2018
comment
Кроме того, из Руководства разработчика Android: избегайте обозначения точек входа вашего приложения, таких как действия, службы и приемники широковещательных сообщений, в качестве источников данных. Вместо этого они должны координировать свои действия только с другими компонентами для получения подмножества данных, относящихся к этой точке входа. Каждый компонент приложения довольно недолговечен, в зависимости от взаимодействия пользователя со своим устройством и общего текущего состояния системы. - person dglozano; 27.11.2018