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