• 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.Log;
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
doInBackground(Void... params)66         protected D doInBackground(Void... params) {
67             if (DEBUG) Log.v(TAG, this + " >>> doInBackground");
68             try {
69                 D data = AsyncTaskLoader.this.onLoadInBackground();
70                 if (DEBUG) Log.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) Log.v(TAG, this + "  <<< doInBackground (was canceled)", ex);
83                 return null;
84             }
85         }
86 
87         /* Runs on the UI thread */
88         @Override
onPostExecute(D data)89         protected void onPostExecute(D data) {
90             if (DEBUG) Log.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
onCancelled(D data)100         protected void onCancelled(D data) {
101             if (DEBUG) Log.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
run()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. */
waitForLoader()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 
AsyncTaskLoader(Context context)136     public AsyncTaskLoader(Context context) {
137         this(context, AsyncTask.THREAD_POOL_EXECUTOR);
138     }
139 
140     /** {@hide} */
AsyncTaskLoader(Context context, Executor executor)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      */
setUpdateThrottle(long delayMS)153     public void setUpdateThrottle(long delayMS) {
154         mUpdateThrottle = delayMS;
155         if (delayMS != 0) {
156             mHandler = new Handler();
157         }
158     }
159 
160     @Override
onForceLoad()161     protected void onForceLoad() {
162         super.onForceLoad();
163         cancelLoad();
164         mTask = new LoadTask();
165         if (DEBUG) Log.v(TAG, "Preparing load: mTask=" + mTask);
166         executePendingTask();
167     }
168 
169     @Override
onCancelLoad()170     protected boolean onCancelLoad() {
171         if (DEBUG) Log.v(TAG, "onCancelLoad: mTask=" + mTask);
172         if (mTask != null) {
173             if (!mStarted) {
174                 mContentChanged = true;
175             }
176             if (mCancellingTask != null) {
177                 // There was a pending task already waiting for a previous
178                 // one being canceled; just drop it.
179                 if (DEBUG) Log.v(TAG,
180                         "cancelLoad: still waiting for cancelled task; dropping next");
181                 if (mTask.waiting) {
182                     mTask.waiting = false;
183                     mHandler.removeCallbacks(mTask);
184                 }
185                 mTask = null;
186                 return false;
187             } else if (mTask.waiting) {
188                 // There is a task, but it is waiting for the time it should
189                 // execute.  We can just toss it.
190                 if (DEBUG) Log.v(TAG, "cancelLoad: task is waiting, dropping it");
191                 mTask.waiting = false;
192                 mHandler.removeCallbacks(mTask);
193                 mTask = null;
194                 return false;
195             } else {
196                 boolean cancelled = mTask.cancel(false);
197                 if (DEBUG) Log.v(TAG, "cancelLoad: cancelled=" + cancelled);
198                 if (cancelled) {
199                     mCancellingTask = mTask;
200                     cancelLoadInBackground();
201                 }
202                 mTask = null;
203                 return cancelled;
204             }
205         }
206         return false;
207     }
208 
209     /**
210      * Called if the task was canceled before it was completed.  Gives the class a chance
211      * to clean up post-cancellation and to properly dispose of the result.
212      *
213      * @param data The value that was returned by {@link #loadInBackground}, or null
214      * if the task threw {@link OperationCanceledException}.
215      */
onCanceled(D data)216     public void onCanceled(D data) {
217     }
218 
executePendingTask()219     void executePendingTask() {
220         if (mCancellingTask == null && mTask != null) {
221             if (mTask.waiting) {
222                 mTask.waiting = false;
223                 mHandler.removeCallbacks(mTask);
224             }
225             if (mUpdateThrottle > 0) {
226                 long now = SystemClock.uptimeMillis();
227                 if (now < (mLastLoadCompleteTime+mUpdateThrottle)) {
228                     // Not yet time to do another load.
229                     if (DEBUG) Log.v(TAG, "Waiting until "
230                             + (mLastLoadCompleteTime+mUpdateThrottle)
231                             + " to execute: " + mTask);
232                     mTask.waiting = true;
233                     mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle);
234                     return;
235                 }
236             }
237             if (DEBUG) Log.v(TAG, "Executing: " + mTask);
238             mTask.executeOnExecutor(mExecutor, (Void[]) null);
239         }
240     }
241 
dispatchOnCancelled(LoadTask task, D data)242     void dispatchOnCancelled(LoadTask task, D data) {
243         onCanceled(data);
244         if (mCancellingTask == task) {
245             if (DEBUG) Log.v(TAG, "Cancelled task is now canceled!");
246             rollbackContentChanged();
247             mLastLoadCompleteTime = SystemClock.uptimeMillis();
248             mCancellingTask = null;
249             if (DEBUG) Log.v(TAG, "Delivering cancellation");
250             deliverCancellation();
251             executePendingTask();
252         }
253     }
254 
dispatchOnLoadComplete(LoadTask task, D data)255     void dispatchOnLoadComplete(LoadTask task, D data) {
256         if (mTask != task) {
257             if (DEBUG) Log.v(TAG, "Load complete of old task, trying to cancel");
258             dispatchOnCancelled(task, data);
259         } else {
260             if (isAbandoned()) {
261                 // This cursor has been abandoned; just cancel the new data.
262                 onCanceled(data);
263             } else {
264                 commitContentChanged();
265                 mLastLoadCompleteTime = SystemClock.uptimeMillis();
266                 mTask = null;
267                 if (DEBUG) Log.v(TAG, "Delivering result");
268                 deliverResult(data);
269             }
270         }
271     }
272 
273     /**
274      * Called on a worker thread to perform the actual load and to return
275      * the result of the load operation.
276      *
277      * Implementations should not deliver the result directly, but should return them
278      * from this method, which will eventually end up calling {@link #deliverResult} on
279      * the UI thread.  If implementations need to process the results on the UI thread
280      * they may override {@link #deliverResult} and do so there.
281      *
282      * To support cancellation, this method should periodically check the value of
283      * {@link #isLoadInBackgroundCanceled} and terminate when it returns true.
284      * Subclasses may also override {@link #cancelLoadInBackground} to interrupt the load
285      * directly instead of polling {@link #isLoadInBackgroundCanceled}.
286      *
287      * When the load is canceled, this method may either return normally or throw
288      * {@link OperationCanceledException}.  In either case, the {@link Loader} will
289      * call {@link #onCanceled} to perform post-cancellation cleanup and to dispose of the
290      * result object, if any.
291      *
292      * @return The result of the load operation.
293      *
294      * @throws OperationCanceledException if the load is canceled during execution.
295      *
296      * @see #isLoadInBackgroundCanceled
297      * @see #cancelLoadInBackground
298      * @see #onCanceled
299      */
loadInBackground()300     public abstract D loadInBackground();
301 
302     /**
303      * Calls {@link #loadInBackground()}.
304      *
305      * This method is reserved for use by the loader framework.
306      * Subclasses should override {@link #loadInBackground} instead of this method.
307      *
308      * @return The result of the load operation.
309      *
310      * @throws OperationCanceledException if the load is canceled during execution.
311      *
312      * @see #loadInBackground
313      */
onLoadInBackground()314     protected D onLoadInBackground() {
315         return loadInBackground();
316     }
317 
318     /**
319      * Called on the main thread to abort a load in progress.
320      *
321      * Override this method to abort the current invocation of {@link #loadInBackground}
322      * that is running in the background on a worker thread.
323      *
324      * This method should do nothing if {@link #loadInBackground} has not started
325      * running or if it has already finished.
326      *
327      * @see #loadInBackground
328      */
cancelLoadInBackground()329     public void cancelLoadInBackground() {
330     }
331 
332     /**
333      * Returns true if the current invocation of {@link #loadInBackground} is being canceled.
334      *
335      * @return True if the current invocation of {@link #loadInBackground} is being canceled.
336      *
337      * @see #loadInBackground
338      */
isLoadInBackgroundCanceled()339     public boolean isLoadInBackgroundCanceled() {
340         return mCancellingTask != null;
341     }
342 
343     /**
344      * Locks the current thread until the loader completes the current load
345      * operation. Returns immediately if there is no load operation running.
346      * Should not be called from the UI thread: calling it from the UI
347      * thread would cause a deadlock.
348      * <p>
349      * Use for testing only.  <b>Never</b> call this from a UI thread.
350      *
351      * @hide
352      */
waitForLoader()353     public void waitForLoader() {
354         LoadTask task = mTask;
355         if (task != null) {
356             task.waitForLoader();
357         }
358     }
359 
360     @Override
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)361     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
362         super.dump(prefix, fd, writer, args);
363         if (mTask != null) {
364             writer.print(prefix); writer.print("mTask="); writer.print(mTask);
365                     writer.print(" waiting="); writer.println(mTask.waiting);
366         }
367         if (mCancellingTask != null) {
368             writer.print(prefix); writer.print("mCancellingTask="); writer.print(mCancellingTask);
369                     writer.print(" waiting="); writer.println(mCancellingTask.waiting);
370         }
371         if (mUpdateThrottle != 0) {
372             writer.print(prefix); writer.print("mUpdateThrottle=");
373                     TimeUtils.formatDuration(mUpdateThrottle, writer);
374                     writer.print(" mLastLoadCompleteTime=");
375                     TimeUtils.formatDuration(mLastLoadCompleteTime,
376                             SystemClock.uptimeMillis(), writer);
377                     writer.println();
378         }
379     }
380 }
381