Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.app;
     18 
     19 import android.os.Handler;
     20 import android.os.HandlerThread;
     21 import android.os.Looper;
     22 import android.os.Message;
     23 import android.os.Process;
     24 import android.os.StrictMode;
     25 import android.util.Log;
     26 
     27 import com.android.internal.annotations.GuardedBy;
     28 import com.android.internal.util.ExponentiallyBucketedHistogram;
     29 
     30 import java.util.LinkedList;
     31 
     32 /**
     33  * Internal utility class to keep track of process-global work that's outstanding and hasn't been
     34  * finished yet.
     35  *
     36  * New work will be {@link #queue queued}.
     37  *
     38  * It is possible to add 'finisher'-runnables that are {@link #waitToFinish guaranteed to be run}.
     39  * This is used to make sure the work has been finished.
     40  *
     41  * This was created for writing SharedPreference edits out asynchronously so we'd have a mechanism
     42  * to wait for the writes in Activity.onPause and similar places, but we may use this mechanism for
     43  * other things in the future.
     44  *
     45  * The queued asynchronous work is performed on a separate, dedicated thread.
     46  *
     47  * @hide
     48  */
     49 public class QueuedWork {
     50     private static final String LOG_TAG = QueuedWork.class.getSimpleName();
     51     private static final boolean DEBUG = false;
     52 
     53     /** Delay for delayed runnables, as big as possible but low enough to be barely perceivable */
     54     private static final long DELAY = 100;
     55 
     56     /** If a {@link #waitToFinish()} takes more than {@value #MAX_WAIT_TIME_MILLIS} ms, warn */
     57     private static final long MAX_WAIT_TIME_MILLIS = 512;
     58 
     59     /** Lock for this class */
     60     private static final Object sLock = new Object();
     61 
     62     /**
     63      * Used to make sure that only one thread is processing work items at a time. This means that
     64      * they are processed in the order added.
     65      *
     66      * This is separate from {@link #sLock} as this is held the whole time while work is processed
     67      * and we do not want to stall the whole class.
     68      */
     69     private static Object sProcessingWork = new Object();
     70 
     71     /** Finishers {@link #addFinisher added} and not yet {@link #removeFinisher removed} */
     72     @GuardedBy("sLock")
     73     private static final LinkedList<Runnable> sFinishers = new LinkedList<>();
     74 
     75     /** {@link #getHandler() Lazily} created handler */
     76     @GuardedBy("sLock")
     77     private static Handler sHandler = null;
     78 
     79     /** Work queued via {@link #queue} */
     80     @GuardedBy("sLock")
     81     private static final LinkedList<Runnable> sWork = new LinkedList<>();
     82 
     83     /** If new work can be delayed or not */
     84     @GuardedBy("sLock")
     85     private static boolean sCanDelay = true;
     86 
     87     /** Time (and number of instances) waited for work to get processed */
     88     @GuardedBy("sLock")
     89     private final static ExponentiallyBucketedHistogram
     90             mWaitTimes = new ExponentiallyBucketedHistogram(
     91             16);
     92     private static int mNumWaits = 0;
     93 
     94     /**
     95      * Lazily create a handler on a separate thread.
     96      *
     97      * @return the handler
     98      */
     99     private static Handler getHandler() {
    100         synchronized (sLock) {
    101             if (sHandler == null) {
    102                 HandlerThread handlerThread = new HandlerThread("queued-work-looper",
    103                         Process.THREAD_PRIORITY_FOREGROUND);
    104                 handlerThread.start();
    105 
    106                 sHandler = new QueuedWorkHandler(handlerThread.getLooper());
    107             }
    108             return sHandler;
    109         }
    110     }
    111 
    112     /**
    113      * Add a finisher-runnable to wait for {@link #queue asynchronously processed work}.
    114      *
    115      * Used by SharedPreferences$Editor#startCommit().
    116      *
    117      * Note that this doesn't actually start it running.  This is just a scratch set for callers
    118      * doing async work to keep updated with what's in-flight. In the common case, caller code
    119      * (e.g. SharedPreferences) will pretty quickly call remove() after an add(). The only time
    120      * these Runnables are run is from {@link #waitToFinish}.
    121      *
    122      * @param finisher The runnable to add as finisher
    123      */
    124     public static void addFinisher(Runnable finisher) {
    125         synchronized (sLock) {
    126             sFinishers.add(finisher);
    127         }
    128     }
    129 
    130     /**
    131      * Remove a previously {@link #addFinisher added} finisher-runnable.
    132      *
    133      * @param finisher The runnable to remove.
    134      */
    135     public static void removeFinisher(Runnable finisher) {
    136         synchronized (sLock) {
    137             sFinishers.remove(finisher);
    138         }
    139     }
    140 
    141     /**
    142      * Trigger queued work to be processed immediately. The queued work is processed on a separate
    143      * thread asynchronous. While doing that run and process all finishers on this thread. The
    144      * finishers can be implemented in a way to check weather the queued work is finished.
    145      *
    146      * Is called from the Activity base class's onPause(), after BroadcastReceiver's onReceive,
    147      * after Service command handling, etc. (so async work is never lost)
    148      */
    149     public static void waitToFinish() {
    150         long startTime = System.currentTimeMillis();
    151         boolean hadMessages = false;
    152 
    153         Handler handler = getHandler();
    154 
    155         synchronized (sLock) {
    156             if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) {
    157                 // Delayed work will be processed at processPendingWork() below
    158                 handler.removeMessages(QueuedWorkHandler.MSG_RUN);
    159 
    160                 if (DEBUG) {
    161                     hadMessages = true;
    162                     Log.d(LOG_TAG, "waiting");
    163                 }
    164             }
    165 
    166             // We should not delay any work as this might delay the finishers
    167             sCanDelay = false;
    168         }
    169 
    170         StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
    171         try {
    172             processPendingWork();
    173         } finally {
    174             StrictMode.setThreadPolicy(oldPolicy);
    175         }
    176 
    177         try {
    178             while (true) {
    179                 Runnable finisher;
    180 
    181                 synchronized (sLock) {
    182                     finisher = sFinishers.poll();
    183                 }
    184 
    185                 if (finisher == null) {
    186                     break;
    187                 }
    188 
    189                 finisher.run();
    190             }
    191         } finally {
    192             sCanDelay = true;
    193         }
    194 
    195         synchronized (sLock) {
    196             long waitTime = System.currentTimeMillis() - startTime;
    197 
    198             if (waitTime > 0 || hadMessages) {
    199                 mWaitTimes.add(Long.valueOf(waitTime).intValue());
    200                 mNumWaits++;
    201 
    202                 if (DEBUG || mNumWaits % 1024 == 0 || waitTime > MAX_WAIT_TIME_MILLIS) {
    203                     mWaitTimes.log(LOG_TAG, "waited: ");
    204                 }
    205             }
    206         }
    207     }
    208 
    209     /**
    210      * Queue a work-runnable for processing asynchronously.
    211      *
    212      * @param work The new runnable to process
    213      * @param shouldDelay If the message should be delayed
    214      */
    215     public static void queue(Runnable work, boolean shouldDelay) {
    216         Handler handler = getHandler();
    217 
    218         synchronized (sLock) {
    219             sWork.add(work);
    220 
    221             if (shouldDelay && sCanDelay) {
    222                 handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
    223             } else {
    224                 handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
    225             }
    226         }
    227     }
    228 
    229     /**
    230      * @return True iff there is any {@link #queue async work queued}.
    231      */
    232     public static boolean hasPendingWork() {
    233         synchronized (sLock) {
    234             return !sWork.isEmpty();
    235         }
    236     }
    237 
    238     private static void processPendingWork() {
    239         long startTime = 0;
    240 
    241         if (DEBUG) {
    242             startTime = System.currentTimeMillis();
    243         }
    244 
    245         synchronized (sProcessingWork) {
    246             LinkedList<Runnable> work;
    247 
    248             synchronized (sLock) {
    249                 work = (LinkedList<Runnable>) sWork.clone();
    250                 sWork.clear();
    251 
    252                 // Remove all msg-s as all work will be processed now
    253                 getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
    254             }
    255 
    256             if (work.size() > 0) {
    257                 for (Runnable w : work) {
    258                     w.run();
    259                 }
    260 
    261                 if (DEBUG) {
    262                     Log.d(LOG_TAG, "processing " + work.size() + " items took " +
    263                             +(System.currentTimeMillis() - startTime) + " ms");
    264                 }
    265             }
    266         }
    267     }
    268 
    269     private static class QueuedWorkHandler extends Handler {
    270         static final int MSG_RUN = 1;
    271 
    272         QueuedWorkHandler(Looper looper) {
    273             super(looper);
    274         }
    275 
    276         public void handleMessage(Message msg) {
    277             if (msg.what == MSG_RUN) {
    278                 processPendingWork();
    279             }
    280         }
    281     }
    282 }
    283