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