/utils/src/com/google/marvin/utils/UserTask.java

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