简体   繁体   English

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

[英]BoundService + LiveData + ViewModel best practice in new Android recommended architecture

I been struggling a lot thinking about where to place Android Services in the new Android recommended Architecture .我一直在努力思考将 Android Services 放置在新的Android 推荐的 Architecture中的哪个位置。 I came up with many possible solutions, but I cannot make up my mind about which one is the best approach.我想出了许多可能的解决方案,但我无法决定哪一个是最好的方法。

I did a lot of research, and I couldn't find any useful guideline nor tutorial.我做了很多研究,但找不到任何有用的指南或教程。 The only hint I found about where to place the Service in my app architecture is this one, from @JoseAlcerreca Medium post我发现的关于在我的应用程序架构中放置服务的唯一提示是这个,来自@JoseAlcerreca Medium 帖子

Ideally, ViewModels shouldn't know anything about Android.理想情况下,ViewModel 不应该对 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.*).一般的经验法则是确保您的 ViewModel 中没有 android.* 导入(android.arch.* 等例外情况除外)。 The same applies to presenters.这同样适用于演讲者。

According to that, I should place my Android Services on the top of my Architecture Components hierarchy, at the same level as my Activities and Fragments.据此,我应该将我的 Android 服务放在架构组件层次结构的顶部,与我的活动和片段处于同一级别。 That's because Android Services are part of the Android framework, so ViewModels shouldn't know about them.那是因为 Android 服务是 Android 框架的一部分,所以 ViewModel 不应该知道它们。

Now, I will explain briefly my scenario, but only to make the panorama clearer, not because I want an answer for this specific scenario.现在,我将简要解释一下我的场景,但只是为了让全景更清晰,而不是因为我想要这个特定场景的答案。

  • I have an Android Application that has a MainActivity with many fragments in it, all of them tied together in a BottomNavBar.我有一个 Android 应用程序,它有一个 MainActivity,里面有很多片段,所有这些片段都捆绑在一个 BottomNavBar 中。
  • I have a BluetoothService bound to myActivity and one of its fragments (because I want the Service to have the same lifecycle as the Activty but I also want to interact with it directly from my fragment).我有一个绑定到 myActivity 及其片段之一的 BluetoothService(因为我希望服务具有与 Activty 相同的生命周期,但我也想直接从我的片段与其交互)。
  • The fragment interacts with the BluetoothService to get two types of information:该片段与 BluetoothService 交互以获取两种类型的信息:
    • Information about the state of the Bluetooth connection.有关蓝牙连接状态的信息。 Doesn't need to be persisted.不需要坚持。
    • Data that comes from the Bluetooth Device (it is a Scale, so weight and body composition in this case).来自蓝牙设备的数据(它是一个秤,在这种情况下是体重和身体成分)。 Needs to be persisted.需要坚持。

Here are the 3 different architectures I can think of:以下是我能想到的 3 种不同的架构:

LiveData inside AndroidService AndroidService 中的 LiveData

UPDATE: This is the approach I personally went with at the time because it worked well and allowed me to get it done relatively fast.更新:这是我个人当时采用的方法,因为它运作良好,让我能够相对快速地完成它。 However, I suggest following the updated answer by Jeel Vankhede for what seems to be a more "idiomatic" implementation.但是,我建议遵循 Jeel Vankhede 的更新答案,以获得似乎更“惯用”的实现。

Android 服务架构中的 LiveData

  • The LiveData with the state of the connection and with the weight measurements coming from the Bluetooth Device are inside the BluetoothService.带有连接状态和来自蓝牙设备的重量测量值的 LiveData 位于 BluetoothService 内部。
  • The Fragment can trigger operations in the BluetoothService (scanDevices for example) Fragment 可以触发 BluetoothService 中的操作(例如 scanDevices)
  • The Fragment observes the LiveData about the state of the connection and adapts the UI accordingly (for example, enable a button if the state is connected). Fragment 观察有关连接状态的 LiveData 并相应地调整 UI(例如,如果状态为连接,则启用按钮)。
  • The Fragment observes the LiveData of the new weight measurements. Fragment 观察新体重测量的 LiveData。 If a new weight measurement comes from the BluetoothDevice, the Fragment then tells its own ViewModel to save the new data.如果来自 BluetoothDevice 的新重量测量值,则 Fragment 会告诉它自己的 ViewModel 保存新数据。 It is done via a Repository class.它是通过 Repository 类完成的。

Shared ViewModel between fragment and AndroidService在 Fragment 和 AndroidService 之间共享 ViewModel 共享 ViewModel 拱门

  • The Fragment can trigger operations in the BluetoothService (scanDevices for example) Fragment 可以触发 BluetoothService 中的操作(例如 scanDevices)
  • The BluetoothService updates the Bluetooth related LiveData in the shared ViewModel. BluetoothService 更新共享 ViewModel 中的蓝牙相关 LiveData。
  • The Fragment observes the LiveData in its own ViewModel. Fragment 在自己的 ViewModel 中观察 LiveData。

Service ViewModel服务视图模型服务视图模型拱

  • The Fragment can trigger operations in the BluetoothService (scanDevices for example) Fragment 可以触发 BluetoothService 中的操作(例如 scanDevices)
  • The BluetoothService updates the Bluetooth related LiveData in its own ViewModel. BluetoothService 在自己的 ViewModel 中更新与蓝牙相关的 LiveData。
  • The Fragment observes the LiveData in its own ViewModel and the BluetoothService ViewModel. Fragment 在自己的 ViewModel 和 BluetoothService ViewModel 中观察 LiveData。

I am pretty sure I should place them on top of the architecture and treat them just like an Activity/Fragment, because BoundServices are part of the Android Framework, they are managed by the Android OS and they are bound to other Activities and Fragments.我很确定我应该将它们放在架构之上,并将它们视为 Activity/Fragment,因为 BoundServices 是 Android 框架的一部分,它们由 Android 操作系统管理,并且绑定到其他 Activity 和 Fragment。 In that case, I don't know what's the best way to interact with LiveData, ViewModels and Activities/Fragments.在这种情况下,我不知道与 LiveData、ViewModel 和活动/片段交互的最佳方式是什么。

Some might think that they should be considered as a DataSource (since in my case it's getting data from a scale using Bluetooth), but I don't think this is a good idea, because of all what I've said in the previous paragraph and speciallybecause 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.每个应用程序组件的寿命都很短,具体取决于用户与其设备的交互以及系统当前的整体健康状况。

So, finally, my question is:所以,最后,我的问题是:

Where should we place our Android (Bound) Services and what is their relation with the other architectural components?我们应该将我们的 Android(绑定)服务放在哪里,它们与其他架构组件的关系是什么? Is any of these alternatives a good approach?这些替代方案中的任何一个都是好方法吗?

Updated:更新:

After getting suggestion from @Ibrahim Disouki (Thank you for that) I dig deeper and found out something interesting!在得到@Ibrahim Disouki 的建议后(谢谢),我深入挖掘并发现了一些有趣的东西! Here's background.这里是背景。

OP seeks for solution " Where Service component of Android Framework stands considering Android Architecture Components ". OP寻求解决方案“ Android 框架的服务组件在哪里考虑 Android 架构组件”。 So, here's out the box(SDK) solution.所以,这里是开箱即用(SDK)解决方案。

It stands at the same level as Activity/Fragment .它与Activity/Fragment处于同一级别。 How?如何? If you're extending Service class though rather than that, start extending LifecycleService .如果您要扩展 Service 类而不是那样,请开始扩展LifecycleService Reason behind that is simple that previously we had to rely on Activity/Fragment lifecycle in order to receive updates/do some contextual operations on Service.这背后的原因很简单,以前我们必须依赖 Activity/Fragment 生命周期才能接收更新/对 Service 进行一些上下文操作。 But now it's not the case.但现在情况并非如此。

LifecycleService now has it's own lifecycle registry/maintainer called ServiceLifecycleDispatcher which takes care of lifecycle of service which also makes it LifecycleOwner . LifecycleService现在拥有自己的生命周期注册表/维护器,称为ServiceLifecycleDispatcher ,它负责服务的生命周期,这也使其成为LifecycleOwner

It leaves us in condition that from now on, You can have a ViewModel to LifecycleService doing operations for itself & if you're following proper app architecture and having repository pattern leaves you to single source of truth!它让我们处于以下状态:从现在开始,您可以让LifecycleServiceViewModel为自己进行操作,如果您遵循正确的应用程序架构并且拥有存储库模式,那么您将获得单一的事实来源!

In context of OP LifecycleService now can have ability to maintain it's ViewModel to do business logic related to repository layer, and later on another lifecycle aware component like, Activity/Fragment can also consume/reuse same ViewModel to have their specific operations to it.在 OP LifecycleService 的上下文中,现在可以维护其ViewModel以执行与存储库层相关的业务逻辑,稍后在另一个生命周期感知组件(如 Activity/Fragment)上也可以使用/重用相同的ViewModel对其进行特定操作。

Please note that by doing so, you're in state of having two different LifecycleOwner s (Activity & LifecycleServie) which means you can't share view models between LifecycleService & other lifecycle aware components.请注意,这样做,您处于拥有两个不同LifecycleOwner (Activity 和 LifecycleServie)的状态,这意味着您无法在LifecycleService和其他生命周期感知组件之间共享视图模型。 If you don't like approach then be good with old callback kind of approach having callbacks back to Activity/Fragment from service when data is ready to serve etc.如果你不喜欢这种方法,那么最好使用旧的回调方法,当数据准备好服务时,从服务中回调到 Activity/Fragment 等。


Obselete:过时的:

(I suggest not to read through) (我建议不要通读)

In my opinion, Service should be on same level as Activity/Fragment , because it's Framework component & not MVVM .在我看来, Service应该与Activity/Fragment处于同一级别,因为它是 Framework 组件而不是MVVM but because of that Service doesn't implements LifecycleOwner and it's Android Framework Component, it shouldn't be treated as data source because it can be entry point to application.但由于该服务没有实现LifecycleOwner并且它是 Android 框架组件,因此不应将其视为数据源,因为它可以作为应用程序的入口点。

So, dilemma here is that sometimes (In your case) , Service acts as data source which provides data from some long running task to UI.因此,这里的困境是有时(在您的情况下) ,服务充当数据源,将来自某个长时间运行的任务的数据提供给 UI。

So what it should be in Android Architecture Component ?那么它应该在Android 架构组件中是什么? I think you can treat it as LifecycleObserver .我认为您可以将其视为LifecycleObserver because, no matter what you do in background, you'll need to consider about lifecycle of the LifecycleOwner .因为,无论您在后台做什么,都需要考虑 LifecycleOwner 的生命周期

Why?为什么? because, we usually do bind it to LifecycleOwner (Activity/Fragments) & to do long running tasks off the UI.因为,我们通常会将它绑定到LifecycleOwner (活动/片段)并在 UI 之外执行长时间运行的任务。 So, it can be treated like LifecycleObserver .因此,它可以被视为LifecycleObserver In such a way we made our Service as " Lifecycle aware component " !通过这种方式,我们将我们的服务作为“生命周期感知组件”!


How you can implement it?你如何实现它?

  1. Take your service class and implement LifecycleObserver interface to it.获取您的服务类并为其实现LifecycleObserver接口。

  2. When you bind your service to Activity/Fragment , during your service connection of your service class, add your service to your activity as LifecycleObserver by calling method getLifecycle().addObserver(service class obj)当您将服务绑定到Activity/Fragment时,在服务类的服务连接期间,通过调用方法getLifecycle().addObserver(service class obj)将您的服务作为 LifecycleObserver 添加到您的活动中

  3. Now, Take an interface in service class to provide callback from service to your UI and every time your data changes, check that if your service has at least on Lifecycle event create or resume to provide callback with.现在,在服务类中使用一个接口来提供从服务到 UI 的回调,并且每次数据更改时,检查您的服务是否至少有生命周期事件创建恢复来提供回调。

In such a way, we won't require LiveData to update to from service and even no ViewModel (Why do we need it for service? We don't need configuration changes to survive on service lifecycle. And main task to VM is that to consist data between lifecycles) .这样,我们就不需要LiveData从服务更新,甚至不需要ViewModel (为什么我们需要它来服务?我们不需要配置更改来在服务生命周期中生存。VM 的主要任务是包含生命周期之间的数据)


Side Note: If you think you're having long running background operations, then consider using WorkManager .旁注:如果您认为您的后台操作运行时间很长,请考虑使用WorkManager After using this library, you'll feel like Services should be marked as deprecated by now!使用此库后,您会觉得服务现在应该被标记为已弃用! (Just a random thought) (只是一个偶然的想法)

One way to avoid direct contact with an Android service while still being able to use it is through an interface object.在仍然能够使用它的同时避免直接接触 Android 服务的一种方法是通过接口对象。 This is part of the "I" for Interface Segregation in the acronym, SOLID .这是首字母缩写词SOLID中接口隔离的“I”的一部分。 Here is a small example:这是一个小例子:

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();
    }
}

Given the above paradigm, you are free to place your services in a package that's outside your packages that contain POJO code.鉴于上述范例,您可以自由地将服务放在包含 POJO 代码的包之外的包中。 There is no "right" location to put your services -- but there are definitely WRONG places to put them (eg where your POJO code goes).没有放置服务的“正确”位置——但肯定有放置它们的错误位置(例如,您的 POJO 代码所在的位置)。

What if we bind/unbind to/from service from activity or multiple activities as usual in onStart/onStop, then we have singleton instance that holds Bluetooth related manager (I use nordic lib for ble manager) .如果我们像往常一样在 onStart/onStop 中从活动或多个活动中绑定/取消绑定服务,那么我们将拥有包含蓝牙相关管理器的单例实例(我使用北欧库作为 ble 管理器)。 That instance is in service so that we can disconnect for example when service is destroyed because ui unbound from it and reconnect to ble when service is created.该实例正在服务中,因此我们可以在服务被销毁时断开连接,因为 ui 未绑定它,并在创建服务时重新连接到 ble。 We also inject that ble manager singleton into viewmodel to make interaction and data listening easier via livedata or rx or similar reactive data provided by ble manager, for example for connection state.我们还将 ble 管理器单例注入到视图模型中,以便通过 livedata 或 rx 或 ble 管理器提供的类似反应数据(例如连接状态)更轻松地进行交互和数据侦听。 This way we can interact from viewmodel with ble, subscribe to characteristics etc and service is there to provide scope that can survive over multiple activities and basically knows when to connect or disconnect.通过这种方式,我们可以从 viewmodel 与 ble 进行交互,订阅特性等,并且服务可以提供可以在多个活动中生存的范围,并且基本上知道何时连接或断开连接。 I have tried this approach in my app and so far it works ok.我已经在我的应用程序中尝试过这种方法,到目前为止它工作正常。

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

In my opinion using LiveData in servise is comfortable在我看来,在服务中使用 LiveData 很舒服

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

Then in fragment然后在片段中

     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) {}
     }

I am not pro in LiveData, but what can be wrong with such approach?我不是 LiveData 的专业人士,但这种方法有什么问题?

This question confuse me a long time.这个问题让我困惑了很久。 I don't think bind service should have a viewModle , as we known , the service are not view layer !我认为绑定服务不应该有 viewModle,众所周知,服务不是视图层!

By the way, Service need to start/bind,unbind at activity顺便说一句,服务需要在活动中启动/绑定,取消绑定

Finally, I think a easy and not bad way is the LiveData inside AndroidService.最后,我认为一个简单且不错的方法是AndroidService 中的 LiveData。 But not use Livedata to send data, use custom callback method.但不使用 Livedata 发送数据,使用自定义回调方法。 Live data only send the newest data every time, if you need got the complete data also the page is onPause, there is a mistake.( Now, we can use kotlin flow)实时数据每次只发送最新数据,如果您需要完整数据并且页面处于暂停状态,则会出错。(现在,我们可以使用kotlin flow)

Also We not just receive data from ble(bluetooth low energe) device, we also need send data to ble device.此外,我们不仅从 ble(蓝牙低功耗)设备接收数据,我们还需要将数据发送到 ble 设备。

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

How about treating your service like this?像这样对待你的服务怎么样?

在此处输入图像描述

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM