Home | History | Annotate | Download | only in content
      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.content;
     18 
     19 import android.os.AsyncTask;
     20 import android.os.Handler;
     21 import android.os.OperationCanceledException;
     22 import android.os.SystemClock;
     23 import android.util.Slog;
     24 import android.util.TimeUtils;
     25 
     26 import java.io.FileDescriptor;
     27 import java.io.PrintWriter;
     28 import java.util.concurrent.CountDownLatch;
     29 import java.util.concurrent.Executor;
     30 
     31 /**
     32  * Abstract Loader that provides an {@link AsyncTask} to do the work.  See
     33  * {@link Loader} and {@link android.app.LoaderManager} for more details.
     34  *
     35  * <p>Here is an example implementation of an AsyncTaskLoader subclass that
     36  * loads the currently installed applications from the package manager.  This
     37  * implementation takes care of retrieving the application labels and sorting
     38  * its result set from them, monitoring for changes to the installed
     39  * applications, and rebuilding the list when a change in configuration requires
     40  * this (such as a locale change).
     41  *
     42  * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java
     43  *      loader}
     44  *
     45  * <p>An example implementation of a fragment that uses the above loader to show
     46  * the currently installed applications in a list is below.
     47  *
     48  * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java
     49  *      fragment}
     50  *
     51  * @param <D> the data type to be loaded.
     52  */
     53 public abstract class AsyncTaskLoader<D> extends Loader<D> {
     54     static final String TAG = "AsyncTaskLoader";
     55     static final boolean DEBUG = false;
     56 
     57     final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable {
     58         private final CountDownLatch mDone = new CountDownLatch(1);
     59 
     60         // Set to true to indicate that the task has been posted to a handler for
     61         // execution at a later time.  Used to throttle updates.
     62         boolean waiting;
     63 
     64         /* Runs on a worker thread */
     65         @Override
     66         protected D doInBackground(Void... params) {
     67             if (DEBUG) Slog.v(TAG, this + " >>> doInBackground");
     68             try {
     69                 D data = AsyncTaskLoader.this.onLoadInBackground();
     70                 if (DEBUG) Slog.v(TAG, this + "  <<< doInBackground");
     71                 return data;
     72             } catch (OperationCanceledException ex) {
     73                 if (!isCancelled()) {
     74                     // onLoadInBackground threw a canceled exception spuriously.
     75                     // This is problematic because it means that the LoaderManager did not
     76                     // cancel the Loader itself and still expects to receive a result.
     77                     // Additionally, the Loader's own state will not have been updated to
     78                     // reflect the fact that the task was being canceled.
     79                     // So we treat this case as an unhandled exception.
     80                     throw ex;
     81                 }
     82                 if (DEBUG) Slog.v(TAG, this + "  <<< doInBackground (was canceled)", ex);
     83                 return null;
     84             }
     85         }
     86 
     87         /* Runs on the UI thread */
     88         @Override
     89         protected void onPostExecute(D data) {
     90             if (DEBUG) Slog.v(TAG, this + " onPostExecute");
     91             try {
     92                 AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
     93             } finally {
     94                 mDone.countDown();
     95             }
     96         }
     97 
     98         /* Runs on the UI thread */
     99         @Override
    100         protected void onCancelled(D data) {
    101             if (DEBUG) Slog.v(TAG, this + " onCancelled");
    102             try {
    103                 AsyncTaskLoader.this.dispatchOnCancelled(this, data);
    104             } finally {
    105                 mDone.countDown();
    106             }
    107         }
    108 
    109         /* Runs on the UI thread, when the waiting task is posted to a handler.
    110          * This method is only executed when task execution was deferred (waiting was true). */
    111         @Override
    112         public void run() {
    113             waiting = false;
    114             AsyncTaskLoader.this.executePendingTask();
    115         }
    116 
    117         /* Used for testing purposes to wait for the task to complete. */
    118         public void waitForLoader() {
    119             try {
    120                 mDone.await();
    121             } catch (InterruptedException e) {
    122                 // Ignore
    123             }
    124         }
    125     }
    126 
    127     private final Executor mExecutor;
    128 
    129     volatile LoadTask mTask;
    130     volatile LoadTask mCancellingTask;
    131 
    132     long mUpdateThrottle;
    133     long mLastLoadCompleteTime = -10000;
    134     Handler mHandler;
    135 
    136     public AsyncTaskLoader(Context context) {
    137         this(context, AsyncTask.THREAD_POOL_EXECUTOR);
    138     }
    139 
    140     /** {@hide} */
    141     public AsyncTaskLoader(Context context, Executor executor) {
    142         super(context);
    143         mExecutor = executor;
    144     }
    145 
    146     /**
    147      * Set amount to throttle updates by.  This is the minimum time from
    148      * when the last {@link #loadInBackground()} call has completed until
    149      * a new load is scheduled.
    150      *
    151      * @param delayMS Amount of delay, in milliseconds.
    152      */
    153     public void setUpdateThrottle(long delayMS) {
    154         mUpdateThrottle = delayMS;
    155         if (delayMS != 0) {
    156             mHandler = new Handler();
    157         }
    158     }
    159 
    160     @Override
    161     protected void onForceLoad() {
    162         super.onForceLoad();
    163         cancelLoad();
    164         mTask = new LoadTask();
    165         if (DEBUG) Slog.v(TAG, "Preparing load: mTask=" + mTask);
    166         executePendingTask();
    167     }
    168 
    169     @Override
    170     protected boolean onCancelLoad() {
    171         if (DEBUG) Slog.v(TAG, "onCancelLoad: mTask=" + mTask);
    172         if (mTask != null) {
    173             if (mCancellingTask != null) {
    174                 // There was a pending task already waiting for a previous
    175                 // one being canceled; just drop it.
    176                 if (DEBUG) Slog.v(TAG,
    177                         "cancelLoad: still waiting for cancelled task; dropping next");
    178                 if (mTask.waiting) {
    179                     mTask.waiting = false;
    180                     mHandler.removeCallbacks(mTask);
    181                 }
    182                 mTask = null;
    183                 return false;
    184             } else if (mTask.waiting) {
    185                 // There is a task, but it is waiting for the time it should
    186                 // execute.  We can just toss it.
    187                 if (DEBUG) Slog.v(TAG, "cancelLoad: task is waiting, dropping it");
    188                 mTask.waiting = false;
    189                 mHandler.removeCallbacks(mTask);
    190                 mTask = null;
    191                 return false;
    192             } else {
    193                 boolean cancelled = mTask.cancel(false);
    194                 if (DEBUG) Slog.v(TAG, "cancelLoad: cancelled=" + cancelled);
    195                 if (cancelled) {
    196                     mCancellingTask = mTask;
    197                     cancelLoadInBackground();
    198                 }
    199                 mTask = null;
    200                 return cancelled;
    201             }
    202         }
    203         return false;
    204     }
    205 
    206     /**
    207      * Called if the task was canceled before it was completed.  Gives the class a chance
    208      * to clean up post-cancellation and to properly dispose of the result.
    209      *
    210      * @param data The value that was returned by {@link #loadInBackground}, or null
    211      * if the task threw {@link OperationCanceledException}.
    212      */
    213     public void onCanceled(D data) {
    214     }
    215 
    216     void executePendingTask() {
    217         if (mCancellingTask == null && mTask != null) {
    218             if (mTask.waiting) {
    219                 mTask.waiting = false;
    220                 mHandler.removeCallbacks(mTask);
    221             }
    222             if (mUpdateThrottle > 0) {
    223                 long now = SystemClock.uptimeMillis();
    224                 if (now < (mLastLoadCompleteTime+mUpdateThrottle)) {
    225                     // Not yet time to do another load.
    226                     if (DEBUG) Slog.v(TAG, "Waiting until "
    227                             + (mLastLoadCompleteTime+mUpdateThrottle)
    228                             + " to execute: " + mTask);
    229                     mTask.waiting = true;
    230                     mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle);
    231                     return;
    232                 }
    233             }
    234             if (DEBUG) Slog.v(TAG, "Executing: " + mTask);
    235             mTask.executeOnExecutor(mExecutor, (Void[]) null);
    236         }
    237     }
    238 
    239     void dispatchOnCancelled(LoadTask task, D data) {
    240         onCanceled(data);
    241         if (mCancellingTask == task) {
    242             if (DEBUG) Slog.v(TAG, "Cancelled task is now canceled!");
    243             rollbackContentChanged();
    244             mLastLoadCompleteTime = SystemClock.uptimeMillis();
    245             mCancellingTask = null;
    246             if (DEBUG) Slog.v(TAG, "Delivering cancellation");
    247             deliverCancellation();
    248             executePendingTask();
    249         }
    250     }
    251 
    252     void dispatchOnLoadComplete(LoadTask task, D data) {
    253         if (mTask != task) {
    254             if (DEBUG) Slog.v(TAG, "Load complete of old task, trying to cancel");
    255             dispatchOnCancelled(task, data);
    256         } else {
    257             if (isAbandoned()) {
    258                 // This cursor has been abandoned; just cancel the new data.
    259                 onCanceled(data);
    260             } else {
    261                 commitContentChanged();
    262                 mLastLoadCompleteTime = SystemClock.uptimeMillis();
    263                 mTask = null;
    264                 if (DEBUG) Slog.v(TAG, "Delivering result");
    265                 deliverResult(data);
    266             }
    267         }
    268     }
    269 
    270     /**
    271      * Called on a worker thread to perform the actual load and to return
    272      * the result of the load operation.
    273      *
    274      * Implementations should not deliver the result directly, but should return them
    275      * from this method, which will eventually end up calling {@link #deliverResult} on
    276      * the UI thread.  If implementations need to process the results on the UI thread
    277      * they may override {@link #deliverResult} and do so there.
    278      *
    279      * To support cancellation, this method should periodically check the value of
    280      * {@link #isLoadInBackgroundCanceled} and terminate when it returns true.
    281      * Subclasses may also override {@link #cancelLoadInBackground} to interrupt the load
    282      * directly instead of polling {@link #isLoadInBackgroundCanceled}.
    283      *
    284      * When the load is canceled, this method may either return normally or throw
    285      * {@link OperationCanceledException}.  In either case, the {@link Loader} will
    286      * call {@link #onCanceled} to perform post-cancellation cleanup and to dispose of the
    287      * result object, if any.
    288      *
    289      * @return The result of the load operation.
    290      *
    291      * @throws OperationCanceledException if the load is canceled during execution.
    292      *
    293      * @see #isLoadInBackgroundCanceled
    294      * @see #cancelLoadInBackground
    295      * @see #onCanceled
    296      */
    297     public abstract D loadInBackground();
    298 
    299     /**
    300      * Calls {@link #loadInBackground()}.
    301      *
    302      * This method is reserved for use by the loader framework.
    303      * Subclasses should override {@link #loadInBackground} instead of this method.
    304      *
    305      * @return The result of the load operation.
    306      *
    307      * @throws OperationCanceledException if the load is canceled during execution.
    308      *
    309      * @see #loadInBackground
    310      */
    311     protected D onLoadInBackground() {
    312         return loadInBackground();
    313     }
    314 
    315     /**
    316      * Called on the main thread to abort a load in progress.
    317      *
    318      * Override this method to abort the current invocation of {@link #loadInBackground}
    319      * that is running in the background on a worker thread.
    320      *
    321      * This method should do nothing if {@link #loadInBackground} has not started
    322      * running or if it has already finished.
    323      *
    324      * @see #loadInBackground
    325      */
    326     public void cancelLoadInBackground() {
    327     }
    328 
    329     /**
    330      * Returns true if the current invocation of {@link #loadInBackground} is being canceled.
    331      *
    332      * @return True if the current invocation of {@link #loadInBackground} is being canceled.
    333      *
    334      * @see #loadInBackground
    335      */
    336     public boolean isLoadInBackgroundCanceled() {
    337         return mCancellingTask != null;
    338     }
    339 
    340     /**
    341      * Locks the current thread until the loader completes the current load
    342      * operation. Returns immediately if there is no load operation running.
    343      * Should not be called from the UI thread: calling it from the UI
    344      * thread would cause a deadlock.
    345      * <p>
    346      * Use for testing only.  <b>Never</b> call this from a UI thread.
    347      *
    348      * @hide
    349      */
    350     public void waitForLoader() {
    351         LoadTask task = mTask;
    352         if (task != null) {
    353             task.waitForLoader();
    354         }
    355     }
    356 
    357     @Override
    358     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
    359         super.dump(prefix, fd, writer, args);
    360         if (mTask != null) {
    361             writer.print(prefix); writer.print("mTask="); writer.print(mTask);
    362                     writer.print(" waiting="); writer.println(mTask.waiting);
    363         }
    364         if (mCancellingTask != null) {
    365             writer.print(prefix); writer.print("mCancellingTask="); writer.print(mCancellingTask);
    366                     writer.print(" waiting="); writer.println(mCancellingTask.waiting);
    367         }
    368         if (mUpdateThrottle != 0) {
    369             writer.print(prefix); writer.print("mUpdateThrottle=");
    370                     TimeUtils.formatDuration(mUpdateThrottle, writer);
    371                     writer.print(" mLastLoadCompleteTime=");
    372                     TimeUtils.formatDuration(mLastLoadCompleteTime,
    373                             SystemClock.uptimeMillis(), writer);
    374                     writer.println();
    375         }
    376     }
    377 }
    378