81

我一直在努力思考将 Android Services 放置在新的Android 推荐的 Architecture中的哪个位置。我想出了许多可能的解决方案,但我无法决定哪一个是最好的方法。

我做了很多研究,但找不到任何有用的指南或教程。我发现的关于在我的应用程序架构中放置服务的唯一提示是这个,来自@JoseAlcerreca Medium 帖子

理想情况下,ViewModel 不应该对 Android 有任何了解。这提高了可测试性、泄漏安全性和模块化。一般的经验法则是确保您的 ViewModel 中没有 android.* 导入(android.arch.* 等例外情况除外)。这同样适用于演讲者。

据此,我应该将我的 Android 服务放在架构组件层次结构的顶部,与我的活动和片段处于同一级别。那是因为 Android 服务是 Android 框架的一部分,所以 ViewModel 不应该知道它们。

现在,我将简要解释一下我的场景,但只是为了让全景更清晰,而不是因为我想要这个特定场景的答案。

  • 我有一个 Android 应用程序,它有一个 MainActivity,里面有很多片段,所有这些片段都捆绑在一个 BottomNavBar 中。
  • 我有一个绑定到 myActivity 及其片段之一的 BluetoothService(因为我希望服务具有与 Activty 相同的生命周期,但我也想直接从我的片段与其交互)。
  • 该片段与 BluetoothService 交互以获取两种类型的信息:
    • 有关蓝牙连接状态的信息。不需要坚持。
    • 来自蓝牙设备的数据(它是一个秤,在这种情况下是体重和身体成分)。需要坚持。

以下是我能想到的 3 种不同的架构:

AndroidService 中的 LiveData

更新:这是我个人当时采用的方法,因为它运作良好,让我能够相对快速地完成它。但是,我建议遵循 Jeel Vankhede 的更新答案,以获得似乎更“惯用”的实现。

Android 服务架构中的 LiveData

  • 带有连接状态和来自蓝牙设备的重量测量值的 LiveData 在 BluetoothService 中。
  • Fragment 可以触发 BluetoothService 中的操作(例如 scanDevices)
  • Fragment 观察有关连接状态的 LiveData 并相应地调整 UI(例如,如果状态为连接,则启用按钮)。
  • Fragment 观察新体重测量的 LiveData。如果来自 BluetoothDevice 的新重量测量值,则 Fragment 会告诉它自己的 ViewModel 保存新数据。它是通过 Repository 类完成的。

在 Fragment 和 AndroidService 之间共享 ViewModel 共享 ViewModel 拱门

  • Fragment 可以触发 BluetoothService 中的操作(例如 scanDevices)
  • BluetoothService 更新共享 ViewModel 中的蓝牙相关 LiveData。
  • Fragment 在自己的 ViewModel 中观察 LiveData。

服务视图模型 服务视图模型拱

  • Fragment 可以触发 BluetoothService 中的操作(例如 scanDevices)
  • BluetoothService 在自己的 ViewModel 中更新与蓝牙相关的 LiveData。
  • Fragment 在自己的 ViewModel 和 BluetoothService ViewModel 中观察 LiveData。

我很确定我应该将它们放在架构之上,并将它们视为 Activity/Fragment,因为 BoundServices 是 Android 框架的一部分,它们由 Android 操作系统管理,并且绑定到其他 Activity 和 Fragment。在这种情况下,我不知道与 LiveData、ViewModel 和活动/片段交互的最佳方式是什么。

有些人可能认为它们应该被视为数据源(因为在我的例子中,它是使用蓝牙从秤中获取数据),但我认为这不是一个好主意,因为我在上一段中说过特别是因为它在这里说的

避免将应用程序的入口点(例如活动、 服务和广播接收器)指定为数据源。相反,它们应该只与其他组件协调以检索与该入口点相关的数据子集。每个应用程序组件的寿命都很短,具体取决于用户与其设备的交互以及系统当前的整体健康状况。

所以,最后,我的问题是:

我们应该将我们的 Android(绑定)服务放在哪里,它们与其他架构组件的关系是什么?这些替代方案中的任何一个都是好方法吗?

4

5 回答 5

47

更新:

在得到@Ibrahim Disouki 的建议后(谢谢),我深入挖掘并发现了一些有趣的东西!这里是背景。

OP寻求解决方案“ Android 框架的服务组件在哪里考虑 Android 架构组件”。所以,这里是开箱即用(SDK)解决方案。

它与 处于同一水平Activity/Fragment。如何?如果您要扩展 Service 类而不是那样,请开始扩展LifecycleService。这背后的原因很简单,以前我们必须依赖 Activity/Fragment 生命周期才能接收更新/对 Service 进行一些上下文操作。但现在情况并非如此。

LifecycleService现在有它自己的生命周期注册表/维护ServiceLifecycleDispatcher器,它负责服务的生命周期,这也使它成为LifecycleOwner

它给我们留下的条件是,从现在开始,您可以自己ViewModel进行LifecycleService操作,并且如果您遵循正确的应用程序架构并且拥有存储库模式,那么您将获得单一的事实来源!

在 OP LifecycleService 的上下文中,现在可以维护它ViewModel以执行与存储库层相关的业务逻辑,然后在另一个生命周期感知组件(如 Activity/Fragment)上也可以使用/重用ViewModel它们以对其进行特定操作。

请注意,通过这样做,您处于拥有两个不同LifecycleOwner的状态(Activity 和 LifecycleServie),这意味着您无法在LifecycleService其他生命周期感知组件之间共享视图模型。如果你不喜欢这种方法,那么最好使用旧的回调方法,当数据准备好服务时,从服务中回调到 Activity/Fragment 等。


过时的:

(我建议不要通读)

在我看来,Service应该与Activity/Fragment处于同一级别,因为它是 Framework 组件而不是MVVM。但由于该服务没有实现LifecycleOwner并且它是 Android 框架组件,因此不应将其视为数据源,因为它可以作为应用程序的入口点。

因此,这里的困境是有时(在您的情况下),服务充当数据源,将来自某个长时间运行的任务的数据提供给 UI。

那么它应该在Android 架构组件中是什么?我认为您可以将其视为LifecycleObserver。因为,无论您在后台做什么,都需要考虑 LifecycleOwner 的生命周期

为什么?因为,我们通常会将它绑定到LifecycleOwner (活动/片段)并在 UI 之外执行长时间运行的任务。因此,它可以被视为LifecycleObserver。通过这种方式,我们将我们的服务作为“生命周期感知组件”!


你如何实现它?

  1. 获取您的服务类并为其实现LifecycleObserver接口。

  2. 当您将服务绑定到时Activity/Fragment,在您的服务类的服务连接期间,通过调用方法将您的服务作为 LifecycleObserver 添加到您的活动中getLifecycle().addObserver(service class obj)

  3. 现在,在服务类中使用一个接口来提供从服务到 UI 的回调,并且每次数据更改时,检查您的服务是否至少有生命周期事件创建恢复来提供回调。

通过这种方式,我们不需要LiveData从服务更新,甚至不需要ViewModel (为什么我们需要它来服务?我们不需要配置更改来在服务生命周期中生存。VM 的主要任务是包含数据生命周期之间)


旁注:如果您认为您有长时间运行的后台操作,请考虑使用WorkManager. 使用此库后,您会觉得服务现在应该被标记为已弃用!(只是一个偶然的想法)

于 2018-11-30T06:15:25.620 回答
1

在仍然能够使用它的同时避免直接接触 Android 服务的一种方法是通过接口对象。这是首字母缩写词SOLID中接口隔离的“I”的一部分。这是一个小例子:

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 代码所在的位置)。

于 2019-01-18T22:17:16.253 回答
1

如果我们像往常一样在 onStart/onStop 中从活动或多个活动中绑定/取消绑定服务,那么我们将拥有包含蓝牙相关管理器的单例实例(我使用北欧库作为 ble 管理器)。该实例正在服务中,因此我们可以在服务被销毁时断开连接,因为 ui 未绑定它,并在创建服务时重新连接到 ble。我们还将 ble 管理器单例注入到视图模型中,以便通过 livedata 或 rx 或 ble 管理器提供的类似反应数据(例如连接状态)更轻松地进行交互和数据侦听。通过这种方式,我们可以从 viewmodel 与 ble 进行交互,订阅特性等,并且服务可以提供可以在多个活动中生存的范围,并且基本上知道何时连接或断开连接。我已经在我的应用程序中尝试过这种方法,到目前为止它工作正常。

示例项目 https://github.com/uberchilly/BoundServiceMVVM

于 2020-06-07T22:21:59.143 回答
0

在我看来,在服务中使用 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 的专业人士,但这种方法有什么问题?

于 2019-11-18T20:48:49.003 回答
-3

像这样对待你的服务怎么样?

在此处输入图像描述

于 2018-11-27T08:17:18.010 回答