现代应用架构中的Android Bund服务
#kotlin #android #mobile #tristan

目录

  1. What we are making
  2. Visual Representation
  3. Creating Service
  4. Creating Repository layer

我在Google Playstore上的应用程序

github代码

Youtube 视频

警告

  • 此代码当前有一个错误。如果您在片段之间导航,它将崩溃您的应用程序。如果您正在阅读本文,请不要再阅读。

我们将要制作

  • 在本教程中,我们将学习如何使用Bound Service并将其实施到现代应用架构中。我不会介绍如何设置界限或如何实施一项服务。但是,如果您想在Android中提供更多有关服务的详细信息,请查看最后的服务博客文章,HERE

警告

  • 此代码当前有一个错误。如果您在片段之间导航,它将崩溃您的应用程序。如果您正在阅读本文,请不要再阅读。

免责声明

  • 如果您知道将服务集成到现代应用程序体系结构中的更清洁方法,请在下面评论!!!!!

视觉表示

  • 要从抽象的意义上描述这一点,我们将有一个片段/活动来以明确的意图创建服务。一旦创建了服务,我们将能够通过存储库层访问它,该存储库将与ViewModel交谈,并最终将数据显示到UI组合代码。这听起来可能有些混乱,所以我制作了这张图表,为每个人提供了视觉指南。

Diagram of mvvm data flow

创建服务

  • 我介绍了如何在previous tutorial中创建服务,因此,如果您需要更多澄清,请查看

  • 因此,我们知道创建服务的前两个步骤是实现服务接口并将其注册在您的Android清单文件中。最终,您的服务将看起来像这样:

class BillingService : Service() {
    // Binder given to clients.
    private val binder = LocalBinder()

    // Random number generator.
    private val mGenerator = Random()


    /** Method for clients.  */
    val randomNumber: Int
        get() = mGenerator.nextInt(100)

    inner class LocalBinder : Binder() {
        // Return this instance of LocalService so clients can call public methods

        fun getService(): BillingService = this@BillingService
    }


    //called by the Android system
    // IBinder gets passed to onServiceConnected()
    override fun onBind(intent: Intent): IBinder {

        return binder
    }

}

###WARNING
- THIS CODE CURRENTLY HAS A BUG. IF YOU NAVIGATE BETWEEN FRAGMENTS IT WILL CRASH YOUR APP. IF YOU ARE READING THIS, PLEASE DO NOT READ ANY FURTHER.

  • 在此服务中,您将在其中放置所有通常希望其致电的方法。因此,如果您的服务被迫拨打数据库,则方法将在此类中进行。

创建存储库层

  • 这是事物开始变得有些复杂的地方。主要是因为我们需要存储库来做2件事:
    1)跟踪服务是否活着
    2)与服务互动

  • 那么,我们如何跟踪服务是否活着?答案是custom CoroutineScopeMutableStateFlow和匿名ServiceConnection对象的组合

MutableStateFlow

  • mutableStateFlow被认为是hot flow,这意味着事件在流上始终可用,无论是否有人关注它们。与冷流相反,只有在流中至少一个消费者时,才有可用事件。状态流的消费者(调用收集{})的消费者将直接传递给它们的当前值,以及在消费者处于活动状态时发送到流中的任何新对象。我们的mutableStateFlow将用于确定该服务是否绑定,它看起来像这样:
private val mBound = MutableStateFlow(false)

  • 创建了一个热流,我们可以从中阅读并发出{}

Custom CoroutineScope

  • 因此,每当我们的服务绑定或不绑定时,我们就会遇到更新mBound变量的问题。我们已经知道mutableStateFlow是一个流,我们只需使用emit{}即可向我们的mutableStateFlow发送值。但是,emit{}是一个悬浮功能。这意味着必须在Coroutine范围内完成,但是什么范围?这是一个棘手的问题,因为我们想要一个始终可用的范围,并且只有在应用程序关闭时才会被销毁(因此ViewModelScope将行不通)。我对此的最初反应是使用GlobalsCope,但是在阅读了有关Best practices的信息之后,不建议这样做。这意味着我们必须自己创建它!
  • 重新添加Coroutines & Patterns for work that shouldn’t be cancelled博客文章后,我确定此代码是最好的解决方案:
private val externalScope: CoroutineScope = CoroutineScope(SupervisorJob())

  • 本质上,我们创建的范围非常最小,而SupervisorJob()允许我们的Coroutines分别失败,而不是传播层次结构。

  • 要使事情更容易测试,我们可以简单地将其注入我们的存储库层:

class BillingRepository(
    private val externalScope: CoroutineScope = CoroutineScope(SupervisorJob()) //when injected make this a singleton
) {
// rest of the repository layer code.
}

警告

  • 此代码当前有一个错误。如果您在片段之间导航,它将崩溃您的应用程序。如果您正在阅读本文,请不要再阅读。

ServiceConnection object

  • 服务连接对象是用于监视应用程序服务状态的接口。本质上,我们需要创建一个实现该接口的类的实例。然后将该对象传递给我们的应用程序/片段,这允许Android系统提供有关服务状态的更新。我们可以在存储库中创建一个:
    private lateinit var mService: BillingService
    private val mBound = MutableStateFlow(false)

    fun getServiceConnection() = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance.
            val binder = service as BillingService.LocalBinder
            mService =binder.getService()
            externalScope.launch {
                mBound.emit(true)
            }
        }

        override fun onServiceDisconnected(arg0: ComponentName) {
            externalScope.launch {
                mBound.emit(false)
            }
        }
    }

  • 所以这里有几件事要指出。BillingService来自我以前的教程,您可以检查HERE。请记住,externalScope是我们以前刚创建的自定义范围。
  • 重要的是,我们将此对象放入一个函数中,因为这使我们能够通过层将其传递给活动/片段以进行绑定,这要归功于bindService()方法。

在服务上调用方法。

  • 现在要在服务上调用方法,我们只是在存储库中暂停方法,就像这样:
fun getStuff(): Flow<Int> = flow{
         mBound.collect{ serviceConnected ->
             if(serviceConnected){
                 emit(mService.randomNumber)
             }else{
                 emit(999)
             }

         }
    }
  • 这显然是一种简化的方法,但基本面是相同的。对于服务的每个呼叫,我们都必须首先检查我们的服务是否绑定并返回适当的值。我只是添加了delay(3000)以模仿服务请求。

将数据获取到UI

  • 那么,我们可以与存储库层进行交互,就好像它是普通的存储库层一样。

结论

  • 感谢您抽出宝贵的时间阅读我的博客文章。如果您有任何疑问或疑虑,请在下面发表评论或在Twitter上与我联系。