I am have been reading over this tutorial for creating repeating apps:
http://rdyonline.net/android-bytes-alarms/
I have followed the instructions and it is working beautifully. However I don't like using something if I don't understand how it works.
Alarm manager is using an version >= 19 so instead of alarm repeating (exact) it requires a one off alarm that is reset on exiting the intent.
Now as I said it is working, Every 15 minutes it is going off (in my version). I can see that they are bundling data with the intent, but I really have no understanding what is re-triggering the single shot alarm.
This is their code:
Repeating alarms
If you’re targeting any Android version before API 19 (KitKat), or,
you don’t need them to be exact then repeating alarms are nice and
easy. All you need in this case is to use the setRepeating call.
In some cases, it will be important that you set a repeating alarm
that is accurate, I’ll go in to a little more detail on how to handle
this.
The trick here is to make sure you schedule the next alarm once the
previous alarm goes off. You’ll have to check whether the alarm you
have set is intended to be repeated and also make sure the platform
you’re running on is above API 19
#Override
public void onReceive(android.content.Context context,
android.content.Intent intent) {
WrappedAlarmManager am = new WrappedAlarmManager(context);
Bundle extras = intent.getExtras();
if (am.isSingleAlarm(extras)) {
Toast.makeText(context, "Single alarm", Toast.LENGTH_SHORT).show();
} else if (am.isRepeatAlarm(extras)) {
Toast.makeText(context, "Repeat alarm", Toast.LENGTH_SHORT).show();
if (android.os.Build.VERSION.SDK_INT >= 19) {
am.scheduleRepeatingAlarm(context);
}
}
}
A quick check to see if it’s a repeating alarm and then the repeating
alarm is scheduled again. Below are the two pertinent methods to deal
with this logic:
public boolean isRepeatAlarm(Bundle extras) {
return extras.containsKey(KEY_REPEAT) && extras.getBoolean(KEY_REPEAT);
}
public void scheduleRepeatingAlarm(Context context) {
Intent intent = new Intent(context, NotificationReceiver.class);
Bundle extras = new Bundle();
extras.putBoolean(KEY_REPEAT, true);
intent.putExtras(extras);
PendingIntent pIntent = PendingIntent.getBroadcast(context,
REPEAT_ALARM_ID, intent, PendingIntent.FLAG_CANCEL_CURRENT);
Calendar futureDate = Calendar.getInstance();
futureDate.add(Calendar.SECOND, (int) (INTERVAL_SEVEN_SECONDS / 1000));
if (android.os.Build.VERSION.SDK_INT >= 19) {
setSingleExactAlarm(futureDate.getTime().getTime(), pIntent);
} else {
mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, futureDate
.getTime().getTime(), INTERVAL_SEVEN_SECONDS, pIntent);
}
}
Thanks for your help
To schedule a repeating alarm in SDK versions >= 19, when alarm broadcast is received , the boolean value KEY_REPEAT is checked and if it is true then a single exact alarm is scheduled for INTERVAL_SEVEN_SECONDS / 1000 seconds later.
After INTERVAL_SEVEN_SECONDS / 1000 seconds later the broadcast is again received and next alram is set.
This receiving broadcast and scheduling next alarm cycle repeats continuously.
Related
I'm trying to code a block of code that has to wake up a few times a day and notify a server. I'm trying to use an alarm intent and a broadcast receiver but the receiver is being rapidly triggered infinitely and I can't seem to stop this.
All of my code sits in one file. The process flow is simple.
Wake up on boot, check if we should communicate, attempt to communicate else set up one of two waiting conditions, activate alarm.
Wake up on alarm, attempt to communicate, re-activate alarm if necessary, otherwise kill it.
When I build and deploy this apk on my device the following process flow happens:
reboot
receiver receives boot intent just fine
alarm gets scheduled
alarm intent gets triggered after 80 seconds as intended
then after the next 80 seconds,
then log-cat shows the broadcast receiver being triggered very rapidly. Several times a second as if its being spammed.
I am completely baffled at why its behaving like this
private PendingIntent pendingIntent;
/**
* Receive a signal, in our case, the device has booted up
* #param context The Context in which the receiver is running.
* #param intent The Intent being received.
*/
#SuppressLint("UnsafeProtectedBroadcastReceiver")
#Override
public void onReceive(Context context, Intent intent) {
Log.d("autostart", "broadcast received");
if(intent.getAction()==null)return;
Intent alarmIntent = new Intent(context, autostart.class);
alarmIntent.setAction("device.activation.alarm");
pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);//cancel flag seems to be ignored
cancel(context);//cancel command seems to be ignored
if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
// Set the alarm here.
Log.d("autostart","We have booted");
RegisterActivation registerActivation = new RegisterActivation(context);
if(registerActivation.AlreadyActivated())
return;//no need
if(registerActivation.ActivationPending()){
//perform regular activation
return;
}
if(registerActivation.canComplete()){
boolean success = registerActivation.sendActivation();//talk to server
if(success) {
registerActivation.markCompleted();
cancel(context);
}
else {
registerActivation.markFileWaiting();
startPending(context);
}
return;
}
if(registerActivation.shouldWait()){//if can complete fails, then shouldWait will immediately return true
Log.d("autostart", "waiting");
registerActivation.markFileSimWait();
startWait(context);
return;
}
}
if(intent.getAction().equals("device.activation.alarm")){
Log.d("autostart","alarm triggered");
cancel(context);
RegisterActivation registerActivation = new RegisterActivation(context);
if(registerActivation.AlreadyActivated()){//for now always false
cancel(context);
return;
}
if(registerActivation.ActivationPending()){//for now always false
//same as before
return;
}
if(registerActivation.canComplete()){//glitch happens here
if(registerActivation.sendActivation()){
registerActivation.markCompleted();
cancel(context);
}else{
registerActivation.markFileWaiting();
startPending(context);//this immediatly triggers the broadcast recieve
}
return;
}
if(registerActivation.shouldWait()){
registerActivation.markFileSimWait();
startWait(context);
}
}
}
public void startPending(Context context) {
AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
int interval = 80000;//will later become 4 hours
manager.set(AlarmManager.RTC_WAKEUP,interval,pendingIntent);
Log.d("autostart", "alarm activated");
}
public void startWait(Context context) {//same function but different time interval
AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
int interval = 90000;// will later become 12 hours
manager.set(AlarmManager.RTC_WAKEUP, interval, pendingIntent);
}
public void cancel(Context context) {
AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
manager.cancel(pendingIntent);
}
thank you to Mike M.
https://stackoverflow.com/users/2850651/mike-m
I'd mark your comment as the answer but you only commented
I went from
manager.set(AlarmManager.RTC_WAKEUP,interval,pendingIntent);
to:
manager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+interval, interval, pendingIntent);
I had to add the interval to the intervalMillis paramater.
And as tested, the manager.set(...) function is really not responding all too well to what I want it to do so I just have to trigger a cancel at the start.
Thanks again Mike :)
I have to run a receiver which receives action USER_PRESENT only for a particular duration on specific days of the week. Here the duration and weekdays are selected by user.
What I have tried is using Preferences with AlarmManager to achieve this and I would very much like to use something other than Alarms with Preferences to achieve this as It becomes too difficult to test alarms with weekly alarms that runs after user selected duration and for user selected week days.
Is there any other way I can do this work other than using Alarms and Preferences. A code sample would really helpful !!
For more details here is my approach using Alarms with Preferences :
Now Firstly I calculate the start time by letting user choose the hour and minutes through a DialogFragment where a TimePickerDialog is inflated so that user can choose the starting time and I get the hrs and min in the onTimeSet() callback and then I find out the start time for the receiver to go off.
Code Snippet goes something like this for calculating start time in millis from hrs and min:
Calendar calSet = Calendar.getInstance();
//setting alarm from current day so that it starts from today onwards
int day = calSet.get(Calendar.DAY_OF_WEEK);
calSet.set(Calendar.DAY_OF_WEEK, day);
calSet.set(Calendar.HOUR_OF_DAY, hrs);
calSet.set(Calendar.MINUTE, min);
calSet.set(Calendar.SECOND, 0);
calSet.set(Calendar.MILLISECOND, 0);
Long milliseconds = calSet.getTimeInMillis();
//check if the time is already passed
Long daily = 24L * 60L * 60L * 1000L;
if (milliseconds < System.currentTimeMillis()) {
//if already passed then push it for next day by adding just 24 hrs
milliseconds = milliseconds + daily;
}
And then I save this calculated time in millis in a preference say : SharedPreferences.Editor.putLong("PeriodicLockStartTimeInMillis", milliseconds);
Now I store the days user has selected using checkBoxes and setting preferences for each day's checkbox
SharedPreferences.Editor.putBoolean("DAYNAME", true);
also storing the duration for which the user wants the receiver to work:
SharedPreferences.Editor.putLong("LockDurationInMillis", minutesinmillis);
Then Using AlarmManager to set an alarm which will set a BroadcastReceiver whose name here is PeriodicLockServiceas an PendingIntent that will hit its receiver .
Code for setting alarm here :
Intent reminderIntent = new Intent(getActivity(), PeriodicLockService.class);
reminderIntent.setAction("ACTION_REPEATING_ALARM_RECEIVER");
pendingIntent = PendingIntent.getBroadcast(getActivity(), PeriodicLockService.REPEATING_ALARM_UNIQUE_ID, reminderIntent, PendingIntent.FLAG_UPDATE_CURRENT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, milliseconds, pendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, milliseconds, pendingIntent);
}
Now in PeriodicLockService when onReceive is hit then I firstly check if user had set things to run for today by using the preference as :
//Fetching today's day from Calendar to compare if user has set lock for today
Calendar calendar = Calendar.getInstance();
int day = calendar.get(Calendar.DAY_OF_WEEK);
switch (day) {
case Calendar.SUNDAY:
if (Preferences.getBooleanPreference(context, SUN_DAY)) {
startLockNow(context);
}
break;
case Calendar.MONDAY:
if (Preferences.getBooleanPreference(context, MON_DAY)) {
startLockNow(context);
}
break;
case Calendar.TUESDAY:
if (Preferences.getBooleanPreference(context, TUES_DAY)) {
startLockNow(context);
}
break;
case Calendar.WEDNESDAY:
if (Preferences.getBooleanPreference(context, WED_DAY)) {
startLockNow(context);
}
break;
case Calendar.THURSDAY:
if (Preferences.getBooleanPreference(context, THURS_DAY)) {
startLockNow(context);
}
break;
case Calendar.FRIDAY:
if (Preferences.getBooleanPreference(context, FRI_DAY)) {
startLockNow(context);
}
break;
case Calendar.SATURDAY:
if (Preferences.getBooleanPreference(context, SAT_DAY)) {
startLockNow(context);
}
break;
}
private void startLockNow(Context context) {
Long lockStartTimeInMillis = Preferences.getLongPreference(context, "PeriodicLockStartTimeInMillis");
//Update Unlock Time
Long LockDurationInMillis = Preferences.getLongPreference(context, "LockDurationInMillis"); //End time to stop the Receiver for action USER_PRESENT
Long newEndTime = lockStartTimeInMillis + LockDurationInMillis;
//Set Unlocked notification broadcast which also disables the receiver for action `USER_PRESENT`
Intent intent = new Intent(context, FinalUnlockedBroadcast.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, newEndTime + 1000, pendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, newEndTime + 1000, pendingIntent);
}
//update the time for next lock by adding a day
milliseconds = Preferences.getLongPreference(context, "PeriodicLockStartTimeInMillis") + 24L * 60L * 60L * 1000L;
Intent reminderIntent = new Intent(context, PeriodicLockService.class);
reminderIntent.setAction("ACTION_REPEATING_ALARM_RECEIVER");
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, REPEATING_ALARM_UNIQUE_ID, reminderIntent, PendingIntent.FLAG_UPDATE_CURRENT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, milliseconds , pendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, milliseconds , pendingIntent);
}
}
But the thing is this doesn't always seem to work and its difficult to get logs from users devices running my app.
Is there any other way I can do this work other than using Alarms and Preferences
Android-Job Android-Job repo
Android-Job abstracts away which implementation you want to use to perform background work.
Depending on the requirements, this library decides which API to use to run your job.
It provides a superset of all the features from JobScheduler, GCMNetworkManager and AlarmManager.
All features from Android Nougat are backward compatible.
Less boilerplate.
Implementing Android-Job is super easy.
The API includes below classes/interfaces.
Job : Your jobs need to extend this class and override onRunJob method. The heavy lifting is done here. You must return a Result from this method so that the system knows whether to attempt to run your job at a later time.
JobRequest: You can schedule a Job by creating a JobRequest using its builder constructor and passing your Job tag.
JobCreator: JobCreator acts like a factory to provide a Job based on a job tag. Your concrete JobCreator class must implement the JobCreator interface and override the create method.
JobManager: The JobManager class serves as the entry point. Before using this class you must initialize this as singleton. JobManager takes a Context. After creating the instance, you have to add your JobCreator to JobManager.
if u interested to read more plz take alook to this awesome article Easy Job Scheduling with Android-Job
thanks for Rajesh Pattanaik the person who wrote this article
I would recommend you Firebase Job Dispatcher. It is a library for scheduling background jobs in your Android app. It provides a JobScheduler-compatible API that works on all recent versions of Android (API level 9+) that have Google Play services installed.
For instructions on how to use it in your app, click here.
You can use Job Scheduler
public class MyJobService extends JobService {
#Override
public boolean onStartJob(JobParameters params) {
Toast.makeText(this, "testing", Toast.LENGTH_LONG).show();
Log.i("sid", "Job scheduler called");
jobFinished(params, true);
return true;
}
#Override
public boolean onStopJob(JobParameters params) {
return false;
}
}
Call the below method in your activity
private void constructJob(){
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, new ComponentName(this, MyJobService.class));
builder.setMinimumLatency(60000)
.setBackoffCriteria(10000,JobInfo.BACKOFF_POLICY_LINEAR)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
.setPersisted(true);
mJobScheduler.schedule(builder.build());
}
Make sure you call builder.setPersisted(true) in order to run the service even after reboot. Another thing to have a look is that this code will work fine on Android N (API 24) or above APIs, however, for API 21 - 23 it call builder.setPeriodic(60000) instead of builder.setMinimumLatency(60000)
UPDATE
In order to run Job Scheduler on API 15 and above use JobSchedulerCompat.
Add below dependency in your gradle file -
compile 'me.tatarka.support:jobscheduler:0.1.1'
In my app, I use the AlarmManager class to set an alarm. To trigger the alarm after the mobile is rebooted I have used BroadcastReceiver. All works fine and my alarm is triggered at regular intervals. Now the problem arises in this case :
Suppose my current time is 2:30 pm and I set my alarm at 2:35 pm. After that, I switch off the mobile. After an hour when I switch on my mobile, no alarm is pop-up as the time on which the alarm is set. This is happening because the current time exceeds the time on which I set the alarm. To solve this issue what should I do. I have posted my code for setting alarm in the AlarmManager class. Please help me to solve this out
public class AlarmReceiver extends BroadcastReceiver {
#SuppressWarnings("static-access")
#Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
Intent myIntent = new Intent(context, MyAlarmService.class);
PendingIntent pendingIntent = PendingIntent.getService(context, i, myIntent, i);
AlarmManager alarmManager = (AlarmManager)context.getSystemService(context.ALARM_SERVICE);
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.add(Calendar.MILLISECOND, (int) Utilities.diff(NoteManager.getSingletonObject().getAlarmTime(i)));
alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
}
}
}
public static long diff(Date date) {
long difference = 0;
try {
// set current time
Calendar c = Calendar.getInstance();
difference = date.getTime() - c.getTimeInMillis();
if (difference < 0) {
// if difference is -1 - means alarm time is of previous time then current
// then firstly change it to +positive and subtract form 86400000 to get exact new time to play alarm
// 86400000-Total no of milliseconds of 24hr Day
difference = difference * -1;
difference = 86400000 - difference;
}
}
catch (Exception e) {
e.printStackTrace();
}
return difference;
}
In The Manifest File
<receiver android:name=".AlarmReciever">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
better way is to store that alarm details in database and retrieve it on boot via broadcast receiver as you are saying you implemented one. once notified remove the details from the database. this way u can track all your alarms. even you can start a Service on startup and do this operation
The Alarm app in the Android does the same, if your phone is switched off and there is Alarm to ring up, It will make your phone switch On , ring the alarm and go to sleep again.
Here is the link of source of Alarm app Git_Alarm app you can download it and see how it is doing this.
and if you are doing something else in your alarm reciever then to ring Alarm up. you can basically set alarmreciever again in the phone Boot up, here is the one answer which may help you Alarm problem if phone is switched off
Edit :- one link was broken, replaced it
I'm trying to check if my alarm is active or not. The alarmIsSet method will return false before the alarm is set, true when the alarm is set. So far so good, however, after the alarm i canceled alarmIsSet will continue to return true until I reboot the device.
How do I fix this?
public class Alarm extends Activity {
private Intent intent = new Intent("PROPOSE_A_TOAST");
private void alarm (boolean activate) {
AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, intent, 0);
if(activate == true) {
int type = AlarmManager.ELAPSED_REALTIME_WAKEUP;
long interval = 3000;
long triggerTime = SystemClock.elapsedRealtime();
am.setRepeating(type, triggerTime, interval, pi);
} else {
am.cancel(pi);
}
}
private boolean alarmIsSet() {
return PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_NO_CREATE) != null;
}
}
You just have to add
pi.cancel();
after
am.cancel(pi);
After having some headaches with this stuff myself, I found out that if I somehow had created a pending intent while testing stuff, that it actually was not cleared between tests. Even killing the app didn't do it. The intent still stayed in the system and kept returning true when checking for it. I actually had to write some code to kill it before it tested right.
Easiest way is to check the values of the (date and) time in the alarm variable, if it is not the same value as when an alarm has not been set (for you to check once what that is) then it would indicate the alarm is active and at the time of the check in the program it is either a time that has passed and the alarm has sounded or it is a time that is yet to arrive and the alarm has not yet sounded or gone off. Note that the rules may permit only one alarm activation per device session before a reboot or power off or every 12 or 24 hours and that could be why the status is not cleared.
I changed AlarmController.java in ApiDemo a little bit, so I want the alarm not to go off when the phone is sleeping by using AlarmManager.RTC.
Intent intent = new Intent(AlarmController.this, RepeatingAlarm.class);
PendingIntent sender = PendingIntent.getBroadcast(AlarmController.this,
0, intent, 0);
// We want the alarm to go off 30 seconds from now.
long firstTime = SystemClock.elapsedRealtime();
firstTime += 15*1000;
// Schedule the alarm!
AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
am.setRepeating(AlarmManager.RTC, //AlarmManager.ELAPSED_REALTIME_WAKEUP,
firstTime, 15*1000, sender);
The receiver code is like below:
public class RepeatingAlarm extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
Log.d("DEBUG", "In RepeatingAlarm.onReceive, intent=" + intent);
Toast.makeText(context, R.string.repeating_received, Toast.LENGTH_SHORT).show();
}
}
I ran the modified app, but I still see many log messages as below after the phone wento sleep (the screen was black):
D/DEBUG ( 1390): In RepeatingAlarm.onReceive, intent=Intent { flg=0x4 cmp=com.example.android.apis/.app.RepeatingAlarm (has extras) }
This means the flag AlarmManager.RTC didn't work. Can someone tell me why?
Thanks.
Since you are using elapsedRealtime to get the alarm start time, I think you need to use the ELAPSED_REALTIME flag instead of the RTC flag.
My guess is that the alarm manager is thinking it's missed a ton of alarms because you are using the RTC flag which means the alarm manager is expecting you to send a time value in milliseconds since Jan 1st 1970, but instead you are sending elapsed milliseconds since the device booted, which is going to be a much much smaller number.
If you use the RTC flags you need to use System.currentTimeMillis() or get the time in milliseconds from a Java Date or Calendar object. If you use ELAPSED_REALTIME flags then you need to use SystemClock.elapsedRealtime().