Android如何设置多个同时间触发、各自发送独立通知的闹钟
问题根因分析
- 前台服务通知ID固定导致覆盖:
AlarmService的onStartCommand方法中调用startForeground(1, notification)时使用了固定值1作为通知ID,同一服务多次调用该方法时,同ID的通知会直接覆盖之前的通知,无法生成独立的专属通知,对应闹钟状态也会被覆盖。 - 未传递闹钟唯一标识:启动
AlarmService的Intent没有携带alarmId等唯一参数,就算多个闹钟触发服务调用,也无法区分每个请求对应的闹钟,无法跳转到对应专属的Activity页面。 - PendingIntent Flag存在兼容问题:调用
PendingIntent.getBroadcast时最后参数传了0,Android 12及以上版本要求明确指定PendingIntent的可变性,否则会出现匹配异常,导致同时间的PendingIntent被覆盖。 - 通知通道使用随机值:创建通知时用随机值作为通道ID,Android 8.0及以上系统要求通知通道必须提前创建,随机ID会导致通知无法正常弹出。
修复步骤
1. 修改闹钟调度的PendingIntent配置
确保每个闹钟的PendingIntent唯一,添加明确兼容Flag,同时携带alarmId参数:
public void schedule(Context context) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(context, AlarmBroadcastReceiver.class); // 携带闹钟唯一ID,后续传递给服务 intent.putExtra("alarmId", alarmId); // 适配Android 12+,不需要修改Intent内容用FLAG_IMMUTABLE,需要修改则用FLAG_MUTABLE int flag = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0; PendingIntent alarmPendingIntent = PendingIntent.getBroadcast(context, alarmId, intent, flag); Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(System.currentTimeMillis()); calendar.set(Calendar.HOUR_OF_DAY, hour); calendar.set(Calendar.MINUTE, minute); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); alarmManager.setExact( AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), alarmPendingIntent ); }
2. 修改广播接收器,传递闹钟参数给服务
广播接收器收到触发事件后,将alarmId等参数传递给AlarmService:
private void startAlarmService(Context context, Intent intent) { Intent intentService = new Intent(context, AlarmService.class); // 传递闹钟ID和其他自定义参数 intentService.putExtra("alarmId", intent.getIntExtra("alarmId", -1)); Log.println(Log.DEBUG, "DEBUG", "************Alarm Broadcast Receiver**********"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(intentService); } else { context.startService(intentService); } }
3. 重构AlarmService,支持多闹钟并行处理
@Override public void onCreate() { super.onCreate(); mediaPlayer = MediaPlayer.create(this, R.raw.alarm); mediaPlayer.setLooping(true); vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); } @Override public int onStartCommand(Intent intent, int flags, int startId) { // 获取当前请求对应的闹钟ID int alarmId = intent.getIntExtra("alarmId", -1); // 跳转Activity时携带alarmId,让RingActivity区分是哪个闹钟的操作 Intent notificationIntent = new Intent(this, RingActivity.class); notificationIntent.putExtra("alarmId", alarmId); // 用alarmId作为请求码,保证PendingIntent唯一 int requestCode = alarmId; int flag = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0; PendingIntent pendingIntent = PendingIntent.getActivity(this, requestCode, notificationIntent, flag); // 用alarmId作为通知ID,保证唯一不覆盖 int notificationId = alarmId; // 使用提前创建的固定通知通道ID String channelId = "alarm_notify_channel"; Notification notification = new NotificationCompat.Builder(this, channelId) .setContentTitle("闹钟提醒") .setContentText("你设置的闹钟已触发") .setSmallIcon(R.drawable.pill) .setContentIntent(pendingIntent) .setOngoing(true) // 通知常驻,无法手动划掉 .build(); if(!mediaPlayer.isPlaying()) { mediaPlayer.start(); long[] pattern = { 0, 100, 1000 }; vibrator.vibrate(pattern, 0); } // 用alarmId作为前台服务的通知ID,唯一不覆盖 startForeground(notificationId, notification); return START_STICKY; } @Override public void onDestroy() { super.onDestroy(); mediaPlayer.stop(); vibrator.cancel(); }
4. 补充配置
提前在应用启动时创建ID为alarm_notify_channel的通知通道,Android 8.0及以上系统必须配置该通道才能正常弹出通知;RingActivity中获取传入的alarmId,处理完当前闹钟的关闭/贪睡逻辑后,再判断是否还有未处理的同时间闹钟,确认所有闹钟处理完毕后再停止AlarmService和响铃逻辑。
内容的提问来源于stack exchange,提问作者xdxdxd
相关产品推荐
相关产品推荐

