Alarm Manager broadcasts unexpectedly

2019-08-17 23:50发布

问题:

I have a application which should fire up an alarm at a specified time stored in the database. The time is stored like (HH:mm) format, 24-hour format.

Here is the code.

SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
String temp = h+":"+m;
Date date = sdf.parse(temp);
alarmMgr = (AlarmManager)_c.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(_c, AlarmBroadcastReciever.class);
alarmIntent = PendingIntent.getBroadcast(_c, requestCode, intent, 0);
alarmMgr.set(AlarmManager.RTC_WAKEUP,Calendar.getInstance().getTimeInMillis()+  date.getTime(), alarmIntent);

_c: context variable h is the hour (24-hour format) and m is the minutes.

I wanted to fire the alarm at 4:30 am but it got fired immediately at 03:09:35 AM.

How to schedule alarms only one time at that specific time (no-repeating)?

PS: I am tending towards using alarms because the application needs repeating alarms all at dynamic times based on database queries.

回答1:

    int h = 4;
    int m = 30;
    LocalTime alarmTime = LocalTime.of(h, m);
    ZoneId zone = ZoneId.of("Asia/Kolkata");
    long alarmTimeMillis = LocalDate.now(zone)
            .atTime(alarmTime)
            .atZone(zone)
            .toInstant()
            .toEpochMilli();
    System.out.println(alarmTimeMillis);
    System.out.println(Instant.ofEpochMilli(alarmTimeMillis));

When I ran this today, the output was

1522882800000
2018-04-04T23:00:00Z

To check whether the millisecond value of 1 522 882 800 000 was correct I converted it to an Instant. Instants always print in UTC, so the time printed agrees with 4:30 AM in Asia/Kolkata time zone (offset +05:30). You will probably want to add a check to see if the time is already passed, and if so, add one day so the alarm happens at the specified time tomorrow. LocalDate and some of the other classes have a plusDays method for that.

If you get your alarm time as a string like 04:30 from the database, parse it like this:

    String alarmTimeString = "04:30";
    LocalTime alarmTime = LocalTime.parse(alarmTimeString);

The rest is the same.

I am using and recommending java.time, the modern Java date and time API. Because SimpleDateFormat is notoriously troublesome, and its friends Date and Calendar are long outdated too. The modern API is so much nicer to work with.

What went wrong in your code?

There are two bugs in your calculation of the alarm time:

  1. While your parsing produces a Date that prints as for example Thu Jan 01 04:30:00 IST 1970 and thus looks right, this is deceiving. The 04:30:00 is in your local time zone. When you use date.getTime(), you get the number of milliseconds since the epoch, and the epoch is defined in UTC, not in your time zone. For example, I used Asia/Kolkata time zone and got milliseconds of -3 600 000, equal to minus 1 hour.
  2. Calendar.getInstance().getTimeInMillis() gives the time now. When adding your milliseconds to this time, you specify an alarm time that number of milliseconds from now — in my example an alarm time 1 hour ago.

Question: Can I use java.time on Android?

Yes, java.time works nicely on older and newer Android devices. It just requires at least Java 6.

  • In Java 8 and later and on newer Android devices the modern API comes built-in.
  • In Java 6 and 7 get the ThreeTen Backport, the backport of the new classes (ThreeTen for JSR 310; see the links at the bottom).
  • On (older) Android use the Android edition of ThreeTen Backport. It’s called ThreeTenABP. And make sure you import the date and time classes from org.threeten.bp with subpackages.

Links

  • Oracle tutorial: Date Time explaining how to use java.time.
  • Java Specification Request (JSR) 310, where java.time was first described.
  • ThreeTen Backport project, the backport of java.timeto Java 6 and 7 (ThreeTen for JSR-310).
  • ThreeTenABP, Android edition of ThreeTen Backport
  • Question: How to use ThreeTenABP in Android Project, with a very thorough explanation.