diff options
author | Philip P. Moltmann <moltmann@google.com> | 2017-02-14 16:03:27 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2017-02-14 16:03:32 +0000 |
commit | d043a840f7f12ce2c54e7d4564a6ac9cfa24dd17 (patch) | |
tree | 698f6dce0f8e92c34ccca039b84d440865d311ac | |
parent | f47658f1206c35face246193a425fc23765031f3 (diff) | |
parent | 755d78dbd9d61d1746b638c6fc952d2043c6561b (diff) | |
download | base-d043a840f7f12ce2c54e7d4564a6ac9cfa24dd17.tar.gz |
Merge "[DO NOT MERGE] Revert "[DO NOT MERGE] Delay SharedPreferences.apply() by 50 ms"" into nyc-mr2-dev
-rw-r--r-- | core/java/android/app/QueuedWork.java | 209 | ||||
-rw-r--r-- | core/java/android/app/SharedPreferencesImpl.java | 8 | ||||
-rw-r--r-- | core/java/android/content/BroadcastReceiver.java | 6 |
3 files changed, 56 insertions, 167 deletions
diff --git a/core/java/android/app/QueuedWork.java b/core/java/android/app/QueuedWork.java index 0ae85056da56..6ee478059171 100644 --- a/core/java/android/app/QueuedWork.java +++ b/core/java/android/app/QueuedWork.java @@ -16,197 +16,86 @@ package android.app; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; -import android.os.Process; -import android.util.Log; - -import com.android.internal.annotations.GuardedBy; - -import java.util.LinkedList; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; /** - * Internal utility class to keep track of process-global work that's outstanding and hasn't been - * finished yet. - * - * New work will be {@link #queue queued}. + * Internal utility class to keep track of process-global work that's + * outstanding and hasn't been finished yet. * - * It is possible to add 'finisher'-runnables that are {@link #waitToFinish guaranteed to be run}. - * This is used to make sure the work has been finished. - * - * This was created for writing SharedPreference edits out asynchronously so we'd have a mechanism - * to wait for the writes in Activity.onPause and similar places, but we may use this mechanism for - * other things in the future. - * - * The queued asynchronous work is performed on a separate, dedicated thread. + * This was created for writing SharedPreference edits out + * asynchronously so we'd have a mechanism to wait for the writes in + * Activity.onPause and similar places, but we may use this mechanism + * for other things in the future. * * @hide */ public class QueuedWork { - private static final String LOG_TAG = QueuedWork.class.getSimpleName(); - - /** Delay for delayed runnables */ - private static final long DELAY = 50; - /** Lock for this class */ - private static final Object sLock = new Object(); + // The set of Runnables that will finish or wait on any async + // activities started by the application. + private static final ConcurrentLinkedQueue<Runnable> sPendingWorkFinishers = + new ConcurrentLinkedQueue<Runnable>(); - /** Finishers {@link #addFinisher added} and not yet {@link #removeFinisher removed} */ - @GuardedBy("sLock") - private static final LinkedList<Runnable> sFinishers = new LinkedList<>(); - - /** {@link #getHandler() Lazily} created handler */ - @GuardedBy("sLock") - private static Handler sHandler = null; - - /** Work queued via {@link #queue} */ - @GuardedBy("sLock") - private static final LinkedList<Runnable> sWork = new LinkedList<>(); - - /** If new work can be delayed or not */ - @GuardedBy("sLock") - private static boolean sCanDelay = true; + private static ExecutorService sSingleThreadExecutor = null; // lazy, guarded by class /** - * Lazily create a handler on a separate thread. - * - * @return the handler + * Returns a single-thread Executor shared by the entire process, + * creating it if necessary. */ - private static Handler getHandler() { - synchronized (sLock) { - if (sHandler == null) { - HandlerThread handlerThread = new HandlerThread("queued-work-looper", - Process.THREAD_PRIORITY_BACKGROUND); - handlerThread.start(); - - sHandler = new QueuedWorkHandler(handlerThread.getLooper()); + public static ExecutorService singleThreadExecutor() { + synchronized (QueuedWork.class) { + if (sSingleThreadExecutor == null) { + // TODO: can we give this single thread a thread name? + sSingleThreadExecutor = Executors.newSingleThreadExecutor(); } - return sHandler; + return sSingleThreadExecutor; } } /** - * Add a finisher-runnable to wait for {@link #queue asynchronously processed work}. - * - * Used by SharedPreferences$Editor#startCommit(). + * Add a runnable to finish (or wait for) a deferred operation + * started in this context earlier. Typically finished by e.g. + * an Activity#onPause. Used by SharedPreferences$Editor#startCommit(). * - * Note that this doesn't actually start it running. This is just a scratch set for callers - * doing async work to keep updated with what's in-flight. In the common case, caller code - * (e.g. SharedPreferences) will pretty quickly call remove() after an add(). The only time - * these Runnables are run is from {@link #waitToFinish}. - * - * @param finisher The runnable to add as finisher + * Note that this doesn't actually start it running. This is just + * a scratch set for callers doing async work to keep updated with + * what's in-flight. In the common case, caller code + * (e.g. SharedPreferences) will pretty quickly call remove() + * after an add(). The only time these Runnables are run is from + * waitToFinish(), below. */ - public static void addFinisher(Runnable finisher) { - synchronized (sLock) { - sFinishers.add(finisher); - } + public static void add(Runnable finisher) { + sPendingWorkFinishers.add(finisher); } - /** - * Remove a previously {@link #addFinisher added} finisher-runnable. - * - * @param finisher The runnable to remove. - */ - public static void removeFinisher(Runnable finisher) { - synchronized (sLock) { - sFinishers.remove(finisher); - } + public static void remove(Runnable finisher) { + sPendingWorkFinishers.remove(finisher); } /** - * Trigger queued work to be processed immediately. The queued work is processed on a separate - * thread asynchronous. While doing that run and process all finishers on this thread. The - * finishers can be implemented in a way to check weather the queued work is finished. + * Finishes or waits for async operations to complete. + * (e.g. SharedPreferences$Editor#startCommit writes) * - * Is called from the Activity base class's onPause(), after BroadcastReceiver's onReceive, - * after Service command handling, etc. (so async work is never lost) + * Is called from the Activity base class's onPause(), after + * BroadcastReceiver's onReceive, after Service command handling, + * etc. (so async work is never lost) */ public static void waitToFinish() { - Handler handler = getHandler(); - - synchronized (sLock) { - if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) { - // Force the delayed work to be processed now - handler.removeMessages(QueuedWorkHandler.MSG_RUN); - handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN); - } - - // We should not delay any work as this might delay the finishers - sCanDelay = false; - } - - try { - while (true) { - Runnable finisher; - - synchronized (sLock) { - finisher = sFinishers.poll(); - } - - if (finisher == null) { - break; - } - - finisher.run(); - } - } finally { - sCanDelay = true; - } - } - - /** - * Queue a work-runnable for processing asynchronously. - * - * @param work The new runnable to process - * @param shouldDelay If the message should be delayed - */ - public static void queue(Runnable work, boolean shouldDelay) { - Handler handler = getHandler(); - - synchronized (sLock) { - sWork.add(work); - - if (shouldDelay && sCanDelay) { - handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY); - } else { - handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN); - } + Runnable toFinish; + while ((toFinish = sPendingWorkFinishers.poll()) != null) { + toFinish.run(); } } - + /** - * @return True iff there is any {@link #queue async work queued}. + * Returns true if there is pending work to be done. Note that the + * result is out of data as soon as you receive it, so be careful how you + * use it. */ public static boolean hasPendingWork() { - synchronized (sLock) { - return !sWork.isEmpty(); - } - } - - private static class QueuedWorkHandler extends Handler { - static final int MSG_RUN = 1; - - QueuedWorkHandler(Looper looper) { - super(looper); - } - - public void handleMessage(Message msg) { - if (msg.what == MSG_RUN) { - LinkedList<Runnable> work; - - synchronized (sWork) { - work = (LinkedList<Runnable>) sWork.clone(); - sWork.clear(); - - // Remove all msg-s as all work will be processed now - removeMessages(MSG_RUN); - } - - work.forEach(Runnable::run); - } - } + return !sPendingWorkFinishers.isEmpty(); } + } diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java index 59434331b36a..f273cd8670f0 100644 --- a/core/java/android/app/SharedPreferencesImpl.java +++ b/core/java/android/app/SharedPreferencesImpl.java @@ -379,12 +379,12 @@ final class SharedPreferencesImpl implements SharedPreferences { } }; - QueuedWork.addFinisher(awaitCommit); + QueuedWork.add(awaitCommit); Runnable postWriteRunnable = new Runnable() { public void run() { awaitCommit.run(); - QueuedWork.removeFinisher(awaitCommit); + QueuedWork.remove(awaitCommit); } }; @@ -557,10 +557,10 @@ final class SharedPreferencesImpl implements SharedPreferences { } if (DEBUG) { - Log.d(TAG, "queued " + mcr.memoryStateGeneration + " -> " + mFile.getName()); + Log.d(TAG, "added " + mcr.memoryStateGeneration + " -> " + mFile.getName()); } - QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit); + QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable); } private static FileOutputStream createFileOutputStream(File file) { diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java index 68c25ef5bd06..a7a86158edee 100644 --- a/core/java/android/content/BroadcastReceiver.java +++ b/core/java/android/content/BroadcastReceiver.java @@ -211,16 +211,16 @@ public abstract class BroadcastReceiver { // of the list to finish the broadcast, so we don't block this // thread (which may be the main thread) to have it finished. // - // Note that we don't need to use QueuedWork.addFinisher() with the + // Note that we don't need to use QueuedWork.add() with the // runnable, since we know the AM is waiting for us until the // executor gets to it. - QueuedWork.queue(new Runnable() { + QueuedWork.singleThreadExecutor().execute( new Runnable() { @Override public void run() { if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, "Finishing broadcast after work to component " + mToken); sendFinished(mgr); } - }, false); + }); } else { if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, "Finishing broadcast to component " + mToken); |