/**
* 2011 Foxykeep (http://datadroid.foxykeep.com)
* <p>
* Licensed under the Beerware License : <br />
* As long as you retain this notice you can do whatever you want with this stuff. If we meet some
* day, and you think this stuff is worth it, you can buy me a beer in return
*/
package external.GifImageViewEx.com.foxykeep.datadroid.service;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
* MultiThreadIntentService is a base class for {@link Service}s that handle
* asynchronous requests (expressed as {@link Intent}s) on demand. Clients send
* requests through {@link android.content.Context#startService(Intent)} calls;
* the service is started as needed, handles each Intent in turn using a worker
* thread, and stops itself when it runs out of work.
* <p>
* This "work queue processor" pattern is commonly used to offload tasks from an
* application's main thread. The MultiThreadedIntentService class exists to
* simplify this pattern and take care of the mechanics. To use it, extend
* MultiThreadedIntentService and implement {@link #onHandleIntent(Intent)}.
* MultiThreadedIntentService will receive the Intents, launch a worker thread,
* and stop the service as appropriate.
* <p>
* All requests are handled on multiple worker threads -- they may take as long
* as necessary (and will not block the application's main loop). By default
* only one concurrent worker thread is used. You can modify the number of
* current worker threads by overriding {@link #getMaximumNumberOfThreads()}.
* <p>
* For obvious efficiency reasons, MultiThreadedIntentService won't stop itself
* as soon as all tasks has been processed. It will only stop itself after a
* certain delay (about 30s). This optimization prevents the system from
* creating new instances over and over again when tasks are sent.
*
* @author Foxykeep
*/
public abstract class MultiThreadedIntentService extends Service {
private static final long STOP_SELF_DELAY = TimeUnit.SECONDS.toMillis(30L);
private ExecutorService mThreadPool;
private boolean mRedelivery;
private ArrayList<Future<?>> mFutureList;
private Handler mHandler;
private final Runnable mStopSelfRunnable = new Runnable() {
@Override
public void run() {
stopSelf();
}
};
private final Runnable mWorkDoneRunnable = new Runnable() {
@Override
public void run() {
if (Looper.getMainLooper().getThread() != Thread.currentThread()) {
throw new IllegalStateException(
"This runnable can only be called in the Main thread!");
}
final ArrayList<Future<?>> futureList = mFutureList;
for (int i = 0; i < futureList.size(); i++) {
if (futureList.get(i).isDone()) {
futureList.remove(i);
i--;
}
}
if (futureList.isEmpty()) {
mHandler.postDelayed(mStopSelfRunnable, STOP_SELF_DELAY);
}
}
};
/**
* 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;
}
@Override
public void onCreate() {
super.onCreate();
int maximumNumberOfThreads = getMaximumNumberOfThreads();
if (maximumNumberOfThreads <= 0) {
throw new IllegalArgumentException(
"Maximum number of threads must be " + "strictly positive");
}
mThreadPool = Executors.newFixedThreadPool(maximumNumberOfThreads);
mHandler = new Handler();
mFutureList = new ArrayList<Future<?>>();
}
@Override
public void onStart(Intent intent, int startId) {
mHandler.removeCallbacks(mStopSelfRunnable);
mFutureList.add(mThreadPool.submit(new IntentRunnable(intent)));
}
@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() {
super.onDestroy();
mThreadPool.shutdown();
}
/**
* Unless you provide binding for your service, you don't need to implement
* this method, because the default implementation returns null.
*
* @see android.app.Service#onBind
*/
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* Define the maximum number of concurrent worker threads used to execute
* the incoming Intents.
* <p>
* By default only one concurrent worker thread is used at the same time.
* Overrides this method in subclasses to change this number.
* <p>
* This method is called once in the {@link #onCreate()}. Modifying the
* value returned after the {@link #onCreate()} is called will have no
* effect.
*
* @return The maximum number of concurrent worker threads
*/
protected int getMaximumNumberOfThreads() {
return 1;
}
private class IntentRunnable implements Runnable {
private final Intent mIntent;
public IntentRunnable(Intent intent) {
mIntent = intent;
}
public void run() {
onHandleIntent(mIntent);
mHandler.removeCallbacks(mWorkDoneRunnable);
mHandler.post(mWorkDoneRunnable);
}
}
/**
* This method is invoked on the worker thread with a request to process.
* The processing happens on a worker thread that runs independently from
* other application logic. When all requests have been handled, the
* IntentService stops itself, so you should not call {@link #stopSelf}.
*
* @param intent The value passed to {@link Context#startService(Intent)}.
*/
abstract protected void onHandleIntent(Intent intent);
}