/ItemID/src/com/ideal/itemid/UserTask.java
Java | 480 lines | 168 code | 45 blank | 267 comment | 4 complexity | 9c00a54bdfea1fd6c3d6d2dded7ce4d5 MD5 | raw file
1/* 2 * Copyright (C) 2008 Google Inc. 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 com.ideal.itemid; 18 19import java.util.concurrent.BlockingQueue; 20import java.util.concurrent.Callable; 21import java.util.concurrent.CancellationException; 22import java.util.concurrent.ExecutionException; 23import java.util.concurrent.FutureTask; 24import java.util.concurrent.LinkedBlockingQueue; 25import java.util.concurrent.ThreadFactory; 26import java.util.concurrent.ThreadPoolExecutor; 27import java.util.concurrent.TimeUnit; 28import java.util.concurrent.TimeoutException; 29import java.util.concurrent.atomic.AtomicInteger; 30 31import android.os.Handler; 32import android.os.Message; 33import android.os.Process; 34 35/** 36 * <p> 37 * UserTask enables proper and easy use of the UI thread. This class allows to 38 * perform background operations and publish results on the UI thread without 39 * having to manipulate threads and/or handlers. 40 * </p> 41 * <p> 42 * A user task is defined by a computation that runs on a background thread and 43 * whose result is published on the UI thread. A user task is defined by 3 44 * generic types, called <code>Params</code>, <code>Progress</code> and 45 * <code>Result</code>, and 4 steps, called <code>begin</code>, 46 * <code>doInBackground</code>, <code>processProgress<code> and <code>end</code> 47 * . 48 * </p> 49 * <h2>Usage</h2> 50 * <p> 51 * UserTask must be subclassed to be used. The subclass will override at least 52 * one method ({@link #doInBackground(Object[])}), and most often will override 53 * a second one ({@link #onPostExecute(Object)}.) 54 * </p> 55 * <p> 56 * Here is an example of subclassing: 57 * </p> 58 * 59 * <pre> 60 * private class DownloadFilesTask extends UserTask<URL, Integer, Long> { 61 * public File doInBackground(URL... urls) { 62 * int count = urls.length; 63 * long totalSize = 0; 64 * for (int i = 0; i < count; i++) { 65 * totalSize += Downloader.downloadFile(urls[i]); 66 * publishProgress((int) ((i / (float) count) * 100)); 67 * } 68 * } 69 * 70 * public void onProgressUpdate(Integer... progress) { 71 * setProgressPercent(progress[0]); 72 * } 73 * 74 * public void onPostExecute(Long result) { 75 * showDialog("Downloaded " + result + " bytes"); 76 * } 77 * } 78 * </pre> 79 * <p> 80 * Once created, a task is executed very simply: 81 * </p> 82 * 83 * <pre> 84 * new DownloadFilesTask().execute(new URL[] { ... }); 85 * </pre> 86 * 87 * <h2>User task's generic types</h2> 88 * <p> 89 * The three types used by a user task are the following: 90 * </p> 91 * <ol> 92 * <li><code>Params</code>, the type of the parameters sent to the task upon 93 * execution.</li> 94 * <li><code>Progress</code>, the type of the progress units published during 95 * the background computation.</li> 96 * <li><code>Result</code>, the type of the result of the background 97 * computation.</li> 98 * </ol> 99 * <p> 100 * Not all types are always used by a user task. To mark a type as unused, 101 * simply use the type {@link Void}: 102 * </p> 103 * 104 * <pre> 105 * private class MyTask extends UserTask<Void, Void, Void) { ... } 106 * </pre> 107 * 108 * <h2>The 4 steps</h2> 109 * <p> 110 * When a user task is executed, the task goes through 4 steps: 111 * </p> 112 * <ol> 113 * <li>{@link #onPreExecute()}, invoked on the UI thread immediately after the 114 * task is executed. This step is normally used to setup the task, for instance 115 * by showing a progress bar in the user interface.</li> 116 * <li>{@link #doInBackground(Object[])}, invoked on the background thread 117 * immediately after {@link # onPreExecute ()} finishes executing. This step is 118 * used to perform background computation that can take a long time. The 119 * parameters of the user task are passed to this step. The result of the 120 * computation must be returned by this step and will be passed back to the last 121 * step. This step can also use {@link #publishProgress(Object[])} to publish 122 * one or more units of progress. These values are published on the UI thread, 123 * in the {@link #onProgressUpdate(Object[])} step.</li> 124 * <li>{@link # onProgressUpdate (Object[])}, invoked on the UI thread after a 125 * call to {@link #publishProgress(Object[])}. The timing of the execution is 126 * undefined. This method is used to display any form of progress in the user 127 * interface while the background computation is still executing. For instance, 128 * it can be used to animate a progress bar or show logs in a text field.</li> 129 * <li>{@link # onPostExecute (Object)}, invoked on the UI thread after the 130 * background computation finishes. The result of the background computation is 131 * passed to this step as a parameter.</li> 132 * </ol> 133 * <h2>Threading rules</h2> 134 * <p> 135 * There are a few threading rules that must be followed for this class to work 136 * properly: 137 * </p> 138 * <ul> 139 * <li>The task instance must be created on the UI thread.</li> 140 * <li>{@link #execute(Object[])} must be invoked on the UI thread.</li> 141 * <li>Do not call {@link # onPreExecute ()}, {@link # onPostExecute (Object)}, 142 * {@link #doInBackground(Object[])}, {@link # onProgressUpdate (Object[])} 143 * manually.</li> 144 * <li>The task can be executed only once (an exception will be thrown if a 145 * second execution is attempted.)</li> 146 * </ul> 147 */ 148public abstract class UserTask<Params, Progress, Result> { 149 private static final String LOG_TAG = "UserTask"; 150 151 // TODO: get a better understanding of these values and how to tweak them 152 // for optimal performance... 153 private static final int CORE_POOL_SIZE = 6; 154 155 private static final int MAXIMUM_POOL_SIZE = 10; 156 157 private static final int KEEP_ALIVE = 10; 158 159 private static final BlockingQueue<Runnable> sWorkQueue = new LinkedBlockingQueue<Runnable>( 160 MAXIMUM_POOL_SIZE); 161 162 private static final ThreadFactory sThreadFactory = new ThreadFactory() { 163 private final AtomicInteger mCount = new AtomicInteger(1); 164 165 public Thread newThread(Runnable r) { 166 return new Thread(r, "UserTask #" + mCount.getAndIncrement()); 167 } 168 }; 169 170 private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, 171 MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory); 172 173 private static final int MESSAGE_POST_RESULT = 0x1; 174 175 private static final int MESSAGE_POST_PROGRESS = 0x2; 176 177 private static final int MESSAGE_POST_CANCEL = 0x3; 178 179 private static final InternalHandler sHandler = new InternalHandler(); 180 181 private final WorkerRunnable<Params, Result> mWorker; 182 183 private final FutureTask<Result> mFuture; 184 185 private volatile Status mStatus = Status.PENDING; 186 187 /** 188 * Indicates the current status of the task. Each status will be set only 189 * once during the lifetime of a task. 190 */ 191 public enum Status { 192 /** 193 * Indicates that the task has not been executed yet. 194 */ 195 PENDING, 196 /** 197 * Indicates that the task is running. 198 */ 199 RUNNING, 200 /** 201 * Indicates that {@link UserTask#onPostExecute(Object)} has finished. 202 */ 203 FINISHED, 204 } 205 206 /** 207 * Creates a new user task. This constructor must be invoked on the UI 208 * thread. 209 */ 210 public UserTask() { 211 mWorker = new WorkerRunnable<Params, Result>() { 212 public Result call() throws Exception { 213 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 214 return doInBackground(mParams); 215 } 216 }; 217 218 mFuture = new FutureTask<Result>(mWorker) { 219 @Override 220 protected void done() { 221 Message message; 222 Result result = null; 223 224 try { 225 result = get(); 226 } catch (InterruptedException e) { 227 android.util.Log.w(LOG_TAG, e); 228 } catch (ExecutionException e) { 229 throw new RuntimeException("An error occured while executing doInBackground()", 230 e.getCause()); 231 } catch (CancellationException e) { 232 message = sHandler.obtainMessage(MESSAGE_POST_CANCEL, 233 new UserTaskResult<Result>(UserTask.this, (Result[]) null)); 234 message.sendToTarget(); 235 return; 236 } catch (Throwable t) { 237 throw new RuntimeException("An error occured while executing " 238 + "doInBackground()", t); 239 } 240 241 message = sHandler.obtainMessage(MESSAGE_POST_RESULT, new UserTaskResult<Result>( 242 UserTask.this, result)); 243 message.sendToTarget(); 244 } 245 }; 246 } 247 248 /** 249 * Returns the current status of this task. 250 * 251 * @return The current status. 252 */ 253 public final Status getStatus() { 254 return mStatus; 255 } 256 257 /** 258 * Override this method to perform a computation on a background thread. The 259 * specified parameters are the parameters passed to 260 * {@link #execute(Object[])} by the caller of this task. This method can 261 * call {@link #publishProgress(Object[])} to publish updates on the UI 262 * thread. 263 * 264 * @param params The parameters of the task. 265 * @return A result, defined by the subclass of this task. 266 * @see #onPreExecute() 267 * @see #onPostExecute(Object) 268 * @see #publishProgress(Object[]) 269 */ 270 public abstract Result doInBackground(Params... params); 271 272 /** 273 * Runs on the UI thread before {@link #doInBackground(Object[])}. 274 * 275 * @see #onPostExecute(Object) 276 * @see #doInBackground(Object[]) 277 */ 278 public void onPreExecute() { 279 } 280 281 /** 282 * Runs on the UI thread after {@link #doInBackground(Object[])}. The 283 * specified result is the value returned by 284 * {@link #doInBackground(Object[])} or null if the task was cancelled or an 285 * exception occured. 286 * 287 * @param result The result of the operation computed by 288 * {@link #doInBackground(Object[])}. 289 * @see #onPreExecute() 290 * @see #doInBackground(Object[]) 291 */ 292 @SuppressWarnings( { 293 "UnusedDeclaration" 294 }) 295 public void onPostExecute(Result result) { 296 } 297 298 /** 299 * Runs on the UI thread after {@link #publishProgress(Object[])} is 300 * invoked. The specified values are the values passed to 301 * {@link #publishProgress(Object[])}. 302 * 303 * @param values The values indicating progress. 304 * @see #publishProgress(Object[]) 305 * @see #doInBackground(Object[]) 306 */ 307 @SuppressWarnings( { 308 "UnusedDeclaration" 309 }) 310 public void onProgressUpdate(Progress... values) { 311 } 312 313 /** 314 * Runs on the UI thread after {@link #cancel(boolean)} is invoked. 315 * 316 * @see #cancel(boolean) 317 * @see #isCancelled() 318 */ 319 public void onCancelled() { 320 } 321 322 /** 323 * Returns <tt>true</tt> if this task was cancelled before it completed 324 * normally. 325 * 326 * @return <tt>true</tt> if task was cancelled before it completed 327 * @see #cancel(boolean) 328 */ 329 public final boolean isCancelled() { 330 return mFuture.isCancelled(); 331 } 332 333 /** 334 * Attempts to cancel execution of this task. This attempt will fail if the 335 * task has already completed, already been cancelled, or could not be 336 * cancelled for some other reason. If successful, and this task has not 337 * started when <tt>cancel</tt> is called, this task should never run. If 338 * the task has already started, then the <tt>mayInterruptIfRunning</tt> 339 * parameter determines whether the thread executing this task should be 340 * interrupted in an attempt to stop the task. 341 * 342 * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this 343 * task should be interrupted; otherwise, in-progress tasks are 344 * allowed to complete. 345 * @return <tt>false</tt> if the task could not be cancelled, typically 346 * because it has already completed normally; <tt>true</tt> 347 * otherwise 348 * @see #isCancelled() 349 * @see #onCancelled() 350 */ 351 public final boolean cancel(boolean mayInterruptIfRunning) { 352 return mFuture.cancel(mayInterruptIfRunning); 353 } 354 355 /** 356 * Waits if necessary for the computation to complete, and then retrieves 357 * its result. 358 * 359 * @return The computed result. 360 * @throws CancellationException If the computation was cancelled. 361 * @throws ExecutionException If the computation threw an exception. 362 * @throws InterruptedException If the current thread was interrupted while 363 * waiting. 364 */ 365 public final Result get() throws InterruptedException, ExecutionException { 366 return mFuture.get(); 367 } 368 369 /** 370 * Waits if necessary for at most the given time for the computation to 371 * complete, and then retrieves its result. 372 * 373 * @param timeout Time to wait before cancelling the operation. 374 * @param unit The time unit for the timeout. 375 * @return The computed result. 376 * @throws CancellationException If the computation was cancelled. 377 * @throws ExecutionException If the computation threw an exception. 378 * @throws InterruptedException If the current thread was interrupted while 379 * waiting. 380 * @throws TimeoutException If the wait timed out. 381 */ 382 public final Result get(long timeout, TimeUnit unit) throws InterruptedException, 383 ExecutionException, TimeoutException { 384 return mFuture.get(timeout, unit); 385 } 386 387 /** 388 * Executes the task with the specified parameters. The task returns itself 389 * (this) so that the caller can keep a reference to it. This method must be 390 * invoked on the UI thread. 391 * 392 * @param params The parameters of the task. 393 * @return This instance of UserTask. 394 * @throws IllegalStateException If {@link #getStatus()} returns either 395 * {@link UserTask.Status#RUNNING} or 396 * {@link UserTask.Status#FINISHED}. 397 */ 398 public final UserTask<Params, Progress, Result> execute(Params... params) { 399 if (mStatus != Status.PENDING) { 400 switch (mStatus) { 401 case RUNNING: 402 throw new IllegalStateException("Cannot execute task:" 403 + " the task is already running."); 404 case FINISHED: 405 throw new IllegalStateException("Cannot execute task:" 406 + " the task has already been executed " 407 + "(a task can be executed only once)"); 408 } 409 } 410 411 mStatus = Status.RUNNING; 412 413 onPreExecute(); 414 415 mWorker.mParams = params; 416 sExecutor.execute(mFuture); 417 418 return this; 419 } 420 421 /** 422 * This method can be invoked from {@link #doInBackground(Object[])} to 423 * publish updates on the UI thread while the background computation is 424 * still running. Each call to this method will trigger the execution of 425 * {@link #onProgressUpdate(Object[])} on the UI thread. 426 * 427 * @param values The progress values to update the UI with. 428 * @see # onProgressUpdate (Object[]) 429 * @see #doInBackground(Object[]) 430 */ 431 protected final void publishProgress(Progress... values) { 432 sHandler.obtainMessage(MESSAGE_POST_PROGRESS, new UserTaskResult<Progress>(this, values)) 433 .sendToTarget(); 434 } 435 436 private void finish(Result result) { 437 onPostExecute(result); 438 mStatus = Status.FINISHED; 439 } 440 441 private static class InternalHandler extends Handler { 442 @SuppressWarnings( { 443 "unchecked", "RawUseOfParameterizedType" 444 }) 445 @Override 446 public void handleMessage(Message msg) { 447 UserTaskResult result = (UserTaskResult) msg.obj; 448 switch (msg.what) { 449 case MESSAGE_POST_RESULT: 450 // There is only one result 451 result.mTask.finish(result.mData[0]); 452 break; 453 case MESSAGE_POST_PROGRESS: 454 result.mTask.onProgressUpdate(result.mData); 455 break; 456 case MESSAGE_POST_CANCEL: 457 result.mTask.onCancelled(); 458 break; 459 } 460 } 461 } 462 463 private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> { 464 Params[] mParams; 465 } 466 467 @SuppressWarnings( { 468 "RawUseOfParameterizedType" 469 }) 470 private static class UserTaskResult<Data> { 471 final UserTask mTask; 472 473 final Data[] mData; 474 475 UserTaskResult(UserTask task, Data... data) { 476 mTask = task; 477 mData = data; 478 } 479 } 480}