Merge "Serialize commands sent to a separate threads"
commit
2bc980ee28
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue