Android Studio 中第二个 notification 覆盖第一个
结论
可以在 onReceive() 中使用 if 或 switch,但无法直接读取 PendingIntent 的 requestCode。101 和 102 只用于区分 PendingIntent,系统不会自动将它们写入接收到的 Intent。
可以按下面的方式处理:
- 创建用于触发
AlarmReceiver的PendingIntent时,通过Intent.setAction()或putExtra()标记事件类型。 - 在
onReceive()中读取标记,只调用对应的通知方法。 - 为两个通知设置不同的 notification ID。当前两处都使用
notify(1, notification),第二个通知会更新或覆盖第一个。 - Android 8.0 及以上版本需要在调用
build()前设置 channel ID。
PendingIntent 的 request code、notification ID 和 notification channel ID 是三种不同的标识,不能混用。
推荐实现
先定义两个广播 action 和两个 notification ID:
public class AlarmReceiver extends BroadcastReceiver {
public static final String ACTION_BILL_DUE =
"com.example.app.ACTION_BILL_DUE";
public static final String ACTION_PAYMENT_SUCCESS =
"com.example.app.ACTION_PAYMENT_SUCCESS";
private static final String CHANNEL_ID = "bills.channelId";
private static final int NOTIFICATION_ID_BILL_DUE = 101;
private static final int NOTIFICATION_ID_PAYMENT_SUCCESS = 102;
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null) {
return;
}
String action = intent.getAction();
if (ACTION_BILL_DUE.equals(action)) {
userHasBill(context);
} else if (ACTION_PAYMENT_SUCCESS.equals(action)) {
userPaymentIsSuccessful(context);
}
}
// ...
}
设置提醒时,将不同的 action 写入发送给 AlarmReceiver 的 Intent。
账单到期提醒:
Intent intent = new Intent(context, AlarmReceiver.class);
intent.setAction(AlarmReceiver.ACTION_BILL_DUE);
PendingIntent billAlarmIntent = PendingIntent.getBroadcast(
context,
101,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
billDueTimeMillis,
billAlarmIntent
);
付款成功通知:
Intent intent = new Intent(context, AlarmReceiver.class);
intent.setAction(AlarmReceiver.ACTION_PAYMENT_SUCCESS);
PendingIntent paymentIntent = PendingIntent.getBroadcast(
context,
102,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
context.sendBroadcast(intent);
如果付款成功事件也需要由 AlarmManager 定时触发,可以同样使用 PendingIntent.getBroadcast() 和相应的闹钟 API。
创建通知
两种通知的大部分逻辑相同,可以合并到一个公共方法中:
private void userHasBill(Context context) {
showNotification(
context,
NOTIFICATION_ID_BILL_DUE,
101,
"Bill Reminder!",
"Just a reminder that you have a bill due!"
);
}
private void userPaymentIsSuccessful(Context context) {
showNotification(
context,
NOTIFICATION_ID_PAYMENT_SUCCESS,
102,
"Payment successful!",
"Your payment was successful!"
);
}
private void showNotification(
Context context,
int notificationId,
int contentRequestCode,
String title,
String message
) {
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(
Context.NOTIFICATION_SERVICE
);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
"Bills",
NotificationManager.IMPORTANCE_DEFAULT
);
notificationManager.createNotificationChannel(channel);
}
Intent notificationIntent =
new Intent(context, MainMenuActivity.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addParentStack(MainMenuActivity.class);
stackBuilder.addNextIntent(notificationIntent);
PendingIntent contentIntent = stackBuilder.getPendingIntent(
contentRequestCode,
PendingIntent.FLAG_UPDATE_CURRENT
| PendingIntent.FLAG_IMMUTABLE
);
NotificationCompat.Builder builder =
new NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(title)
.setContentText(message)
.setContentIntent(contentIntent)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
notificationManager.notify(notificationId, builder.build());
}
这里为两种通知设置了不同的 notification ID:
notificationManager.notify(101, billNotification);
notificationManager.notify(102, paymentNotification);
这样两个通知可以同时存在。如果希望新的同类通知更新旧通知,可以让每种通知继续使用固定 ID。例如,所有账单提醒都使用 101,所有付款成功通知都使用 102。
为什么原代码总显示第二个通知
原来的 onReceive() 每次收到广播后,都会依次执行:
userHasBill(context, intent);
userPaymentIsSuccessful(context, intent);
两个方法最后又都调用了:
notificationManager.notify(1, notification);
在 NotificationManager 看来,它们的 notification ID 都是 1。第一个通知发布后,第二个通知立即使用同一个 ID 更新它,因此最终只能看到付款成功通知。
修改 channel ID 无法解决这个问题。Channel 控制的是通知类别的声音、振动和重要程度,不是单条通知的唯一标识。
也可以使用 Extra
如果不想使用 action,也可以在触发广播时写入事件类型:
public static final String EXTRA_NOTIFICATION_TYPE = "notification_type";
public static final int TYPE_BILL_DUE = 101;
public static final int TYPE_PAYMENT_SUCCESS = 102;
Intent intent = new Intent(context, AlarmReceiver.class);
intent.putExtra(EXTRA_NOTIFICATION_TYPE, TYPE_BILL_DUE);
接收广播时读取并判断这个值:
@Override
public void onReceive(Context context, Intent intent) {
int type = intent.getIntExtra(EXTRA_NOTIFICATION_TYPE, -1);
switch (type) {
case TYPE_BILL_DUE:
userHasBill(context);
break;
case TYPE_PAYMENT_SUCCESS:
userPaymentIsSuccessful(context);
break;
default:
break;
}
}
需要注意的是,Intent 的 extras 不参与 PendingIntent 的身份匹配。如果两个广播只有 extras 不同,仍应使用不同的 request code,或者设置不同的 action。
其他注意事项
setChannelId()必须在build()之前调用。原代码先执行build(),之后才调用builder.setChannelId(),这不会修改已经构建好的Notification。- 建议使用
NotificationCompat.Builder,以便统一处理不同的 Android 版本。 - Android 12 及以上版本通常要求为
PendingIntent明确指定FLAG_IMMUTABLE或FLAG_MUTABLE。这里不需要修改其内容,因此使用FLAG_IMMUTABLE。 - Android 13 及以上版本在发布通知前还需要获得
POST_NOTIFICATIONS权限,否则即使接收器正常执行,通知也可能无法显示。 TaskStackBuilder.addParentStack()通常应传入最终打开 Activity 的父级关系,例如MainMenuActivity.class。具体配置取决于AndroidManifest.xml中定义的 Activity 层级。
备注:内容仅供参考。
