/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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.os.StrictMode;
import android.util.Log;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ExponentiallyBucketedHistogram;

import java.util.LinkedList;

Internal utility class to keep track of process-global work that's outstanding and hasn't been finished yet. New work will be queued. It is possible to add 'finisher'-runnables that are 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.
@hide
/** * 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}. * * 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. * * @hide */
public class QueuedWork { private static final String LOG_TAG = QueuedWork.class.getSimpleName(); private static final boolean DEBUG = false;
Delay for delayed runnables, as big as possible but low enough to be barely perceivable
/** Delay for delayed runnables, as big as possible but low enough to be barely perceivable */
private static final long DELAY = 100;
If a waitToFinish() takes more than 512 ms, warn
/** If a {@link #waitToFinish()} takes more than {@value #MAX_WAIT_TIME_MILLIS} ms, warn */
private static final long MAX_WAIT_TIME_MILLIS = 512;
Lock for this class
/** Lock for this class */
private static final Object sLock = new Object();
Used to make sure that only one thread is processing work items at a time. This means that they are processed in the order added. This is separate from sLock as this is held the whole time while work is processed and we do not want to stall the whole class.
/** * Used to make sure that only one thread is processing work items at a time. This means that * they are processed in the order added. * * This is separate from {@link #sLock} as this is held the whole time while work is processed * and we do not want to stall the whole class. */
private static Object sProcessingWork = new Object();
Finishers added and not yet removed
/** Finishers {@link #addFinisher added} and not yet {@link #removeFinisher removed} */
@GuardedBy("sLock") private static final LinkedList<Runnable> sFinishers = new LinkedList<>();
Lazily created handler
/** {@link #getHandler() Lazily} created handler */
@GuardedBy("sLock") private static Handler sHandler = null;
Work queued via queue
/** Work queued via {@link #queue} */
@GuardedBy("sLock") private static final LinkedList<Runnable> sWork = new LinkedList<>();
If new work can be delayed or not
/** If new work can be delayed or not */
@GuardedBy("sLock") private static boolean sCanDelay = true;
Time (and number of instances) waited for work to get processed
/** Time (and number of instances) waited for work to get processed */
@GuardedBy("sLock") private final static ExponentiallyBucketedHistogram mWaitTimes = new ExponentiallyBucketedHistogram( 16); private static int mNumWaits = 0;
Lazily create a handler on a separate thread.
Returns:the handler
/** * Lazily create a handler on a separate thread. * * @return the handler */
private static Handler getHandler() { synchronized (sLock) { if (sHandler == null) { HandlerThread handlerThread = new HandlerThread("queued-work-looper", Process.THREAD_PRIORITY_FOREGROUND); handlerThread.start(); sHandler = new QueuedWorkHandler(handlerThread.getLooper()); } return sHandler; } }
Add a finisher-runnable to wait for asynchronously processed work. 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 waitToFinish.
Params:
  • finisher – The runnable to add as finisher
/** * Add a finisher-runnable to wait for {@link #queue asynchronously processed work}. * * 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 */
public static void addFinisher(Runnable finisher) { synchronized (sLock) { sFinishers.add(finisher); } }
Remove a previously added finisher-runnable.
Params:
  • finisher – The runnable to remove.
/** * 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); } }
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. Is called from the Activity base class's onPause(), after BroadcastReceiver's onReceive, after Service command handling, etc. (so async work is never lost)
/** * 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. * * 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() { long startTime = System.currentTimeMillis(); boolean hadMessages = false; Handler handler = getHandler(); synchronized (sLock) { if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) { // Delayed work will be processed at processPendingWork() below handler.removeMessages(QueuedWorkHandler.MSG_RUN); if (DEBUG) { hadMessages = true; Log.d(LOG_TAG, "waiting"); } } // We should not delay any work as this might delay the finishers sCanDelay = false; } StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); try { processPendingWork(); } finally { StrictMode.setThreadPolicy(oldPolicy); } try { while (true) { Runnable finisher; synchronized (sLock) { finisher = sFinishers.poll(); } if (finisher == null) { break; } finisher.run(); } } finally { sCanDelay = true; } synchronized (sLock) { long waitTime = System.currentTimeMillis() - startTime; if (waitTime > 0 || hadMessages) { mWaitTimes.add(Long.valueOf(waitTime).intValue()); mNumWaits++; if (DEBUG || mNumWaits % 1024 == 0 || waitTime > MAX_WAIT_TIME_MILLIS) { mWaitTimes.log(LOG_TAG, "waited: "); } } } }
Queue a work-runnable for processing asynchronously.
Params:
  • work – The new runnable to process
  • shouldDelay – If the message should be delayed
/** * 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); } } }
Returns:True iff there is any async work queued.
/** * @return True iff there is any {@link #queue async work queued}. */
public static boolean hasPendingWork() { synchronized (sLock) { return !sWork.isEmpty(); } } private static void processPendingWork() { long startTime = 0; if (DEBUG) { startTime = System.currentTimeMillis(); } synchronized (sProcessingWork) { LinkedList<Runnable> work; synchronized (sLock) { work = (LinkedList<Runnable>) sWork.clone(); sWork.clear(); // Remove all msg-s as all work will be processed now getHandler().removeMessages(QueuedWorkHandler.MSG_RUN); } if (work.size() > 0) { for (Runnable w : work) { w.run(); } if (DEBUG) { Log.d(LOG_TAG, "processing " + work.size() + " items took " + +(System.currentTimeMillis() - startTime) + " ms"); } } } } 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) { processPendingWork(); } } } }