Serialize commands sent to a separate threads

Bug: 9715797
Change-Id: I1eda4d2f0056f70cfb8a92d658e0875706efc170
main
Jean Chalard 2013-07-10 11:54:23 +09:00
parent b6f286bfa5
commit 6a7a569831
1 changed files with 34 additions and 25 deletions

View File

@ -22,13 +22,14 @@ import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.IBinder; import android.os.IBinder;
import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.R;
import java.util.Locale; import java.util.Locale;
import java.util.Random; import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
@ -92,21 +93,27 @@ public final class DictionaryService extends Service {
private static final long VERY_LONG_TIME = TimeUnit.DAYS.toMillis(14); private static final long VERY_LONG_TIME = TimeUnit.DAYS.toMillis(14);
/** /**
* The last seen start Id. This must be stored because we must only call stopSelfResult() with * An executor that serializes tasks given to it.
* the last seen Id, or the service won't stop.
*/ */
private int mLastSeenStartId; private ThreadPoolExecutor mExecutor;
private static final int WORKER_THREAD_TIMEOUT_SECONDS = 15;
/**
* The command count. We need this because we need to not call stopSelfResult() while we still
* have commands running.
*/
private int mCommandCount;
@Override @Override
public void onCreate() { public void onCreate() {
mLastSeenStartId = 0; // By default, a thread pool executor does not timeout its core threads, so it will
mCommandCount = 0; // never kill them when there isn't any work to do any more. That would mean the service
// can never die! By creating it this way and calling allowCoreThreadTimeOut, we allow
// the single thread to time out after WORKER_THREAD_TIMEOUT_SECONDS = 15 seconds, allowing
// the process to be reclaimed by the system any time after that if it's not doing
// anything else.
// Executors#newSingleThreadExecutor creates a ThreadPoolExecutor but it returns the
// superclass ExecutorService which does not have the #allowCoreThreadTimeOut method,
// so we can't use that.
mExecutor = new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */,
WORKER_THREAD_TIMEOUT_SECONDS /* keepAliveTime */,
TimeUnit.SECONDS /* unit for keepAliveTime */,
new LinkedBlockingQueue<Runnable>() /* workQueue */);
mExecutor.allowCoreThreadTimeOut(true);
} }
@Override @Override
@ -131,33 +138,35 @@ public final class DictionaryService extends Service {
* - Handle a finished download. * - Handle a finished download.
* This executes the actions that must be taken after a file (metadata or dictionary data * This executes the actions that must be taken after a file (metadata or dictionary data
* has been downloaded (or failed to download). * has been downloaded (or failed to download).
* The commands that can be spun an another thread will be executed serially, in order, on
* a worker thread that is created on demand and terminates after a short while if there isn't
* any work left to do.
*/ */
@Override @Override
public synchronized int onStartCommand(final Intent intent, final int flags, public synchronized int onStartCommand(final Intent intent, final int flags,
final int startId) { final int startId) {
final DictionaryService self = this; final DictionaryService self = this;
mLastSeenStartId = startId;
mCommandCount += 1;
if (SHOW_DOWNLOAD_TOAST_INTENT_ACTION.equals(intent.getAction())) { if (SHOW_DOWNLOAD_TOAST_INTENT_ACTION.equals(intent.getAction())) {
// This is a UI action, it can't be run in another thread // This is a UI action, it can't be run in another thread
showStartDownloadingToast(this, LocaleUtils.constructLocaleFromString( showStartDownloadingToast(this, LocaleUtils.constructLocaleFromString(
intent.getStringExtra(LOCALE_INTENT_ARGUMENT))); intent.getStringExtra(LOCALE_INTENT_ARGUMENT)));
} else { } else {
// If it's a command that does not require UI, create a thread to do the work // If it's a command that does not require UI, arrange for the work to be done on a
// and return right away. DATE_CHANGED or UPDATE_NOW are examples of such commands. // separate thread, so that we can return right away. The executor will spawn a thread
new Thread("updateOrFinishDownload") { // if necessary, or reuse a thread that has become idle as appropriate.
// DATE_CHANGED or UPDATE_NOW are examples of commands that can be done on another
// thread.
mExecutor.submit(new Runnable() {
@Override @Override
public void run() { public void run() {
dispatchBroadcast(self, intent); dispatchBroadcast(self, intent);
synchronized(self) { // Since calls to onStartCommand are serialized, the submissions to the executor
if (--mCommandCount <= 0) { // are serialized. That means we are guaranteed to call the stopSelfResult()
if (!stopSelfResult(mLastSeenStartId)) { // in the same order that we got them, so we don't need to take care of the
Log.e(TAG, "Can't stop ourselves"); // order.
} stopSelfResult(startId);
}
}
} }
}.start(); });
} }
return Service.START_REDELIVER_INTENT; return Service.START_REDELIVER_INTENT;
} }