AlarmManager以不一致的方式发送待处理的旧数据

时间:2017-12-11 16:44:42

标签: android android-intent alarmmanager android-pendingintent

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

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

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);
}

编辑:我发现至少有一个案例,我总能重现失败。步骤进行:

  • 为当前日期创建提醒。
  • 等待通知,因为它是为当前日期设置的,它几乎会立即出现。
  • 删除提醒。
  • 放弃通知(不进入内部)。
  • 为当前日期创建另一个提醒。
  • 等待通知。
  • 点击通知进入通知内部:失败 再现。

2 个答案:

答案 0 :(得分:0)

关于重现上面发布的失败的步骤,似乎当我放弃通知而没有进入它时,它的未决意图仍然存在,所以当我创建另一个提醒时,系统使用旧的待定意图其中的旧意图,反过来又有旧的标识符。

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

答案 1 :(得分:0)

你的答案没问题。您也可以将代码更改为使用FLAG_UPDATE_CURRENT而不是FLAG_ONE_SHOT

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

这将覆盖现有PendingIntent中的“额外内容”。