You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 05:39:56