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