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.SystemClock;
     22 import android.util.Slog;
     23 import android.util.TimeUtils;
     24 
     25 import java.io.FileDescriptor;
     26 import java.io.PrintWriter;
     27 import java.util.concurrent.CountDownLatch;
     28 
     29 /**
     30  * Abstract Loader that provides an {@link AsyncTask} to do the work.  See
     31  * {@link Loader} and {@link android.app.LoaderManager} for more details.
     32  *
     33  * <p>Here is an example implementation of an AsyncTaskLoader subclass that
     34  * loads the currently installed applications from the package manager.  This
     35  * implementation takes care of retrieving the application labels and sorting
     36  * its result set from them, monitoring for changes to the installed
     37  * applications, and rebuilding the list when a change in configuration requires
     38  * this (such as a locale change).
     39  *
     40  * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java
     41  *      loader}
     42  *
     43  * <p>An example implementation of a fragment that uses the above loader to show
     44  * the currently installed applications in a list is below.
     45  *
     46  * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java
     47  *      fragment}
     48  *
     49  * @param <D> the data type to be loaded.
     50  */
     51 public abstract class AsyncTaskLoader<D> extends Loader<D> {
     52     static final String TAG = "AsyncTaskLoader";
     53     static final boolean DEBUG = false;
     54 
     55     final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable {
     56 
     57         D result;
     58         boolean waiting;
     59 
     60         private CountDownLatch done = new CountDownLatch(1);
     61 
     62         /* Runs on a worker thread */
     63         @Override
     64         protected D doInBackground(Void... params) {
     65             if (DEBUG) Slog.v(TAG, this + " >>> doInBackground");
     66             result = AsyncTaskLoader.this.onLoadInBackground();
     67             if (DEBUG) Slog.v(TAG, this + "  <<< doInBackground");
     68             return result;
     69         }
     70 
     71         /* Runs on the UI thread */
     72         @Override
     73         protected void onPostExecute(D data) {
     74             if (DEBUG) Slog.v(TAG, this + " onPostExecute");
     75             try {
     76                 AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
     77             } finally {
     78                 done.countDown();
     79             }
     80         }
     81 
     82         @Override
     83         protected void onCancelled() {
     84             if (DEBUG) Slog.v(TAG, this + " onCancelled");
     85             try {
     86                 AsyncTaskLoader.this.dispatchOnCancelled(this, result);
     87             } finally {
     88                 done.countDown();
     89             }
     90         }
     91 
     92         @Override
     93         public void run() {
     94             waiting = false;
     95             AsyncTaskLoader.this.executePendingTask();
     96         }
     97     }
     98 
     99     volatile LoadTask mTask;
    100     volatile LoadTask mCancellingTask;
    101 
    102     long mUpdateThrottle;
    103     long mLastLoadCompleteTime = -10000;
    104     Handler mHandler;
    105 
    106     public AsyncTaskLoader(Context context) {
    107         super(context);
    108     }
    109 
    110     /**
    111      * Set amount to throttle updates by.  This is the minimum time from
    112      * when the last {@link #onLoadInBackground()} call has completed until
    113      * a new load is scheduled.
    114      *
    115      * @param delayMS Amount of delay, in milliseconds.
    116      */
    117     public void setUpdateThrottle(long delayMS) {
    118         mUpdateThrottle = delayMS;
    119         if (delayMS != 0) {
    120             mHandler = new Handler();
    121         }
    122     }
    123 
    124     @Override
    125     protected void onForceLoad() {
    126         super.onForceLoad();
    127         cancelLoad();
    128         mTask = new LoadTask();
    129         if (DEBUG) Slog.v(TAG, "Preparing load: mTask=" + mTask);
    130         executePendingTask();
    131     }
    132 
    133     /**
    134      * Attempt to cancel the current load task. See {@link AsyncTask#cancel(boolean)}
    135      * for more info.  Must be called on the main thread of the process.
    136      *
    137      * <p>Cancelling is not an immediate operation, since the load is performed
    138      * in a background thread.  If there is currently a load in progress, this
    139      * method requests that the load be cancelled, and notes this is the case;
    140      * once the background thread has completed its work its remaining state
    141      * will be cleared.  If another load request comes in during this time,
    142      * it will be held until the cancelled load is complete.
    143      *
    144      * @return Returns <tt>false</tt> if the task could not be cancelled,
    145      *         typically because it has already completed normally, or
    146      *         because {@link #startLoading()} hasn't been called; returns
    147      *         <tt>true</tt> otherwise.
    148      */
    149     public boolean cancelLoad() {
    150         if (DEBUG) Slog.v(TAG, "cancelLoad: mTask=" + mTask);
    151         if (mTask != null) {
    152             if (mCancellingTask != null) {
    153                 // There was a pending task already waiting for a previous
    154                 // one being canceled; just drop it.
    155                 if (DEBUG) Slog.v(TAG,
    156                         "cancelLoad: still waiting for cancelled task; dropping next");
    157                 if (mTask.waiting) {
    158                     mTask.waiting = false;
    159                     mHandler.removeCallbacks(mTask);
    160                 }
    161                 mTask = null;
    162                 return false;
    163             } else if (mTask.waiting) {
    164                 // There is a task, but it is waiting for the time it should
    165                 // execute.  We can just toss it.
    166                 if (DEBUG) Slog.v(TAG, "cancelLoad: task is waiting, dropping it");
    167                 mTask.waiting = false;
    168                 mHandler.removeCallbacks(mTask);
    169                 mTask = null;
    170                 return false;
    171             } else {
    172                 boolean cancelled = mTask.cancel(false);
    173                 if (DEBUG) Slog.v(TAG, "cancelLoad: cancelled=" + cancelled);
    174                 if (cancelled) {
    175                     mCancellingTask = mTask;
    176                 }
    177                 mTask = null;
    178                 return cancelled;
    179             }
    180         }
    181         return false;
    182     }
    183 
    184     /**
    185      * Called if the task was canceled before it was completed.  Gives the class a chance
    186      * to properly dispose of the result.
    187      */
    188     public void onCanceled(D data) {
    189     }
    190 
    191     void executePendingTask() {
    192         if (mCancellingTask == null && mTask != null) {
    193             if (mTask.waiting) {
    194                 mTask.waiting = false;
    195                 mHandler.removeCallbacks(mTask);
    196             }
    197             if (mUpdateThrottle > 0) {
    198                 long now = SystemClock.uptimeMillis();
    199                 if (now < (mLastLoadCompleteTime+mUpdateThrottle)) {
    200                     // Not yet time to do another load.
    201                     if (DEBUG) Slog.v(TAG, "Waiting until "
    202                             + (mLastLoadCompleteTime+mUpdateThrottle)
    203                             + " to execute: " + mTask);
    204                     mTask.waiting = true;
    205                     mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle);
    206                     return;
    207                 }
    208             }
    209             if (DEBUG) Slog.v(TAG, "Executing: " + mTask);
    210             mTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
    211         }
    212     }
    213 
    214     void dispatchOnCancelled(LoadTask task, D data) {
    215         onCanceled(data);
    216         if (mCancellingTask == task) {
    217             if (DEBUG) Slog.v(TAG, "Cancelled task is now canceled!");
    218             mLastLoadCompleteTime = SystemClock.uptimeMillis();
    219             mCancellingTask = null;
    220             executePendingTask();
    221         }
    222     }
    223 
    224     void dispatchOnLoadComplete(LoadTask task, D data) {
    225         if (mTask != task) {
    226             if (DEBUG) Slog.v(TAG, "Load complete of old task, trying to cancel");
    227             dispatchOnCancelled(task, data);
    228         } else {
    229             if (isAbandoned()) {
    230                 // This cursor has been abandoned; just cancel the new data.
    231                 onCanceled(data);
    232             } else {
    233                 mLastLoadCompleteTime = SystemClock.uptimeMillis();
    234                 mTask = null;
    235                 if (DEBUG) Slog.v(TAG, "Delivering result");
    236                 deliverResult(data);
    237             }
    238         }
    239     }
    240 
    241     /**
    242      */
    243     public abstract D loadInBackground();
    244 
    245     /**
    246      * Called on a worker thread to perform the actual load. Implementations should not deliver the
    247      * result directly, but should return them from this method, which will eventually end up
    248      * calling {@link #deliverResult} on the UI thread. If implementations need to process
    249      * the results on the UI thread they may override {@link #deliverResult} and do so
    250      * there.
    251      *
    252      * @return Implementations must return the result of their load operation.
    253      */
    254     protected D onLoadInBackground() {
    255         return loadInBackground();
    256     }
    257 
    258     /**
    259      * Locks the current thread until the loader completes the current load
    260      * operation. Returns immediately if there is no load operation running.
    261      * Should not be called from the UI thread: calling it from the UI
    262      * thread would cause a deadlock.
    263      * <p>
    264      * Use for testing only.  <b>Never</b> call this from a UI thread.
    265      *
    266      * @hide
    267      */
    268     public void waitForLoader() {
    269         LoadTask task = mTask;
    270         if (task != null) {
    271             try {
    272                 task.done.await();
    273             } catch (InterruptedException e) {
    274                 // Ignore
    275             }
    276         }
    277     }
    278 
    279     @Override
    280     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
    281         super.dump(prefix, fd, writer, args);
    282         if (mTask != null) {
    283             writer.print(prefix); writer.print("mTask="); writer.print(mTask);
    284                     writer.print(" waiting="); writer.println(mTask.waiting);
    285         }
    286         if (mCancellingTask != null) {
    287             writer.print(prefix); writer.print("mCancellingTask="); writer.print(mCancellingTask);
    288                     writer.print(" waiting="); writer.println(mCancellingTask.waiting);
    289         }
    290         if (mUpdateThrottle != 0) {
    291             writer.print(prefix); writer.print("mUpdateThrottle=");
    292                     TimeUtils.formatDuration(mUpdateThrottle, writer);
    293                     writer.print(" mLastLoadCompleteTime=");
    294                     TimeUtils.formatDuration(mLastLoadCompleteTime,
    295                             SystemClock.uptimeMillis(), writer);
    296                     writer.println();
    297         }
    298     }
    299 }
    300