Re-creating the AlarmManager Service
I have a pretty standard service that I want to start with an alarm. Here's the service initiation section:
class MyService extends Service {
private Context context;
private AlarmManager alarmManager = null;
private final String startReason = "com.stuff.myreason";
private final int REASON_NO_INTENT = 0;
private final int REASON_ALARM = 1;
private final int REASON_X = 2; // and so on.
@Override
void onCreate() {
super.onCreate();
context = getApplicationContext();
alarmManager = (AlarmManager)getSystemService(ALARM_SERVICE);
// do onCreate stuff
}
@Override
int onStartCommand (Intent intent, int flags, int startId) {
int reason = REASON_NO_INTENT;
if (intent != null) {
reason = intent.getExtra(startReason, REASON_NO_INTENT);
}
switch(reason) {
// handle the different reasons we may have been "started"
}
return START_STICKY;
}
}
When I start it with context.startService from an activity, it starts up absolutely fine. Specifically, if it's already running, it doesn't start from scratch, but just injects an existing instance via onStartCommand()
. This is expected behavior. However, when I run it using AlarmManager:
Intent intent = new Intent(context, MyService.class);
intent.putExtra(purposeOfStartCode, REASON_ALARM);
PendingIntent pi = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.set(AlarmManager.RTC_WAKEUP, /* A time in the future */, pi);
When an alarm occurs, the service is restarted from scratch: it starts a new instance, calls onCreate()
, and then onStartCommand()
, rather than just calls, onStartCommand()
on an already running instance.
I've already tried changing the flag PendingIntent
to FLAG_ONE_SHOT
and replace context
with MyService.this
no improvement.
I'm pretty confused about this - can anyone explain this behavior and suggest ways to make it behave as expected?
EDIT. The collection of actions that led to the decision is given below.
source to share
After some research and work, I discovered a number of things. Doing all of this makes this problem look like it's gone:
-
If you override onStart and onStartCommand in a service (to allow older devices) and you add super.onStartCommand in the latter, it will call onStart, which means you get each intent twice!
-
As per one of the other answers (and comments to it), AlarmManager is designed and defined to deliver broadcast and not other types. In practice, however, he is not picky and seems to honor other forms. I think this was one of the keys in solving the problem.
-
If the service is running in the same process as other activities, etc., the service sometimes appears to "just reboot". This could be the actual cause of the problem noted in this question. See Android service onCreate is called multiple times without calling onDestroy .
-
It seems that the situation is more stable with a single use of intents to communicate with the Service, rather than binding and using Messenger methods or binding and access. While both are correct, they are quite tricky to manage (although you could use this approach: What is the preferred way to invoke an Android activity from a service thread, and Using an Android app class to persist data ). While I fully understand that the android docs disagree with me, in my observation heading towards broadcast, single communication seemed key. If you go to a separate process, you still have to do it.
-
It pays to be consistent in how you declare and address your classes. This is a bit messy, but it seems that sometimes people get paid to use fully qualified names ("com.company.superapp.CleverService") rather than short names ("CleverService" or ".CleverService"). Therefore, it is probably best to always use qualified names.
-
The rule of thumb floating around the context ("use getApplicationContext") is not really the right way to go. See When to call activity context or application context? ; in fact, use this unless you really need to use the broader context and manage your variables well.
-
Perhaps the garbage collector will clean up something else in use if it was created in an Activity, Service, Thread, AsyncTask, etc. that no longer exists. If your application is service-based, it might be wise to make a copy of the classes so they don't get cleaned up later.
-
An easier way to start a service than is often suggested is to provide the fully qualified aimFilter as an action. Then you can create an intent to run it with just the class name as a string. This means you don't need to worry about context. See Call service problem .
source to share
Ok, I'm really surprised it launches yours Service
at all! PendingIntent
which you broadcast to AlarmManager
must be broadcast Intent
. So you need to rebuild your code a bit. AlarmManager
causes BroadcastReceiver
, and BroadcastReciever
can cause startService()
.
See description AlarmManager.set()
source to share
I got this to work using the following code:
AlarmManager almgr = (AlarmManager)MyContext.getSystemService(Context.ALARM_SERVICE);
Intent timerIntent = new Intent(MyUniqueLabel);
timerIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
PendingIntent pendingOffLoadIntent = PendingIntent.getBroadcast(MyContext, 1, timerIntent, 0);
YOU MUST do these things for it to work.
1.) Call addFlags and intent and pass it to FLAG_RECEIVER_FORGROUND
2.) Use non-null request code in PendingIntent.getBroadcast
If you leave any of these steps, it won't work.
source to share