通过取消绑定所有有界活动来停止服务
Stop a service by unbinding all bounded activities
我有一个启动和绑定的服务(音乐播放器),我想在点击通知的关闭按钮时停止它。但是,如果有活动绑定到该服务,则该服务不会停止。
单击按钮时如何停止服务?
我尝试收集服务中的活动列表并调用它们的回调以解除绑定(实际上 finish()
然后在 onStop()
将调用 unbind()
)然后我停止该服务通过调用 stopSelf()
但我认为这不是一个好主意,因为生命周期问题和一些活动被多次添加并且维护列表很困难。应该有更好的方法!虽然我搜索了几个小时还没有找到任何东西。
这是我的 PlayerService
的 onStartCommand()
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
info("Starting Player Service ...")
started=true
if (intent == null) {
error("intent was null")
pause()
return START_STICKY
}
if (intent.hasExtra(KEY_SONGS)) {
if(intent.hasExtra(KEY_PLAYLIST_ID)) playlistId=intent.getStringExtra(KEY_PLAYLIST_ID)
initExoPlayer(intent)
} else {
info("No new song information found for intent, intent's action: ${intent.action}")
}
if (intent.action == null) intent.action = ""
when (intent.action) {
ACTION_PLAY -> if (isPlaying()) pause() else play()
ACTION_CANCEL -> {
//TODO: implement a better and the correct way
for (client in clients) {
client.onStop()
}
stopSelf()
}
else -> showNotification()
}
return START_STICKY
}
和我的 activity:
playerService.clients.add(object : PlayerService.Client {
override fun onStop() {
finish()
}
})
这是我的自定义通知:
private fun getCustomNotification(name: String, showMainActivity: Intent): Notification {
/*
some codes to prepare notificationLayout
*/
notificationLayout.setTextViewText(R.id.tv_name, name)
notificationLayout.setImageViewResource(R.id.btn_play, playDrawableID)
notificationLayout.setOnClickPendingIntent(R.id.btn_play, playPendingIntent)
notificationLayout.setOnClickPendingIntent(R.id.btn_cancel, cancelPendingIntent)
// Apply the layouts to the notification
return NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.logo_mouj)
.setCustomContentView(notificationLayout)
.setContentIntent(PendingIntent.getActivity(this, 0, showMainActivity, 0))
.setOngoing(true)
.build()
}
试试这个
fun stopServices() {
val serviceIntent = Intent(this, NotificationService::class.java)
serviceIntent.action = Constants.ACTION.STOPTFOREGROUND_ACTION
stopService(serviceIntent)
}
在你的activity中调用这个方法
在您的服务启动命令中
ACTION.STOPFOREGROUND_ACTION -> {
//Toast.makeText(this, "Service Stoped", Toast.LENGTH_SHORT).show();
stopForeground(true);
stopSelf();
Intent intents = new Intent("com.app.package.ACTION_STOP");
LocalBroadcastManager.getInstance(this).sendBroadcast(intents);
//startActivity(new Intent(this,MainActivity.class));
}
val notificationLayout= new RemoteViews(getPackageName(), R.layout.custom_layout);
Intent closeIntent = new Intent(this, NotificationService.class);
closeIntent.setAction(Constants.ACTION.STOPFOREGROUND_ACTION);
PendingIntent cancelPendingIntent= PendingIntent.getService(this, 0, closeIntent, 0);
点击关闭按钮
notificationLayout.setOnClickPendingIntent(R.id.btn_cancel, cancelPendingIntent);
如果这个任务看起来令人沮丧,那可能是因为这是一个不寻常的用例,并且在某种程度上超出了 Android 设计者的想法。您可能想问问自己是否有更多 "Android-like" 方式来实现您的目标(即 无需 枚举绑定客户端)。
考虑您的要求:
- 播放器应该在用户与应用交互时可用(通过
Activity
)
- 当用户在后台运行应用程序时,播放器应该继续存在
- 当用户点击通知时播放器应该停止
- 当用户使用完该应用程序后,应用程序应该得到清理
我认为您可能选择了通过绑定到 Service
来开始播放,并通过 onDestroy()
-ing 结束播放。这是设计错误,它会给你带来很多问题。毕竟,根据 docs,一个本地进程中 Service
实际上只代表 "an application's desire to perform a longer-running operation while not interacting with the user" 而它 "is not a means itself to do work off of the main thread."
相反,通过将每个 Activity
保持在 onStart()
和 onStop()
之间,在播放开始时使用 startService()
,在播放时使用 stopSelf()
停止,您将自动完成目标 1、2 和 4。当应用程序处于所有 Activities
都在屏幕外的状态时,它将有 0 个绑定客户端。如果除此之外,没有 startService()
未完成,则它是 OS 立即终止的候选者。在任何其他状态下,OS 都会保留它。
这没有任何额外的花哨逻辑来跟踪绑定 Activities
。当然,您可能希望在播放器播放时 startForeground()
,在播放结束时 stopForeground()
(大概您已经处理了该细节)。
这留下了如何实现目标 3 的问题。要从 Activities
或通知中影响 Service
中的状态更改,您只需使用 Intents
(就像您已经使用的那样)。在这种情况下,Service
将通过 stopSelf()
管理自己的生命周期。这再次假设您在适当的时间使用 startForeground()
(Oreo+),否则 Android 可能会由于 background execution limits.
而终止您的应用程序
事实上,如果您选择使用 Intents
,您可能会发现您甚至根本不需要将 Activities
绑定到 Service
(IOW,目标#1 在这种情况下无关紧要)。如果您需要直接引用该服务(通过 Binder
),您只需要绑定到 Service
。
我有一个启动和绑定的服务(音乐播放器),我想在点击通知的关闭按钮时停止它。但是,如果有活动绑定到该服务,则该服务不会停止。 单击按钮时如何停止服务?
我尝试收集服务中的活动列表并调用它们的回调以解除绑定(实际上 finish()
然后在 onStop()
将调用 unbind()
)然后我停止该服务通过调用 stopSelf()
但我认为这不是一个好主意,因为生命周期问题和一些活动被多次添加并且维护列表很困难。应该有更好的方法!虽然我搜索了几个小时还没有找到任何东西。
这是我的 PlayerService
的 onStartCommand()
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
info("Starting Player Service ...")
started=true
if (intent == null) {
error("intent was null")
pause()
return START_STICKY
}
if (intent.hasExtra(KEY_SONGS)) {
if(intent.hasExtra(KEY_PLAYLIST_ID)) playlistId=intent.getStringExtra(KEY_PLAYLIST_ID)
initExoPlayer(intent)
} else {
info("No new song information found for intent, intent's action: ${intent.action}")
}
if (intent.action == null) intent.action = ""
when (intent.action) {
ACTION_PLAY -> if (isPlaying()) pause() else play()
ACTION_CANCEL -> {
//TODO: implement a better and the correct way
for (client in clients) {
client.onStop()
}
stopSelf()
}
else -> showNotification()
}
return START_STICKY
}
和我的 activity:
playerService.clients.add(object : PlayerService.Client {
override fun onStop() {
finish()
}
})
这是我的自定义通知:
private fun getCustomNotification(name: String, showMainActivity: Intent): Notification {
/*
some codes to prepare notificationLayout
*/
notificationLayout.setTextViewText(R.id.tv_name, name)
notificationLayout.setImageViewResource(R.id.btn_play, playDrawableID)
notificationLayout.setOnClickPendingIntent(R.id.btn_play, playPendingIntent)
notificationLayout.setOnClickPendingIntent(R.id.btn_cancel, cancelPendingIntent)
// Apply the layouts to the notification
return NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.logo_mouj)
.setCustomContentView(notificationLayout)
.setContentIntent(PendingIntent.getActivity(this, 0, showMainActivity, 0))
.setOngoing(true)
.build()
}
试试这个
fun stopServices() {
val serviceIntent = Intent(this, NotificationService::class.java)
serviceIntent.action = Constants.ACTION.STOPTFOREGROUND_ACTION
stopService(serviceIntent)
}
在你的activity中调用这个方法
在您的服务启动命令中
ACTION.STOPFOREGROUND_ACTION -> {
//Toast.makeText(this, "Service Stoped", Toast.LENGTH_SHORT).show();
stopForeground(true);
stopSelf();
Intent intents = new Intent("com.app.package.ACTION_STOP");
LocalBroadcastManager.getInstance(this).sendBroadcast(intents);
//startActivity(new Intent(this,MainActivity.class));
}
val notificationLayout= new RemoteViews(getPackageName(), R.layout.custom_layout);
Intent closeIntent = new Intent(this, NotificationService.class);
closeIntent.setAction(Constants.ACTION.STOPFOREGROUND_ACTION);
PendingIntent cancelPendingIntent= PendingIntent.getService(this, 0, closeIntent, 0);
点击关闭按钮
notificationLayout.setOnClickPendingIntent(R.id.btn_cancel, cancelPendingIntent);
如果这个任务看起来令人沮丧,那可能是因为这是一个不寻常的用例,并且在某种程度上超出了 Android 设计者的想法。您可能想问问自己是否有更多 "Android-like" 方式来实现您的目标(即 无需 枚举绑定客户端)。
考虑您的要求:
- 播放器应该在用户与应用交互时可用(通过
Activity
) - 当用户在后台运行应用程序时,播放器应该继续存在
- 当用户点击通知时播放器应该停止
- 当用户使用完该应用程序后,应用程序应该得到清理
我认为您可能选择了通过绑定到 Service
来开始播放,并通过 onDestroy()
-ing 结束播放。这是设计错误,它会给你带来很多问题。毕竟,根据 docs,一个本地进程中 Service
实际上只代表 "an application's desire to perform a longer-running operation while not interacting with the user" 而它 "is not a means itself to do work off of the main thread."
相反,通过将每个 Activity
保持在 onStart()
和 onStop()
之间,在播放开始时使用 startService()
,在播放时使用 stopSelf()
停止,您将自动完成目标 1、2 和 4。当应用程序处于所有 Activities
都在屏幕外的状态时,它将有 0 个绑定客户端。如果除此之外,没有 startService()
未完成,则它是 OS 立即终止的候选者。在任何其他状态下,OS 都会保留它。
这没有任何额外的花哨逻辑来跟踪绑定 Activities
。当然,您可能希望在播放器播放时 startForeground()
,在播放结束时 stopForeground()
(大概您已经处理了该细节)。
这留下了如何实现目标 3 的问题。要从 Activities
或通知中影响 Service
中的状态更改,您只需使用 Intents
(就像您已经使用的那样)。在这种情况下,Service
将通过 stopSelf()
管理自己的生命周期。这再次假设您在适当的时间使用 startForeground()
(Oreo+),否则 Android 可能会由于 background execution limits.
事实上,如果您选择使用 Intents
,您可能会发现您甚至根本不需要将 Activities
绑定到 Service
(IOW,目标#1 在这种情况下无关紧要)。如果您需要直接引用该服务(通过 Binder
),您只需要绑定到 Service
。