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