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