新 Android 推荐架构中的 BoundService + LiveData + ViewModel 最佳实践

BoundService + LiveData + ViewModel best practice in new Android recommended architecture

我一直在思考在新的 Android recommended Architecture 中放置 Android 服务的位置。我想出了很多可能的解决方案,但我无法确定哪一个是最好的方法。

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

Ideally, ViewModels shouldn’t know anything about Android. This improves testability, leak safety and modularity. A general rule of thumb is to make sure there are no android.* imports in your ViewModels (with exceptions like android.arch.*). The same applies to presenters.

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

现在,我将简要说明我的场景,但只是为了让全景更清晰,而不是因为我想要针对这个特定场景的答案。

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

LiveData inside AndroidService

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

片段和 AndroidService 之间的共享 ViewModel

服务视图模型


我很确定我应该将它们放在架构的顶部并像 Activity/Fragment 一样对待它们,因为 BoundServices 是 Android 框架的一部分,它们由 Android OS 并且它们绑定到其他 Activity 和 Fragments。在那种情况下,我不知道与 LiveData、ViewModel 和 Activities/Fragments.

交互的最佳方式是什么

有些人可能认为它们应该被视为数据源(因为在我的例子中它是使用蓝牙从秤中获取数据),但我认为这不是一个好主意,因为我已经知道了在上一段中特别提到 because of what it says here:

Avoid designating your app's entry points—such as activities, services, and broadcast receivers—as sources of data. Instead, they should only coordinate with other components to retrieve the subset of data that is relevant to that entry point. Each app component is rather short-lived, depending on the user's interaction with their device and the overall current health of the system.

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

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

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

已更新:

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

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

Activity/Fragment处于同一水平。如何?如果您要扩展服务 class 而不是那样,请开始扩展 LifecycleService。这背后的原因很简单,以前我们不得不依赖 Activity/Fragment 生命周期来接收 updates/do 服务上的一些上下文操作。但现在不是这样了。

LifecycleService 现在有自己的生命周期 registry/maintainer,称为 ServiceLifecycleDispatcher,它负责服务的生命周期,这也使它成为 LifecycleOwner

从现在开始,您可以让 ViewModelLifecycleService 为自己执行操作,并且如果您遵循正确的应用程序架构并且拥有存储库模式,那么您可以单一事实来源!

在 O.P 的上下文中。 LifecycleService 现在可以维护它的 ViewModel 来执行与存储库层相关的业务逻辑,稍后在另一个生命周期感知组件上,Activity/Fragment 也可以 consume/reuse 相同 ViewModel 到有他们的具体操作。

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


已过时:

(建议不要通读)

在我看来,服务应该与[=129=处于同一水平],因为它是框架组件而不是 MVVM。但由于该服务未实现 LifecycleOwner 并且它是 Android 框架组件,因此不应将其视为 数据源 因为它可以作为应用程序的入口点。

所以,这里的困境是有时 (在你的情况下),服务充当数据源,它提供从一些长 运行 任务到 UI.

那么在Android架构组件中应该是什么?我想你可以把它当作LifecycleObserver。因为,无论您在后台做什么,您都需要考虑 LifecycleOwner.

的生命周期

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


How you can implement it?

  1. 获取你的服务class并实现LifecycleObserver接口。

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

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

这样一来,我们就不需要LiveData更新到from服务,甚至不需要ViewModel (为什么我们需要服务?我们不不需要更改配置来在服务生命周期中存活。VM 的主要任务是在生命周期之间包含数据).


旁注:如果您认为自己有长时间的 运行 后台操作,请考虑使用 WorkManager 使用此库后,您你会觉得现在应该将服务标记为已弃用! (随便想的)

避免直接接触 Android 服务同时仍然能够使用它的一种方法是通过接口对象。这是 "I" 的一部分,用于 Interface Segregation 的缩写,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 代码的包之外的包中。没有 "right" 位置来放置您的服务——但肯定有错误的位置来放置它们(例如,您的 POJO 代码所在的位置)。

我认为在服务中使用 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 专家,但这种方法有什么问题?

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

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

这个问题困惑了我很久。我认为绑定服务不应该有 viewModle,正如我们所知,服务不是视图层!

顺便说一句,服务需要start/bind,解绑在activity

最后,我认为一个简单且不错的方法是AndroidService 中的LiveData。 但不是使用Livedata 发送数据,而是使用自定义回调方法。 实时数据每次只发送最新的数据,如果你需要获取完整的数据而且页面处于暂停状态,这是错误的。(现在,我们可以使用kotlin flow)

此外,我们不仅要从 ble(bluetooth low energe) 设备接收数据,我们还需要将数据发送到 ble 设备。

有一个简单的项目代码: https://github.com/ALuoBo/TestTemp/blob/main/bluetooth/src/main/java/com/lifwear/bluetooth/BLECommService.java