For now, it looks like I indeed need a service, but not exactly IntentService
: IntentService
calls stopSelf()
, while my service should hang around.
It's interesting that Service.onDestroy()
is called when the user chooses "stop" from "Running services" but not when the user chooses "Force stop" from "Applications". "Stop Debugging" also does not cause Service.onDestroy()
invocation.
EDIT:
My current solution is to use a class derived from a custom Service
subclass; the code has been borrowed from the source of IntentService
found somewhere in the 'net. In the derived subclass I override onDestroy()
and believe that's the best application termination notification available.
package com.xyz.customandroid;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
public abstract class HangAroundIntentService extends Service {
/** An extra with this name and the value of boolean true marks an intent as a cancel intent. See {@link #markedAsCancelIntent(Intent)}. */
private static final String CANCEL_MARK = "com.xyz~.customandroid.HangAroundIntentService.cancelQueue()";
private static final int WHAT_MAGIC = 0; // the "what" field for messages
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private String mName;
private boolean mRedelivery;
private boolean mHangAround = true;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
if (!mHangAround) {
stopSelf(msg.arg1);
}
}
}
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public HangAroundIntentService(String name) {
super();
mName = name;
}
/**
* Remove all pending messages from the handler queue.
* Processing of the message already fetched from the queue
* is not terminated by this function.
*
* Although this function is public, it is recommended
* to use the cancel intents instead.
* see {@link #markedAsCancelIntent(Intent)} and {@link #isCancelIntent(Intent)}.
*/
public void cancelQueue() {
mServiceHandler.removeMessages(WHAT_MAGIC);
}
/**
* Sets intent redelivery preferences. Usually called from the constructor
* with your preferred semantics.
*
* <p>If enabled is true,
* {@link #onStartCommand(Intent, int, int)} will return
* {@link Service#START_REDELIVER_INTENT}, so if this process dies before
* {@link #onHandleIntent(Intent)} returns, the process will be restarted
* and the intent redelivered. If multiple Intents have been sent, only
* the most recent one is guaranteed to be redelivered.
*
* <p>If enabled is false (the default),
* {@link #onStartCommand(Intent, int, int)} will return
* {@link Service#START_NOT_STICKY}, and if the process dies, the Intent
* dies along with it.
*/
public void setIntentRedelivery(boolean enabled) {
mRedelivery = enabled;
}
/**
* If enabled is true (default), the service does not stop after processing an intent.
*/
public void setServiceHangAround(boolean enabled) {
mHangAround = enabled;
}
@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
/**
* If intent is a cancel intent, {@link #cancelQueue()} is invoked immediately;
* no other action is done for a cancel intent, whatever information it might contain.
*
* Intents that are not cancel intents are queued
* to be seen from {@link #onHandleIntent(Intent)}.
*/
@Override
public void onStart(Intent intent, int startId) {
if (isCancelIntent(intent)) {
cancelQueue();
} else {
Message msg = mServiceHandler.obtainMessage(WHAT_MAGIC);
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
@Override
public void onDestroy() {
mServiceLooper.quit();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* This method is invoked on the worker thread with a request to process.
* Only one Intent is processed at a time, but the processing happens on a
* worker thread that runs independently from other application logic.
* So, if this code takes a long time, it will hold up other requests to
* the same IntentService, but it will not hold up anything else.
*
* @param intent The value passed to {@link
* android.content.Context#startService(Intent)}.
*/
protected abstract void onHandleIntent(Intent intent);
/**
* Mark an Intent as cancel intent. The Intent will not be placed in the queue;
* instead, it will cause immediate cleaning of the queue
* (unless you redefine {@link #onStart(Intent, int)} in a derived class).
* @param intent to be modified
* @return the original intent after modification
*/
public static Intent markedAsCancelIntent(Intent intent) {
intent.putExtra(CANCEL_MARK, true);
return intent;
}
/**
* Check if the intent has been marked as a cancel intent.
* @param intent to be checked
* @return true if it is a cancel intent
*/
public static boolean isCancelIntent(Intent intent) {
return intent.getBooleanExtra(CANCEL_MARK, false);
}
}
and my service class is defined as:
public class MyService extends HangAroundIntentService {
public MyService() {
super("MyService");
}
public void onDestroy() {
MyData.getMyData().shutdown();
super.onDestroy();
}
// service-specific static methods not shown
}
To be continued...