/** * 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); }