I've written a small Service class that contains a thread pool so that it can process multiple tasks in parallel. The kicker is that is also allows clients to bind to the service and automatically exits when there are no more tasks to run and there are no more bound clients.Some questions I have are:Am I abusing the Service's lifecycle in a way that may come back to bite me in the future?Am I handling all my variables in a thread-safe way?Is the service guaranteed to exit? (at the right time?)Is the service exception-safe? (would a Exception or RuntimeException thrown a task running in the thread pool kill everything?)Is the entire design a bad practice according to Android conventions/standards?This is how the service is used: I have another class (say TestService) that extends ThreadPoolService. TestService will have some public methods that the client can call after binding which will execute some tasks. The client (usually an Activity or Fragment) will bind to the service and also start it so that the service doesn't automatically exit after unbinding.There are 3 ways for the service to exit:In onCreate(), the service starts a 10 second timer so that the service will attempt exit if nothing binds to the service. This timer is cancelled when enqueueOperation() is called.When all clients unbind and onUnbind() is called, the service will attempt to exit.Every time a task completes, the service will attempt to exit.Below are the ThreadPoolService class, a sample service that extends it, and a sample activity that uses it.ThreadPoolService.javapackage com.github.chenxiaolong.dualbootpatcher;import android.app.AlarmManager;import android.app.PendingIntent;import android.app.Service;import android.content.Context;import android.content.Intent;import android.os.Binder;import android.os.IBinder;import android.os.SystemClock;import android.util.Log;import java.util.concurrent.BlockingQueue;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;public class ThreadPoolService extends Service {    private static final boolean DEBUG = true;    private final String TAG = getClass().getSimpleName();    /** Exit action for intent */    private final String ACTION_EXIT = getClass().getCanonicalName() + .exit;    /** Number of milliseconds for delayed exit */    private final long EXIT_DELAY = 10 * 1000;    /** Pending intent to exit the service */    private PendingIntent mExitPendingIntent;    /** Alarm manager */    private AlarmManager mAlarmManager;    /** Number of cores */    private static final int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();    /** Time an idle thread will wait before it exits */    private static final int KEEP_ALIVE_TIME = 1;    /** Units of {@link #KEEP_ALIVE_TIME} */    private static final TimeUnit KEEP_ALIVE_TIME_UNITS = TimeUnit.SECONDS;    /** Queue of operations to be processed by {@link #mThreadPool} */    private final BlockingQueue<Runnable> mWorkQueue = new LinkedBlockingQueue<>();    /** Thread pool for executing operations */    private ThreadPoolExecutor mThreadPool;    /** Number of currently running or pending operations */    private int mOperations = 0;    private final Object mLock = new Object();    /** Whether there are bound clients */    private boolean mBound = false;    /** Binder instance */    private final IBinder mBinder = new ThreadPoolServiceBinder();    /** Local Binder class */    public class ThreadPoolServiceBinder extends Binder {        public ThreadPoolService getService() {            return ThreadPoolService.this;        }    }    /** Log debug messages if {@link #DEBUG} is true */    private void log(String message) {        if (DEBUG) {            Log.d(TAG, message);        }    }    /** {@inheritDoc} */    @Override    public void onCreate() {        super.onCreate();        log(onCreate());        // Initialize thread pool        mThreadPool = new ThreadPoolExecutor(NUMBER_OF_CORES, NUMBER_OF_CORES, KEEP_ALIVE_TIME,                KEEP_ALIVE_TIME_UNITS, mWorkQueue);        mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);        Intent exitIntent = new Intent(this, getClass());        exitIntent.setAction(ACTION_EXIT);        mExitPendingIntent = PendingIntent.getService(this, 0, exitIntent, 0);        scheduleDelayedExit();    }    /** {@inheritDoc} */    @Override    public void onDestroy() {        super.onDestroy();        log(onDestroy());        // Attempt to stop thread pool. This shouldn't be an issue as there should be no tasks        // running at this point. The service stops when all clients have unbinded from it and there        // are no pending tasks.        mThreadPool.shutdownNow();        try {            mThreadPool.awaitTermination(60, TimeUnit.SECONDS);        } catch (InterruptedException e) {            Log.e(TAG, Failed to wait 60 seconds for thread pool termination, e);        }    }    /** {@inheritDoc} */    @Override    public int onStartCommand(final Intent intent, final int flags, final int startId) {        log(onStartCommand(intent= + intent + , flags= + flags + , startId= + startId + ));        if (intent != null && ACTION_EXIT.equals(intent.getAction())) {            attemptToStop();            return START_NOT_STICKY;        }        return START_STICKY;    }    /** {@inheritDoc} */    @Override    public IBinder onBind(final Intent intent) {        log(onBind(intent= + intent + ));        synchronized (mLock) {            mBound = true;        }        return mBinder;    }    /** {@inheritDoc} */    @Override    public boolean onUnbind(final Intent intent) {        log(onUnbind(intent= + intent + ));        synchronized (mLock) {            mBound = false;        }        attemptToStop();        // We're don't need to differentiate between bind and rebind        return false;    }    /**     * Enqueue operation     *     * @param runnable Task to run in thread pool     */    protected void enqueueOperation(final Runnable runnable) {        cancelDelayedExit();        synchronized (mLock) {            mOperations++;            mThreadPool.execute(new Runnable() {                @Override                public void run() {                    try {                        runnable.run();                    } finally {                        synchronized (mLock) {                            mOperations--;                            attemptToStop();                        }                    }                }            });        }    }    /**     * Attempt to stop the service     *     * This will not call {@link #stopSelf()} unless there are no tasks running and no clients bound     */    private void attemptToStop() {        synchronized (mLock) {            log(Attempting to stop service);            if (mOperations > 0 || mBound) {                log(Not stopping: # of operations:  + mOperations + , is bound:  + mBound);                return;            }            log(Calling stopSelf(): there are no more operations);            stopSelf();        }    }    /**     * Schedule delayed exit.     */    private void scheduleDelayedExit() {        log(Scheduling delayed exit after  + EXIT_DELAY + ms);        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,                SystemClock.elapsedRealtime() + EXIT_DELAY, mExitPendingIntent);    }    /**     * Cancel delayed exit     */    private void cancelDelayedExit() {        log(Cancelling delayed exit);        mAlarmManager.cancel(mExitPendingIntent);    }}TestService.javapackage com.github.chenxiaolong.dualbootpatcher;import android.util.Log;public class TestService extends ThreadPoolService {    private static final String TAG = TestService.class.getSimpleName();    public void testAction() {        enqueueOperation(new Runnable() {            @Override            public void run() {                Log.d(TAG, Sleeping for 10 seconds);                try {                    Thread.sleep(10000);                } catch (InterruptedException e) {                    Log.e(TAG, Interrupted, e);                }            }        });    }}TestActivity.javapackage com.github.chenxiaolong.dualbootpatcher;import android.content.ComponentName;import android.content.Context;import android.content.Intent;import android.content.ServiceConnection;import android.os.Bundle;import android.os.IBinder;import android.support.v7.app.AppCompatActivity;import com.github.chenxiaolong.dualbootpatcher.ThreadPoolService.ThreadPoolServiceBinder;public class TestActivity extends AppCompatActivity implements ServiceConnection {    private TestService mService;    @Override    protected void onCreate(final Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_test);    }    @Override    protected void onStart() {        super.onStart();        Intent intent = new Intent(this, TestService.class);        startService(intent);        bindService(intent, this, Context.BIND_AUTO_CREATE);    }    @Override    protected void onStop() {        super.onStop();        unbindService(this);    }    @Override    public void onServiceConnected(final ComponentName name, final IBinder service) {        ThreadPoolServiceBinder binder = (ThreadPoolServiceBinder) service;        mService = (TestService) binder.getService();        // Run test task        mService.testAction();    }    @Override    public void onServiceDisconnected(final ComponentName name) {        mService = null;    }}
Self-stopping started and bound Android service
java;multithreading;android