diff options
Diffstat (limited to 'java/src/com/android/inputmethod/dictionarypack/DictionaryService.java')
-rw-r--r-- | java/src/com/android/inputmethod/dictionarypack/DictionaryService.java | 84 |
1 files changed, 43 insertions, 41 deletions
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java index 46bb5543a..41916b614 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java +++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java @@ -22,14 +22,15 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.IBinder; -import android.text.format.DateUtils; -import android.util.Log; import android.widget.Toast; import com.android.inputmethod.latin.R; import java.util.Locale; import java.util.Random; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; /** * Service that handles background tasks for the dictionary provider. @@ -49,17 +50,10 @@ import java.util.Random; * to access, and mark the current state as such. */ public final class DictionaryService extends Service { - private static final String TAG = DictionaryService.class.getName(); - /** * The package name, to use in the intent actions. */ - private static final String PACKAGE_NAME = "com.android.android.inputmethod.latin"; - - /** - * The action of the intent to tell the dictionary provider to update now. - */ - private static final String UPDATE_NOW_INTENT_ACTION = PACKAGE_NAME + ".UPDATE_NOW"; + private static final String PACKAGE_NAME = "com.android.inputmethod.latin"; /** * The action of the date changing, used to schedule a periodic freshness check @@ -82,36 +76,42 @@ public final class DictionaryService extends Service { * How often, in milliseconds, we want to update the metadata. This is a * floor value; actually, it may happen several hours later, or even more. */ - private static final long UPDATE_FREQUENCY = 4 * DateUtils.DAY_IN_MILLIS; + private static final long UPDATE_FREQUENCY = TimeUnit.DAYS.toMillis(4); /** * We are waked around midnight, local time. We want to wake between midnight and 6 am, * roughly. So use a random time between 0 and this delay. */ - private static final int MAX_ALARM_DELAY = 6 * ((int)AlarmManager.INTERVAL_HOUR); + private static final int MAX_ALARM_DELAY = (int)TimeUnit.HOURS.toMillis(6); /** * How long we consider a "very long time". If no update took place in this time, * the content provider will trigger an update in the background. */ - private static final long VERY_LONG_TIME = 14 * DateUtils.DAY_IN_MILLIS; - - /** - * The last seen start Id. This must be stored because we must only call stopSelfResult() with - * the last seen Id, or the service won't stop. - */ - private int mLastSeenStartId; + private static final long VERY_LONG_TIME = TimeUnit.DAYS.toMillis(14); /** - * The command count. We need this because we need to not call stopSelfResult() while we still - * have commands running. + * An executor that serializes tasks given to it. */ - private int mCommandCount; + private ThreadPoolExecutor mExecutor; + private static final int WORKER_THREAD_TIMEOUT_SECONDS = 15; @Override public void onCreate() { - mLastSeenStartId = 0; - mCommandCount = 0; + // By default, a thread pool executor does not timeout its core threads, so it will + // 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 @@ -136,33 +136,35 @@ public final class DictionaryService extends Service { * - Handle a finished download. * This executes the actions that must be taken after a file (metadata or dictionary data * 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 public synchronized int onStartCommand(final Intent intent, final int flags, final int startId) { final DictionaryService self = this; - mLastSeenStartId = startId; - mCommandCount += 1; if (SHOW_DOWNLOAD_TOAST_INTENT_ACTION.equals(intent.getAction())) { // This is a UI action, it can't be run in another thread showStartDownloadingToast(this, LocaleUtils.constructLocaleFromString( intent.getStringExtra(LOCALE_INTENT_ARGUMENT))); } else { - // If it's a command that does not require UI, create a thread to do the work - // and return right away. DATE_CHANGED or UPDATE_NOW are examples of such commands. - new Thread("updateOrFinishDownload") { + // If it's a command that does not require UI, arrange for the work to be done on a + // separate thread, so that we can return right away. The executor will spawn a thread + // 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 public void run() { dispatchBroadcast(self, intent); - synchronized(self) { - if (--mCommandCount <= 0) { - if (!stopSelfResult(mLastSeenStartId)) { - Log.e(TAG, "Can't stop ourselves"); - } - } - } + // Since calls to onStartCommand are serialized, the submissions to the executor + // are serialized. That means we are guaranteed to call the stopSelfResult() + // in the same order that we got them, so we don't need to take care of the + // order. + stopSelfResult(startId); } - }.start(); + }); } return Service.START_REDELIVER_INTENT; } @@ -173,9 +175,9 @@ public final class DictionaryService extends Service { // at midnight local time, but it may happen if the user changes the date // by hand or something similar happens. checkTimeAndMaybeSetupUpdateAlarm(context); - } else if (UPDATE_NOW_INTENT_ACTION.equals(intent.getAction())) { + } else if (DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION.equals(intent.getAction())) { // Intent to trigger an update now. - UpdateHandler.update(context, false); + UpdateHandler.tryUpdate(context, false); } else { UpdateHandler.downloadFinished(context, intent); } @@ -196,7 +198,7 @@ public final class DictionaryService extends Service { // It doesn't matter too much if this is very inexact. final long now = System.currentTimeMillis(); final long alarmTime = now + new Random().nextInt(MAX_ALARM_DELAY); - final Intent updateIntent = new Intent(DictionaryService.UPDATE_NOW_INTENT_ACTION); + final Intent updateIntent = new Intent(DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION); final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, updateIntent, PendingIntent.FLAG_CANCEL_CURRENT); @@ -226,7 +228,7 @@ public final class DictionaryService extends Service { */ public static void updateNowIfNotUpdatedInAVeryLongTime(final Context context) { if (!isLastUpdateAtLeastThisOld(context, VERY_LONG_TIME)) return; - UpdateHandler.update(context, false); + UpdateHandler.tryUpdate(context, false); } /** |