目录
我在Google Playstore上的应用程序
github代码
Youtube 视频
警告
- 此代码当前有一个错误。如果您在片段之间导航,它将崩溃您的应用程序。如果您正在阅读本文,请不要再阅读。
我们将要制作
- 在本教程中,我们将学习如何使用Bound Service并将其实施到现代应用架构中。我不会介绍如何设置界限或如何实施一项服务。但是,如果您想在Android中提供更多有关服务的详细信息,请查看最后的服务博客文章,HERE
警告
- 此代码当前有一个错误。如果您在片段之间导航,它将崩溃您的应用程序。如果您正在阅读本文,请不要再阅读。
免责声明
- 如果您知道将服务集成到现代应用程序体系结构中的更清洁方法,请在下面评论!!!!!
视觉表示
- 要从抽象的意义上描述这一点,我们将有一个片段/活动来以明确的意图创建服务。一旦创建了服务,我们将能够通过存储库层访问它,该存储库将与ViewModel交谈,并最终将数据显示到UI组合代码。这听起来可能有些混乱,所以我制作了这张图表,为每个人提供了视觉指南。
创建服务
-
我介绍了如何在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 CoroutineScope
,MutableStateFlow
和匿名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上与我联系。