【问题标题】:AlarmManager is sending in an inconsistent way pending intents with old dataAlarmManager 以不一致的方式发送带有旧数据的未决意图
【发布时间】:2023-04-05 19:08:01
【问题描述】:

我正在尝试开发一个简单的提醒应用程序,当用户创建提醒时,我通过“AlarmManager”类设置了一个警报。为了设置警报,我将提醒的标识符放入数据库中的意图中,然后将该意图放入待处理的意图中。当收到警报时,从收到的意图中取出提醒的标识符并在数据库中搜索。

这种方法对我根本不起作用。通常它表现良好,但有时接收到的意图内部的标识符是旧的标识符,所有应用程序都会失败。这是随机发生的,我找不到一致的方式来重现它。由于这是一个随机失败,我完全迷失了这一点。这完全是荒谬的,因为我在附加意图中收到的标识符不是我设置警报的标识符。相反,它是过去触发的旧警报的标识符。

package bembibre.alarmfix.alarms;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;

import java.util.Calendar;

import bembibre.alarmfix.database.RemindersDbAdapter;
import bembibre.alarmfix.logging.Logger;
import bembibre.alarmfix.utils.GeneralUtils;

/**
 * Created by Max Power on 12/08/2017.
 */

/**
 * Sets alarms in the operating system for the reminders of this application.
 */
public class ReminderManager {

    /**
     * This is the key that identifies a metadata item that is attached to the intent of an alarm of
     * a reminder for tracking it.
     */
    public static final String EXTRA_ALARM_ID = "extra_alarm_id";

    private Context mContext;
    private AlarmManager mAlarmManager;

    public ReminderManager(Context context) {
        mContext = context;
        mAlarmManager =
            (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    }

    /**
     * Part of the code that is responsible for setting an alarm.
     *
     * @param taskId  data base identifier of the reminder.
     * @param alarmId number that helps distinguishing each one of the alarms set for a same reminder.
     * @param when    when.
     */
    public void setReminder(long taskId, long alarmId, Calendar when) throws AlarmException {
        PendingIntent pi = getReminderPendingIntent(taskId, alarmId, PendingIntent.FLAG_UPDATE_CURRENT);

        try {
            this.setAlarm(pi, when);
            Logger.log("An alarm has been set successfully for the reminder at " + GeneralUtils.format(when) + ". Reminder id: " + taskId);
        } catch (Throwable throwable) {
            Logger.log("The system doesn't let us to set an alarm for the reminder at " + GeneralUtils.format(when), throwable);
            throw new AlarmException();
        }
    }

    /**
     * Unsets the alarm that would trigger for the reminder with the given database identifier.
     * When calling this method, the reminder could have been erased from the database and it
     * wouldn't be a problem. This method is only for unsetting its associated alarm from the
     * system.
     *
     * @param taskId  database identifier of the reminder.
     * @param alarmId number that helps distinguishing each one of the alarms set for a same reminder.
     * @param date    date for logging purposes.
     */
    public void unsetReminder(long taskId, long alarmId, String date) {
        PendingIntent pi = getReminderPendingIntent(taskId, alarmId, PendingIntent.FLAG_UPDATE_CURRENT);
        mAlarmManager.cancel(pi);
        Logger.log("An alarm has been unset successfully for the reminder at " + date + ". Reminder id: " + taskId);
    }

    /**
     * Returns the <code>PendingIntent</code> object that must be used for calling this application
     * when a reminder's alarm triggers.
     *
     * @param taskId  the number that identifies the associated reminder in the database.
     * @param alarmId incremental identifier for each alarm of the same reminder.
     * @param flag    flag that controls the behaviour of the pending intent.
     * @return the <code>PendingIntent</code> object.
     */
    private PendingIntent getReminderPendingIntent(long taskId, long alarmId, int flag) {
        Intent i = new Intent(mContext, OnAlarmReceiver.class);
        i.putExtra(RemindersDbAdapter.KEY_ROWID, taskId);
        i.putExtra(ReminderManager.EXTRA_ALARM_ID, alarmId);
        PendingIntent pi = PendingIntent.getBroadcast(mContext, (int)taskId, i, flag);
        return pi;
    }

    /**
     * Sets the alarm in the operating system.
     *
     * @param operation
     * @param when
     */
    private void setAlarm(PendingIntent operation, Calendar when) throws Throwable {
        /*
         * The alarm must be set differently depending on the OS version. Anyway, we need the
         * pending intent in order to know what was the reminder for which the alarm was fired, so
         * then the correct notification will be shown.
         */
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            // Before Marshmallow, we can do this for setting a reliable alarm.
            mAlarmManager.set(AlarmManager.RTC_WAKEUP, when.getTimeInMillis(), operation);
        } else {
            /*
             * Starting from Marshmallow, it seems like this is the only way for setting a reliable
             * alarm.
             * If we use the "alarm clock" framework, the user will see a icon of an alarm clock.
             * If we use the setExactAndAllowWhileIdle the user will see nothing, but the OS can
             * delay alarms at some sort of situations.
             */
            mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, when.getTimeInMillis(), operation);
        }
    }
}

编辑:当警报触发时,标识符是正确的,但是,当我为打开具有所需标识符的应用程序的通知创建另一个待处理意图时,当问题发生时,它就在使用的待处理意图中当我触摸通知时。这是执行此操作的代码:

private void makeNotification(Long rowId, String title, String body) {
    android.app.NotificationManager mgr = (android.app.NotificationManager)context.getSystemService(NOTIFICATION_SERVICE);
    Intent notificationIntent;

    long notificationId;
    if (rowId == null) {
        notificationId = 0;
        notificationIntent = new Intent(context, ReminderListActivity.class);
    } else {
        notificationId = rowId;
        notificationIntent = new Intent(context, ReminderEditActivity.class);
        notificationIntent.putExtra(RemindersDbAdapter.KEY_ROWID, rowId);
    }
    PendingIntent pi = PendingIntent.getActivity(context, 0, notificationIntent,
            PendingIntent.FLAG_ONE_SHOT);

    NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context)
            .setSmallIcon(android.R.drawable.stat_sys_warning)
            .setContentTitle(title)
            .setContentText(body)
            .setContentIntent(pi)
            .setStyle(new NotificationCompat.BigTextStyle().bigText(body));;

    Notification note = mBuilder.build();

    note.defaults |= Notification.DEFAULT_SOUND;
    note.flags |= Notification.FLAG_AUTO_CANCEL;

    // An issue could occur if user ever enters over 2,147,483,647 tasks. (Max int value).
    // I highly doubt this will ever happen. But is good to note.
    int id = (int)((long)notificationId);
    mgr.notify(id, note);
    NotificationManager.setNotified(context, id);
}

编辑:我发现至少有一种情况我总是可以重现失败。步骤:

  • 为当前日期创建提醒。
  • 等待通知,因为它设置为当前日期,它几乎会立即到来。
  • 删除提醒。
  • 放弃通知(不要进入其中)。
  • 为当前日期创建另一个提醒。
  • 等待通知。
  • 通过单击进入通知内部:失败 转载。

【问题讨论】:

  • 请接受您自己的答案。这将从未回答的问题列表中删除该问题。

标签: android android-intent alarmmanager android-pendingintent


【解决方案1】:

你的答案是好的。作为替代方案,您也可以将代码更改为使用FLAG_UPDATE_CURRENT 而不是FLAG_ONE_SHOT

PendingIntent pi = PendingIntent.getActivity(context, 0, notificationIntent,
        PendingIntent.FLAG_UPDATE_CURRENT);

这将覆盖现有PendingIntent 中的“附加”。

【讨论】:

  • 问题是,在我的情况下,我可以一次在状态栏中有多个通知,每个通知都有独立的数据。并且每个通知都必须有自己不同的附加信息,而不会干扰其他通知意图的附加信息。
  • 如果您需要同时拥有多个通知,那么是的,您不能使用此解决方案。您需要确保每次都创建一个唯一的PendingIntent,您可以使用唯一的requestCode 来执行此操作,正如您在自己的答案中所指出的那样。
【解决方案2】:

关于重现我上面发布的失败的步骤,似乎当我放弃通知而不进入通知时,它的待处理意图仍然存在,所以当我创建另一个提醒时,系统使用旧的待处理意图和里面有旧的意图,它又得到了旧的标识符。

问题似乎是通过在方法PendingIntent.getActivity()(requestCode)的第二个参数中传递一个不同的数字来解决的,所以所有待处理的意图都是不同的,系统总是创建一个新的而不是尝试使用旧的。作为请求代码,最好的方法是使用提醒的数据库标识符,因此,每个提醒都会收到通知,并带有自己的待处理意图。

【讨论】:

    猜你喜欢
    • 2017-03-07
    • 2013-11-18
    • 1970-01-01
    • 2021-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-15
    • 1970-01-01
    相关资源
    最近更新 更多