• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
doInBackground(Void... params)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)", ex);
82                 return null;
83             }
84         }
85 
86         /* Runs on the UI thread */
87         @Override
onPostExecute(D data)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
onCancelled(D data)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
run()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. */
waitForLoader()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 
AsyncTaskLoader(Context context)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      */
setUpdateThrottle(long delayMS)144     public void setUpdateThrottle(long delayMS) {
145         mUpdateThrottle = delayMS;
146         if (delayMS != 0) {
147             mHandler = new Handler();
148         }
149     }
150 
151     @Override
onForceLoad()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
onCancelLoad()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      */
onCanceled(D data)204     public void onCanceled(D data) {
205     }
206 
executePendingTask()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 
dispatchOnCancelled(LoadTask task, D data)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             rollbackContentChanged();
235             mLastLoadCompleteTime = SystemClock.uptimeMillis();
236             mCancellingTask = null;
237             if (DEBUG) Slog.v(TAG, "Delivering cancellation");
238             deliverCancellation();
239             executePendingTask();
240         }
241     }
242 
dispatchOnLoadComplete(LoadTask task, D data)243     void dispatchOnLoadComplete(LoadTask task, D data) {
244         if (mTask != task) {
245             if (DEBUG) Slog.v(TAG, "Load complete of old task, trying to cancel");
246             dispatchOnCancelled(task, data);
247         } else {
248             if (isAbandoned()) {
249                 // This cursor has been abandoned; just cancel the new data.
250                 onCanceled(data);
251             } else {
252                 commitContentChanged();
253                 mLastLoadCompleteTime = SystemClock.uptimeMillis();
254                 mTask = null;
255                 if (DEBUG) Slog.v(TAG, "Delivering result");
256                 deliverResult(data);
257             }
258         }
259     }
260 
261     /**
262      * Called on a worker thread to perform the actual load and to return
263      * the result of the load operation.
264      *
265      * Implementations should not deliver the result directly, but should return them
266      * from this method, which will eventually end up calling {@link #deliverResult} on
267      * the UI thread.  If implementations need to process the results on the UI thread
268      * they may override {@link #deliverResult} and do so there.
269      *
270      * To support cancellation, this method should periodically check the value of
271      * {@link #isLoadInBackgroundCanceled} and terminate when it returns true.
272      * Subclasses may also override {@link #cancelLoadInBackground} to interrupt the load
273      * directly instead of polling {@link #isLoadInBackgroundCanceled}.
274      *
275      * When the load is canceled, this method may either return normally or throw
276      * {@link OperationCanceledException}.  In either case, the {@link Loader} will
277      * call {@link #onCanceled} to perform post-cancellation cleanup and to dispose of the
278      * result object, if any.
279      *
280      * @return The result of the load operation.
281      *
282      * @throws OperationCanceledException if the load is canceled during execution.
283      *
284      * @see #isLoadInBackgroundCanceled
285      * @see #cancelLoadInBackground
286      * @see #onCanceled
287      */
loadInBackground()288     public abstract D loadInBackground();
289 
290     /**
291      * Calls {@link #loadInBackground()}.
292      *
293      * This method is reserved for use by the loader framework.
294      * Subclasses should override {@link #loadInBackground} instead of this method.
295      *
296      * @return The result of the load operation.
297      *
298      * @throws OperationCanceledException if the load is canceled during execution.
299      *
300      * @see #loadInBackground
301      */
onLoadInBackground()302     protected D onLoadInBackground() {
303         return loadInBackground();
304     }
305 
306     /**
307      * Called on the main thread to abort a load in progress.
308      *
309      * Override this method to abort the current invocation of {@link #loadInBackground}
310      * that is running in the background on a worker thread.
311      *
312      * This method should do nothing if {@link #loadInBackground} has not started
313      * running or if it has already finished.
314      *
315      * @see #loadInBackground
316      */
cancelLoadInBackground()317     public void cancelLoadInBackground() {
318     }
319 
320     /**
321      * Returns true if the current invocation of {@link #loadInBackground} is being canceled.
322      *
323      * @return True if the current invocation of {@link #loadInBackground} is being canceled.
324      *
325      * @see #loadInBackground
326      */
isLoadInBackgroundCanceled()327     public boolean isLoadInBackgroundCanceled() {
328         return mCancellingTask != null;
329     }
330 
331     /**
332      * Locks the current thread until the loader completes the current load
333      * operation. Returns immediately if there is no load operation running.
334      * Should not be called from the UI thread: calling it from the UI
335      * thread would cause a deadlock.
336      * <p>
337      * Use for testing only.  <b>Never</b> call this from a UI thread.
338      *
339      * @hide
340      */
waitForLoader()341     public void waitForLoader() {
342         LoadTask task = mTask;
343         if (task != null) {
344             task.waitForLoader();
345         }
346     }
347 
348     @Override
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)349     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
350         super.dump(prefix, fd, writer, args);
351         if (mTask != null) {
352             writer.print(prefix); writer.print("mTask="); writer.print(mTask);
353                     writer.print(" waiting="); writer.println(mTask.waiting);
354         }
355         if (mCancellingTask != null) {
356             writer.print(prefix); writer.print("mCancellingTask="); writer.print(mCancellingTask);
357                     writer.print(" waiting="); writer.println(mCancellingTask.waiting);
358         }
359         if (mUpdateThrottle != 0) {
360             writer.print(prefix); writer.print("mUpdateThrottle=");
361                     TimeUtils.formatDuration(mUpdateThrottle, writer);
362                     writer.print(" mLastLoadCompleteTime=");
363                     TimeUtils.formatDuration(mLastLoadCompleteTime,
364                             SystemClock.uptimeMillis(), writer);
365                     writer.println();
366         }
367     }
368 }
369