Android前台服务:同一服务如何实现多通知同时显示?
问题分析
你遇到的问题根源在于:同一个Service实例只能绑定一个前台通知。你的AlarmsService是Android默认的单例模式,每次新闹钟触发时都会复用同一个Service实例,调用startForeground(id, notification)时,哪怕传入不同的ID,系统也会用新通知替换掉该Service当前的前台通知,这是Android系统的固有机制。
解决方案
要实现多个闹钟通知同时显示,你需要为每个独立的闹钟启动一个独立的Service实例,让每个Service维护自己的前台通知。具体步骤如下:
1. 创建单闹钟Service类
新建一个处理单个闹钟的Service,比如SingleAlarmService,每个闹钟触发时启动这个Service的新实例:
package com.example.alarmclock.service import android.app.PendingIntent import android.app.Service import android.content.Context import android.content.Intent import android.os.IBinder import androidx.core.app.NotificationCompat import com.example.alarmclock.R import com.example.alarmclock.common.AlarmSound import com.example.alarmclock.common.Constant.EXTRA_ID import com.example.alarmclock.common.Constant.EXTRA_MESSAGE import com.example.alarmclock.common.Constant.EXTRA_SOUND import com.example.alarmclock.common.Constant.NOTIFICATION_CHANNEL_ID import com.example.alarmclock.common.formatCurrentTime import com.example.alarmclock.presentation.alarm_challenge_screen.ui.AlarmChallengeScreen import java.util.Calendar class SingleAlarmService : Service() { private var alarmSound: AlarmSound? = null override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { val id = intent?.getIntExtra(EXTRA_ID, 0) ?: return START_NOT_STICKY val message = intent.getStringExtra(EXTRA_MESSAGE) ?: "" val soundResId = intent.getIntExtra(EXTRA_SOUND, R.raw.default_sound) val time = Calendar.getInstance().formatCurrentTime() displayNotification( id, this, "Alarm Triggered : $time", "Message: $message", createAlarmChallengeScreenIntent(this, time, id) ) alarmSound?.release() alarmSound = AlarmSound(this, soundResId).apply { startSound() } return START_STICKY } override fun onBind(intent: Intent): IBinder? = null override fun onDestroy() { alarmSound?.release() super.onDestroy() } private fun displayNotification( id: Int, context: Context, title: String, description: String, openAlarmChallengeScreenIntent: PendingIntent ) { val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID).apply { setSmallIcon(R.drawable.baseline_alarm_add_24) setContentTitle(title) setContentText(description) setPriority(NotificationCompat.PRIORITY_HIGH) setCategory(NotificationCompat.CATEGORY_ALARM) setOngoing(true) setFullScreenIntent(openAlarmChallengeScreenIntent, true) }.build() startForeground(id, notification) } private fun createAlarmChallengeScreenIntent( context: Context, time: String, id: Int, ): PendingIntent { val intent = Intent(context, AlarmChallengeScreen::class.java).apply { putExtra(EXTRA_ID, id) putExtra(EXTRA_SOUND, intent?.getIntExtra(EXTRA_SOUND, R.raw.default_sound)) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP) } val requestCode = "openAlarmChallengeScreen$time$id".hashCode() return PendingIntent.getActivity(context, requestCode, intent, PendingIntent.FLAG_MUTABLE) } }
2. 在Manifest中声明新Service
在AndroidManifest.xml中添加这个Service的声明:
<service android:name=".service.SingleAlarmService" android:exported="false" android:stopWithTask="false" />
3. 修改闹钟触发逻辑
将原来启动AlarmsService的代码,改为启动SingleAlarmService,确保每个闹钟都启动一个新的Service实例:
import android.os.Build // 替换原来启动AlarmsService的代码 val intent = Intent(context, SingleAlarmService::class.java).apply { putExtra(EXTRA_ID, alarmId) putExtra(EXTRA_MESSAGE, alarmMessage) putExtra(EXTRA_SOUND, alarmSoundResId) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(intent) } else { context.startService(intent) }
4. 处理闹钟关闭逻辑
当用户关闭某个闹钟时,需要停止对应的SingleAlarmService实例,避免资源浪费:
// 根据闹钟ID停止对应的Service val intent = Intent(context, SingleAlarmService::class.java).apply { putExtra(EXTRA_ID, alarmId) } context.stopService(intent)
额外注意事项
- Android 8.0+ 要求启动前台Service必须使用
startForegroundService,且必须在Service启动后5秒内调用startForeground,否则会触发ANR。 - 确保每个闹钟的
EXTRA_ID是唯一的,这样每个Service实例的前台通知ID也唯一,不会互相干扰。 - Android 13+ 需要申请
POST_NOTIFICATIONS权限才能显示通知,记得在代码中动态申请该权限。
内容的提问来源于stack exchange,提问作者kareem mohamed
相关产品推荐
相关产品推荐

