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.SystemClock; 22 import android.util.Slog; 23 import android.util.TimeUtils; 24 25 import java.io.FileDescriptor; 26 import java.io.PrintWriter; 27 import 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 */ 51 public 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