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