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

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

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

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

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

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

  • У меня есть приложение для Android, в котором есть MainActivity со многими фрагментами в нем, все из которых связаны друг с другом в BottomNavBar.
  • У меня есть BluetoothService, связанный с myActivity и одним из его фрагментов (потому что я хочу, чтобы служба имела тот же жизненный цикл, что и Activit, но я также хочу взаимодействовать с ней непосредственно из моего фрагмента).
  • Фрагмент взаимодействует с BluetoothService для получения двух типов информации:
    • Информация о состоянии соединения Bluetooth. Не нужно настаивать.
    • Данные, поступающие с устройства Bluetooth (это масштаб, поэтому вес и состав тела в этом случае). Нужно продолжать.

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

LiveData внутри AndroidService LiveData inside Android Service arch

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

Shared ViewModel между фрагментом и AndroidService Shared ViewModel arch

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

Сервис ViewModel Service ViewMOdel arch

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

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

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

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

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

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

Ответ 1

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

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

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

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


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

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

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

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

Таким образом, мы не будем требовать обновления LiveData от службы и даже без ViewModel (зачем нам это нужно для обслуживания? Нам не нужны изменения конфигурации, чтобы выжить на жизненном цикле обслуживания. Основная задача VM - состоят из данных между жизненными циклами).

Надеюсь, я дал вам понять!

Ответ 2

Один из способов избежать прямого контакта со службой Android и при этом использовать ее - через интерфейсный объект. Это часть "Я" для разделения интерфейса в аббревиатуре 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).

Ответ 3

Как насчет обращения с этим сервисом?

enter image description here

Ответ 4

Я бы предложил использовать Clean Architicture, и вы будете рассматривать вашу службу как источник данных, чтобы она была на уровне данных. Если вы будете придерживаться MVVM/MVC с уровнем репозитория, то я проголосую за ответ M-WaJeEh