I have an alarm that I am wanting to repeat around every 5 minutes. For testing purposes, I have it set to repeat once every 5 seconds instead, as such:
AlarmManager alarmManager = (AlarmManager)getActivity().getSystemService(getActivity().ALARM_SERVICE);
Intent intent = new Intent(getActivity(), CoordinateAlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getService(getActivity(), 1, intent, 0);
int repeatSeconds = 5;
alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
repeatSeconds * 1000, pendingIntent);
And the receiving IntentService prints a log statement when it receives the alarm. However, it fires around once every minute and a half instead of once every 5 seconds, where is it set incorrectly? I have also tried using setRepeating() instead of setInexactRepeating() but I get the same results.
Here is my alarm receiver:
public class CoordinateAlarmReceiver extends IntentService {
public CoordinateAlarmReceiver(){
super("CoordinateAlarmReceiver");
}
/*
Alarm received, get new contacts which then shows notification
*/
#Override
protected void onHandleIntent(Intent intent) {
MyLog.i("coordinate alarm received");
//new GetNewContactsTask(this).execute();
}
}
I assume you are on api 19 or above. The alarmmanager documentations says:
Note: Beginning with API 19 (KITKAT) alarm delivery is inexact: the OS will shift alarms in order to minimize wakeups and battery use. There are new APIs to support applications which need strict delivery guarantees; see setWindow(int, long, long, PendingIntent) and setExact(int, long, PendingIntent). Applications whose targetSdkVersion is earlier than API 19 will continue to see the previous behavior in which all alarms are delivered exactly when requested.
You tried using setRepeating() but on api 19 and above this calls setInexactRepeating(). On 19 and above you
setInexactRepeating(): Schedule a repeating alarm that has inexact trigger time requirements; for example, an alarm that repeats every hour, but not necessarily at the top of every hour.
This explains your weird result.
If you want to set is at a excat time, you should use setExact. Unfortunalety there is no setExactRepating so you have to create this yourself. Schedule a new alarm after one executes or something like that.
Note in the alarmmanager documentation:
Note: The Alarm Manager is intended for cases where you want to have your application code run at a specific time, even if your application is not currently running. For normal timing operations (ticks, timeouts, etc) it is easier and much more efficient to use Handler.
Maybe you should take a look at this.
I had a similar problem in which I needed a Service fired every 15 seconds... I did the following.
I have a class that extends Application called MyApplication. This class holds an instance of an alarm manager.
public class MyApplication extends Application {
private MyAlarmManager alarmMgr;
#Override
public void onCreate() {
Log.d(TAG, "MyApplication onCreate");
super.onCreate();
Log.d(TAG, "initing alarmMgr ...");
alarmMgr = new MyAlarmManager(this);
}
public MyAlarmManager getAlarmManager(){
return alarmMgr;
}
}
An AlarmManager called MyAlarmManager creates, starts & stops the alarms AND NOW sets the next alarm for this one service.
public class MyAlarmManager {
private MyApplication mApp;
private Intent intent;
private PendingIntent pendingIntent;
private AlarmManager alarmMgr;
private static final long FREQUENCY = 15000;
public MyAlarmManager(Context context) {
mApp = (MyApplication) context;
// Android alarm service
alarmMgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
// service to be fired every 15 seconds
intent = new Intent(context, MyService.class);
pendingIntent = PendingIntent.getService(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
setNextAlarm();
}
public void setNextAlarm(){
Log.d(TAG, "setting next alarm...");
alarmMgr.set(AlarmManager.RTC_WAKEUP, (System.currentTimeMillis() + FREQUENCY), pendingIntent);
}
private void stopAlarms(){
Log.d(TAG, "stopping Alarms");
alarmMgr.cancel(pendingIntent);
}
}
When the Service is fired I get an instance of MyApplication, get the AlarmManager and schedule the next alarm.
public class MyService extends Service {
MyApplication mApp;
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "in onStartCommand");
// init the app reference if needed
if (mApp == null) {
Log.d(TAG, "app was null. Creating now...");
mApp = (MyApplication) getApplicationContext();
}
// schedule the next zone check
mApp.getAlarmMgr().setNextAlarm();
// custom functionality ...
}
Related
Previously I was using IntentService to send data to the server periodically. However, since Android O limiting background task and processes I am moving towards JobIntentService.
My Activity code to schedule an alarm
Intent intent = new Intent(BaseActivity.this, EventBroadcastReceiver.class);
// Create a PendingIntent to be triggered when the alarm goes off
final PendingIntent pIntent = PendingIntent.getBroadcast(this, EventBroadcastReceiver.REQUEST_CODE,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
// Setup periodic alarm every half hour
long firstMillis = System.currentTimeMillis(); // alarm is set right away
AlarmManager alarm = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
alarm.setInexactRepeating(AlarmManager.RTC_WAKEUP, firstMillis,
AlarmManager.INTERVAL_HALF_HOUR, pIntent);
And my Service is as follows
public class EventAnalyticsService extends JobIntentService {
#Override
protected void onHandleWork(#NonNull Intent intent) {
// Perform your task
}
#Override
public void onCreate() {
super.onCreate();
}
}
Receiver for this code is
public class EventBroadcastReceiver extends BroadcastReceiver {
public static final int REQUEST_CODE = 12345;
#Override
public void onReceive(Context context, Intent intent) {
Intent myIntent = new Intent(context, EventAnalyticsService.class);
context.startService(myIntent);
}
}
However this is not working for Android O when app is in background and if I use context.startForegroundService(myIntent); to start my service it is throwing exception as Context.startForegroundService() did not then call Service.startForeground()
JobIntentService is there mostly for a service that will be invoked from the UI, such as a service to perform a large download, initiated by the user clicking a "Download!" button.
For periodic background work, use JobScheduler and a JobService. If you need to support older than Android 5.0, use a suitable wrapper library (e.g., Evernote's android-job), one that will use JobScheduler on newer devices and AlarmManager on older devices.
Alternatively, stick with AlarmManager, but use a foreground service.
I'm developing an Android App which collects data from inertial sensors using a service that I call with AlarmManager each five minutes. This service has a timer to collect data only one minute. So I want to collect one minute of data and sleep four minutes (loop process). The problem is that the AlarmManager with setRepeating() is not exact (one minute delay or more) on API>19. There are a lot of post but not a clear solution... In the MainActivity I have:
public void startservice(View view){
AlarmManager scheduler = (AlarmManager) getSystemService(ALARM_SERVICE);
Intent intent = new Intent(getApplicationContext(), SensorLogger.class);
PendingIntent scheduledIntent = PendingIntent.getService(getApplicationContext(), 0, intent, 0);
scheduler.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 60000 * 5, scheduledIntent);
}
And in the service something like:
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
timer.scheduleAtFixedRate(new stoptask(), 60000, 60000);
...
return START_NOT_STICKY;
}
private class stoptask extends TimerTask
{
public void run()
{
...
timer.cancel();
stopSelf();
}
}
Thanks!
You can't use the repeating api exactly anymore. You need to set a single exact alarm, then set a new alarm inside that alarm for the repeat.
I posted a library that takes care of multiple timer implementations on android- take a look at http://gabesechansoftware.com/timers-in-android/ it might help.
I need to plan sheduled task every 10 minutes.
As in Lollipop and higher version setRepeating() is inexact, I use setExact() and (on alarm firing) I set new exact alarm in 10 minutes.
private void setAlarm(long triggerTime, PendingIntent pendingIntent) {
int ALARM_TYPE = AlarmManager.ELAPSED_REALTIME_WAKEUP;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(ALARM_TYPE, triggerTime, pendingIntent);
} else {
alarmManager.set(ALARM_TYPE, triggerTime, pendingIntent);
}
}
triggerTime is calculated SystemClock.elapsedRealtime() + 600_000;
When alarm fires, firstly I plan new one, only after that I run my sheduled task.
setAlarm();
mySheduledTask;
I do have WAKE_LOCK permission in my manifest.
When I test this on Android 4 - it works perfect (deviation might be 12-15 milliseconds).
But when I run app on Xiaomi Redmi Note 3 Pro (5.1.1) - deviation can be up to 15 seconds!
For example, I see in my log file: first run was at 1467119934477 (of RTC time), second - at 1467120541683. Difference is 607_206 milliseconds, not 600_000, as it was planned!
What am I missing? What is a way to simulate behaviour of system alarm (it's the most close usecase that can describe my tack)?
PS. I use IntentService for PendingIntent = PendingIntent.getService(context, 0, myIntent, 0);
The OS chooses how the alarms will work, with consideration of the time you've specified. Because of that, when the phone gets into a 'semi-sleep' mode, it won't necessary use the resource at the time you wish it to. Basically, it waits for 'windows' that the OS opens for it, and only then the alarm you want to run will run, that's why you're experiencing time gaps.
This was introduced on Marshmallow OS and will continue on Nougat OS as well, as part of Google trying to improve the device's battery.
Here's the thing, you have 2 options:
Accept the time delays (but maybe consider using JobScheduler which is more recommended and will save you battery).
Use setExactAndAllowWhileIdle which might cause you battery issues (use this carefully, too many alarms will be bad for your battery).
This method isn't repeating, so you have to declare the next job to be run at the service which the pendingIntent opens.
If you choose option 2, here's the start:
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
int ALARM_TYPE = AlarmManager.RTC_WAKEUP;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
am.setExactAndAllowWhileIdle(ALARM_TYPE, calendar.getTimeInMillis(), pendingIntent);
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
am.setExact(ALARM_TYPE, calendar.getTimeInMillis(), pendingIntent);
else
am.set(ALARM_TYPE, calendar.getTimeInMillis(), pendingIntent);
You can call the method from support.v4:
AlarmManagerCompat.setExact(...);
The internal implementation contains checks by sdk version.
Probably a possible workaround could be something like this:
you schedule the Alarm about 1 minute before the expected time, than you use a Handler.postDelayed to cover the remaining time.
Here you can find an example of this kind of implementation.
The activity just set-up the first alarm:
public class MainActivity extends AppCompatActivity {
private static int WAIT_TIME = 60*1000; //1 minute
public static int DELAY_TIME = 10*60*1000; // delay between iterations: 10min
public static String UPDATE_TIME_KEY = "update_time_key";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setAlarm(this,(new Date().getTime())+DELAY_TIME);
}
public static void setAlarm(Context context, long delay) {
long fireDelay = delay-WAIT_TIME;
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
sharedPreferences.edit().putLong(UPDATE_TIME_KEY,delay).apply();
Intent startIntent = new Intent(context, UpdateReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 1, startIntent,PendingIntent.FLAG_UPDATE_CURRENT );
AlarmManager alarmManager = (AlarmManager) context.getApplicationContext().getSystemService(Context.ALARM_SERVICE);
int ALARM_TYPE = AlarmManager.RTC;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(ALARM_TYPE, fireDelay, pendingIntent);
} else {
alarmManager.set(ALARM_TYPE, fireDelay, pendingIntent);
}
}
}
than the receiver continues the loop:
public class UpdateReceiver extends BroadcastReceiver {
#Override
public void onReceive(final Context context, Intent intent) {
Log.e("RECEIVED","RECEIVED");
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
long fireTime = sharedPreferences.getLong(MainActivity.UPDATE_TIME_KEY, (new Date()).getTime());
long fireDelay =(fireTime-(new Date().getTime())>0)?fireTime-(new Date().getTime()):0;
(new Handler()).postDelayed(new Runnable() {
#Override
public void run() {
Log.e("RECEIVED","PERFORMED");
MainActivity.setAlarm(context,(new Date()).getTime()+MainActivity.DELAY_TIME);
}
},fireDelay);
}
}
I hope it helped.
To answer the question on the system alarm...
Android's stock Alarm Clock/Desk Clock app uses a combination of setAlarmClock and setExactAndAllowWhileIdle.
The following code is used to update notifications:
final PendingIntent operation = PendingIntent.getBroadcast(context, 0,
AlarmStateManager.createIndicatorIntent(context), flags);
final AlarmClockInfo info = new AlarmClockInfo(alarmTime, viewIntent);
alarmManager.setAlarmClock(info, operation);
While at the same time the following code is used to schedule the actual alarm:
if (Utils.isMOrLater()) {
// Ensure the alarm fires even if the device is dozing.
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
} else {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent)
}
The Pending intent set in setExactAndAllowWhileIdle triggers the alarm while setAlarmClock's intent is then simply ignored.
Android Googlesource
From android documentation of AlarmManager
Beginning with API 19 (KITKAT) alarm delivery is inexact: the OS will shift alarms in order to minimize wakeups and battery use. There are new APIs to support applications which need strict delivery guarantees; see setWindow(int, long, long, PendingIntent) and setExact(int, long, PendingIntent). Applications whose targetSdkVersion is earlier than API 19 will continue to see the previous behavior in which all alarms are delivered exactly when requested.
Also while using setExact() :
The alarm will be delivered as nearly as possible to the requested trigger time.
So its still not guaranteed that setExact will be Exact.
You can try use AlarmManager.setAlarmClock maybe it can help you.
Another thing you need to check which type of BroadcastReceiver you are using, it will be better to use WakefulBroadcastReceiver
Btw you need to change logic for work with Alarm Manager for support Android M, you can you something like this:
if(Build.VERSION.SDK_INT < 23){
if(Build.VERSION.SDK_INT >= 19) {
setExact(...);
} else {
set(...);
}
} else {
setExactAndAllowWhileIdle(...);
}
I am trying to create app where i need to send location updates every 1,5,10 minutes and so on.
When app is running, it work accurately but when it goes into background/sleep mode it doesn't work accurately.
I tried both the methods setRepeating/setInExactRepeating but none of them work in background mode.
public static void startSensorAlaram(Context ctx, long minutes) {
AlarmManager alarmManager = (AlarmManager) ctx
.getSystemService(Context.ALARM_SERVICE);
// Alarm_Receiver is a broadcast receiver.
Intent intent = new Intent(ctx, Alaram_Receiver.class);
intent.setAction(Utility.SENSOR_ACTION);
PendingIntent pi = PendingIntent.getBroadcast(ctx, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,System.currentTimeMillis(),minutes,pi);
// alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), minutes, pi);
}
public static void stopAlaramSensor(Context ctx) {
Intent intent = new Intent(ctx, Alaram_Receiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(ctx, 1,
intent, 0);
AlarmManager alarmManager = (AlarmManager) ctx
.getSystemService(Context.ALARM_SERVICE);
alarmManager.cancel(pendingIntent);
}
Alarm Receiver - Broadcast receiver
public class Alaram_Receiver extends WakefulBroadcastReceiver {
private SharedPreferences sp;
#Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
sp = context.getSharedPreferences(Utility.SHARED_PREFS, 0);
if (intent.getAction().equalsIgnoreCase(Utility.SENSOR_ACTION)) {
if (sp.getBoolean("logged_in", false)) {
// context.startService(new Intent(context,SensorService.class));
startWakefulService(context,new Intent(context,SensorService.class));
} else
Utility.stopAlaramSensor(context);
}
}
}
Note:- Min API version is 15 and compile version is 23.
There are two issues.
1) As of Android API >= 19, you should use new AlarmManager.setExact() method instead of set() or setRepeating(). Here is the quote from official document.
Beginning with API 19 (KITKAT) alarm delivery is inexact: the OS will
shift alarms in order to minimize wakeups and battery use. There are
new APIs to support applications which need strict delivery
guarantees; see setWindow(int, long, long, PendingIntent) and
setExact(int, long, PendingIntent).
2) As of Android 6.0, there is a deep sleep mode introduced, called Doze.
It is designed to reduce battery consumption when device is being standby. There are so many restriction and what you could do in that mode is very limited. You need to use the new AlarmManager.setExactAndAllowWhileIdle() to make Alarm fired in Doze mode at your preferred time.
More information about Doze mode is available here Optimizing for Doze and App Standby
I have this Service that do other stuff in the background and must always be running. On its onCreate i've set an Alarm that sends an email once a day. The server is not running in foreground, so sometimes its get killed by the system, and then created again, and when that happens its creates the alarm again, and sends another email every time it happens. So my question is, is there a way to prevent it from triggering the alarm if its already set for repeating in the system? Or is there another "place" in the Service where i can create the alarm once and prevent it for being recreated with the it?
public class ObserverService extends Service {
private String BABAS = "babas";
#Override
public void onCreate() {
setRecurringAlarm(this);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
private void setRecurringAlarm(Context context) {
Log.i(BABAS, "entrou no set alarme");
Calendar updateTime = Calendar.getInstance();
updateTime.setTimeZone(TimeZone.getDefault());//getTimeZone("GMT"));
updateTime.set(Calendar.HOUR_OF_DAY, 00);
updateTime.set(Calendar.MINUTE, 00);
Intent downloader = new Intent("ALARME");//context, AlarmReceiver.class);
PendingIntent recurringDownload = PendingIntent.getBroadcast(context,
0, downloader, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarms = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarms.setInexactRepeating(AlarmManager.RTC_WAKEUP, updateTime.getTimeInMillis(), AlarmManager.INTERVAL_DAY, recurringDownload);
}
}
If that code is everything your Service does you can completely get rid of the Service.
The only thing the service does is schedule a Broadcast once a day on the alarm manager, why not do this schedule directly from the Activity?
edit:
Given your comment, my original answer remains:
The Alarm set on the AlarmManager is permanent until the device is turned off or rebooted.
So you can set the first alarm via Activity and mark it as active on the SharedPreferences. Create a BroadcastReceiver to receive onBoot events, this receiver will check the SharedPreferences for if the alarm is active and in case it is, re-register it on the AlarmManager.
Your service still does the same ContentObserver stuff, but have no association with the recurring AlarmManager event.
I reckon that would be cleanest solution.
Alternatively (but I don't think it's a good/clean solution), you can use the AlarmManager.cancel() to remove the existing PendinIntent, and then re-trigger it
http://developer.android.com/reference/android/app/AlarmManager.html#cancel(android.app.PendingIntent)