AsyncTaskLoader.java revision 9567a66a5e6f49dd8495fb5f6e2efb9f32e84b35
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
17package android.content;
18
19import android.os.AsyncTask;
20import android.os.Handler;
21import android.os.SystemClock;
22import android.util.Slog;
23import android.util.TimeUtils;
24
25import java.io.FileDescriptor;
26import java.io.PrintWriter;
27import 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 */
51public 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