1/* 2 * Copyright 2018 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 androidx.loader.content; 18 19import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21import android.os.Binder; 22import android.os.Handler; 23import android.os.Looper; 24import android.os.Message; 25import android.os.Process; 26 27import androidx.annotation.RestrictTo; 28 29import java.util.concurrent.BlockingQueue; 30import java.util.concurrent.Callable; 31import java.util.concurrent.CancellationException; 32import java.util.concurrent.ExecutionException; 33import java.util.concurrent.Executor; 34import java.util.concurrent.FutureTask; 35import java.util.concurrent.LinkedBlockingQueue; 36import java.util.concurrent.ThreadFactory; 37import java.util.concurrent.ThreadPoolExecutor; 38import java.util.concurrent.TimeUnit; 39import java.util.concurrent.TimeoutException; 40import java.util.concurrent.atomic.AtomicBoolean; 41import java.util.concurrent.atomic.AtomicInteger; 42 43/** 44 * Copy of the required parts of {@link android.os.AsyncTask} from Android 3.0 that is 45 * needed to support AsyncTaskLoader. We use this rather than the one from the platform 46 * because we rely on some subtle behavior of AsyncTask that is not reliable on 47 * older platforms. 48 * 49 * <p>Note that for now this is not publicly available because it is not a 50 * complete implementation, only sufficient for the needs of 51 * {@link android.content.AsyncTaskLoader}. 52 */ 53abstract class ModernAsyncTask<Params, Progress, Result> { 54 private static final String LOG_TAG = "AsyncTask"; 55 56 private static final int CORE_POOL_SIZE = 5; 57 private static final int MAXIMUM_POOL_SIZE = 128; 58 private static final int KEEP_ALIVE = 1; 59 60 private static final ThreadFactory sThreadFactory = new ThreadFactory() { 61 private final AtomicInteger mCount = new AtomicInteger(1); 62 63 @Override 64 public Thread newThread(Runnable r) { 65 return new Thread(r, "ModernAsyncTask #" + mCount.getAndIncrement()); 66 } 67 }; 68 69 private static final BlockingQueue<Runnable> sPoolWorkQueue = 70 new LinkedBlockingQueue<Runnable>(10); 71 72 /** 73 * An {@link Executor} that can be used to execute tasks in parallel. 74 */ 75 public static final Executor THREAD_POOL_EXECUTOR = 76 new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, 77 TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); 78 79 private static final int MESSAGE_POST_RESULT = 0x1; 80 private static final int MESSAGE_POST_PROGRESS = 0x2; 81 82 private static InternalHandler sHandler; 83 84 private static volatile Executor sDefaultExecutor = THREAD_POOL_EXECUTOR; 85 private final WorkerRunnable<Params, Result> mWorker; 86 private final FutureTask<Result> mFuture; 87 88 private volatile Status mStatus = Status.PENDING; 89 90 private final AtomicBoolean mCancelled = new AtomicBoolean(); 91 private final AtomicBoolean mTaskInvoked = new AtomicBoolean(); 92 93 /** 94 * Indicates the current status of the task. Each status will be set only once 95 * during the lifetime of a task. 96 */ 97 public enum Status { 98 /** 99 * Indicates that the task has not been executed yet. 100 */ 101 PENDING, 102 /** 103 * Indicates that the task is running. 104 */ 105 RUNNING, 106 /** 107 * Indicates that {@link android.os.AsyncTask#onPostExecute(Object)} has finished. 108 */ 109 FINISHED, 110 } 111 112 private static Handler getHandler() { 113 synchronized (ModernAsyncTask.class) { 114 if (sHandler == null) { 115 sHandler = new InternalHandler(); 116 } 117 return sHandler; 118 } 119 } 120 121 /** @hide */ 122 @RestrictTo(LIBRARY_GROUP) 123 public static void setDefaultExecutor(Executor exec) { 124 sDefaultExecutor = exec; 125 } 126 127 /** 128 * Creates a new asynchronous task. This constructor must be invoked on the UI thread. 129 */ 130 ModernAsyncTask() { 131 mWorker = new WorkerRunnable<Params, Result>() { 132 @Override 133 public Result call() throws Exception { 134 mTaskInvoked.set(true); 135 Result result = null; 136 try { 137 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 138 //noinspection unchecked 139 result = doInBackground(mParams); 140 Binder.flushPendingCommands(); 141 } catch (Throwable tr) { 142 mCancelled.set(true); 143 throw tr; 144 } finally { 145 postResult(result); 146 } 147 return result; 148 } 149 }; 150 151 mFuture = new FutureTask<Result>(mWorker) { 152 @Override 153 protected void done() { 154 try { 155 final Result result = get(); 156 157 postResultIfNotInvoked(result); 158 } catch (InterruptedException e) { 159 android.util.Log.w(LOG_TAG, e); 160 } catch (ExecutionException e) { 161 throw new RuntimeException( 162 "An error occurred while executing doInBackground()", e.getCause()); 163 } catch (CancellationException e) { 164 postResultIfNotInvoked(null); 165 } catch (Throwable t) { 166 throw new RuntimeException( 167 "An error occurred while executing doInBackground()", t); 168 } 169 } 170 }; 171 } 172 173 void postResultIfNotInvoked(Result result) { 174 final boolean wasTaskInvoked = mTaskInvoked.get(); 175 if (!wasTaskInvoked) { 176 postResult(result); 177 } 178 } 179 180 Result postResult(Result result) { 181 Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, 182 new AsyncTaskResult<>(this, result)); 183 message.sendToTarget(); 184 return result; 185 } 186 187 /** 188 * Returns the current status of this task. 189 * 190 * @return The current status. 191 */ 192 public final Status getStatus() { 193 return mStatus; 194 } 195 196 /** 197 * Override this method to perform a computation on a background thread. The 198 * specified parameters are the parameters passed to {@link #execute} 199 * by the caller of this task. 200 * 201 * This method can call {@link #publishProgress} to publish updates 202 * on the UI thread. 203 * 204 * @param params The parameters of the task. 205 * 206 * @return A result, defined by the subclass of this task. 207 * 208 * @see #onPreExecute() 209 * @see #onPostExecute 210 * @see #publishProgress 211 */ 212 protected abstract Result doInBackground(Params... params); 213 214 /** 215 * Runs on the UI thread before {@link #doInBackground}. 216 * 217 * @see #onPostExecute 218 * @see #doInBackground 219 */ 220 protected void onPreExecute() { 221 } 222 223 /** 224 * <p>Runs on the UI thread after {@link #doInBackground}. The 225 * specified result is the value returned by {@link #doInBackground}.</p> 226 * 227 * <p>This method won't be invoked if the task was cancelled.</p> 228 * 229 * @param result The result of the operation computed by {@link #doInBackground}. 230 * 231 * @see #onPreExecute 232 * @see #doInBackground 233 * @see #onCancelled(Object) 234 */ 235 @SuppressWarnings({"UnusedDeclaration"}) 236 protected void onPostExecute(Result result) { 237 } 238 239 /** 240 * Runs on the UI thread after {@link #publishProgress} is invoked. 241 * The specified values are the values passed to {@link #publishProgress}. 242 * 243 * @param values The values indicating progress. 244 * 245 * @see #publishProgress 246 * @see #doInBackground 247 */ 248 @SuppressWarnings({"UnusedDeclaration"}) 249 protected void onProgressUpdate(Progress... values) { 250 } 251 252 /** 253 * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and 254 * {@link #doInBackground(Object[])} has finished.</p> 255 * 256 * <p>The default implementation simply invokes {@link #onCancelled()} and 257 * ignores the result. If you write your own implementation, do not call 258 * <code>super.onCancelled(result)</code>.</p> 259 * 260 * @param result The result, if any, computed in 261 * {@link #doInBackground(Object[])}, can be null 262 * 263 * @see #cancel(boolean) 264 * @see #isCancelled() 265 */ 266 @SuppressWarnings({"UnusedParameters"}) 267 protected void onCancelled(Result result) { 268 onCancelled(); 269 } 270 271 /** 272 * <p>Applications should preferably override {@link #onCancelled(Object)}. 273 * This method is invoked by the default implementation of 274 * {@link #onCancelled(Object)}.</p> 275 * 276 * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and 277 * {@link #doInBackground(Object[])} has finished.</p> 278 * 279 * @see #onCancelled(Object) 280 * @see #cancel(boolean) 281 * @see #isCancelled() 282 */ 283 protected void onCancelled() { 284 } 285 286 /** 287 * Returns <tt>true</tt> if this task was cancelled before it completed 288 * normally. If you are calling {@link #cancel(boolean)} on the task, 289 * the value returned by this method should be checked periodically from 290 * {@link #doInBackground(Object[])} to end the task as soon as possible. 291 * 292 * @return <tt>true</tt> if task was cancelled before it completed 293 * 294 * @see #cancel(boolean) 295 */ 296 public final boolean isCancelled() { 297 return mCancelled.get(); 298 } 299 300 /** 301 * <p>Attempts to cancel execution of this task. This attempt will 302 * fail if the task has already completed, already been cancelled, 303 * or could not be cancelled for some other reason. If successful, 304 * and this task has not started when <tt>cancel</tt> is called, 305 * this task should never run. If the task has already started, 306 * then the <tt>mayInterruptIfRunning</tt> parameter determines 307 * whether the thread executing this task should be interrupted in 308 * an attempt to stop the task.</p> 309 * 310 * <p>Calling this method will result in {@link #onCancelled(Object)} being 311 * invoked on the UI thread after {@link #doInBackground(Object[])} 312 * returns. Calling this method guarantees that {@link #onPostExecute(Object)} 313 * is never invoked. After invoking this method, you should check the 314 * value returned by {@link #isCancelled()} periodically from 315 * {@link #doInBackground(Object[])} to finish the task as early as 316 * possible.</p> 317 * 318 * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this 319 * task should be interrupted; otherwise, in-progress tasks are allowed 320 * to complete. 321 * 322 * @return <tt>false</tt> if the task could not be cancelled, 323 * typically because it has already completed normally; 324 * <tt>true</tt> otherwise 325 * 326 * @see #isCancelled() 327 * @see #onCancelled(Object) 328 */ 329 public final boolean cancel(boolean mayInterruptIfRunning) { 330 mCancelled.set(true); 331 return mFuture.cancel(mayInterruptIfRunning); 332 } 333 334 /** 335 * Waits if necessary for the computation to complete, and then 336 * retrieves its result. 337 * 338 * @return The computed result. 339 * 340 * @throws CancellationException If the computation was cancelled. 341 * @throws ExecutionException If the computation threw an exception. 342 * @throws InterruptedException If the current thread was interrupted 343 * while waiting. 344 */ 345 public final Result get() throws InterruptedException, ExecutionException { 346 return mFuture.get(); 347 } 348 349 /** 350 * Waits if necessary for at most the given time for the computation 351 * to complete, and then retrieves its result. 352 * 353 * @param timeout Time to wait before cancelling the operation. 354 * @param unit The time unit for the timeout. 355 * 356 * @return The computed result. 357 * 358 * @throws CancellationException If the computation was cancelled. 359 * @throws ExecutionException If the computation threw an exception. 360 * @throws InterruptedException If the current thread was interrupted 361 * while waiting. 362 * @throws TimeoutException If the wait timed out. 363 */ 364 public final Result get(long timeout, TimeUnit unit) throws InterruptedException, 365 ExecutionException, TimeoutException { 366 return mFuture.get(timeout, unit); 367 } 368 369 /** 370 * Executes the task with the specified parameters. The task returns 371 * itself (this) so that the caller can keep a reference to it. 372 * 373 * <p>Note: this function schedules the task on a queue for a single background 374 * thread or pool of threads depending on the platform version. When first 375 * introduced, AsyncTasks were executed serially on a single background thread. 376 * Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed 377 * to a pool of threads allowing multiple tasks to operate in parallel. After 378 * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, it is planned to change this 379 * back to a single thread to avoid common application errors caused 380 * by parallel execution. If you truly want parallel execution, you can use 381 * the {@link #executeOnExecutor} version of this method 382 * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings on 383 * its use. 384 * 385 * <p>This method must be invoked on the UI thread. 386 * 387 * @param params The parameters of the task. 388 * 389 * @return This instance of AsyncTask. 390 * 391 * @throws IllegalStateException If {@link #getStatus()} returns either 392 * {@link android.os.AsyncTask.Status#RUNNING} or 393 * {@link android.os.AsyncTask.Status#FINISHED}. 394 */ 395 public final ModernAsyncTask<Params, Progress, Result> execute(Params... params) { 396 return executeOnExecutor(sDefaultExecutor, params); 397 } 398 399 /** 400 * Executes the task with the specified parameters. The task returns 401 * itself (this) so that the caller can keep a reference to it. 402 * 403 * <p>This method is typically used with {@link #THREAD_POOL_EXECUTOR} to 404 * allow multiple tasks to run in parallel on a pool of threads managed by 405 * AsyncTask, however you can also use your own {@link Executor} for custom 406 * behavior. 407 * 408 * <p><em>Warning:</em> Allowing multiple tasks to run in parallel from 409 * a thread pool is generally <em>not</em> what one wants, because the order 410 * of their operation is not defined. For example, if these tasks are used 411 * to modify any state in common (such as writing a file due to a button click), 412 * there are no guarantees on the order of the modifications. 413 * Without careful work it is possible in rare cases for the newer version 414 * of the data to be over-written by an older one, leading to obscure data 415 * loss and stability issues. 416 * 417 * <p>This method must be invoked on the UI thread. 418 * 419 * @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a 420 * convenient process-wide thread pool for tasks that are loosely coupled. 421 * @param params The parameters of the task. 422 * 423 * @return This instance of AsyncTask. 424 * 425 * @throws IllegalStateException If {@link #getStatus()} returns either 426 * {@link android.os.AsyncTask.Status#RUNNING} 427 * or {@link android.os.AsyncTask.Status#FINISHED}. 428 */ 429 public final ModernAsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, 430 Params... params) { 431 if (mStatus != Status.PENDING) { 432 switch (mStatus) { 433 case RUNNING: 434 throw new IllegalStateException("Cannot execute task:" 435 + " the task is already running."); 436 case FINISHED: 437 throw new IllegalStateException("Cannot execute task:" 438 + " the task has already been executed " 439 + "(a task can be executed only once)"); 440 default: 441 throw new IllegalStateException("We should never reach this state"); 442 } 443 } 444 445 mStatus = Status.RUNNING; 446 447 onPreExecute(); 448 449 mWorker.mParams = params; 450 exec.execute(mFuture); 451 452 return this; 453 } 454 455 /** 456 * Convenience version of {@link #execute(Object...)} for use with 457 * a simple Runnable object. 458 */ 459 public static void execute(Runnable runnable) { 460 sDefaultExecutor.execute(runnable); 461 } 462 463 /** 464 * This method can be invoked from {@link #doInBackground} to 465 * publish updates on the UI thread while the background computation is 466 * still running. Each call to this method will trigger the execution of 467 * {@link #onProgressUpdate} on the UI thread. 468 * 469 * {@link #onProgressUpdate} will note be called if the task has been 470 * canceled. 471 * 472 * @param values The progress values to update the UI with. 473 * 474 * @see #onProgressUpdate 475 * @see #doInBackground 476 */ 477 protected final void publishProgress(Progress... values) { 478 if (!isCancelled()) { 479 getHandler().obtainMessage(MESSAGE_POST_PROGRESS, 480 new AsyncTaskResult<Progress>(this, values)).sendToTarget(); 481 } 482 } 483 484 void finish(Result result) { 485 if (isCancelled()) { 486 onCancelled(result); 487 } else { 488 onPostExecute(result); 489 } 490 mStatus = Status.FINISHED; 491 } 492 493 private static class InternalHandler extends Handler { 494 InternalHandler() { 495 super(Looper.getMainLooper()); 496 } 497 498 @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) 499 @Override 500 public void handleMessage(Message msg) { 501 AsyncTaskResult result = (AsyncTaskResult) msg.obj; 502 switch (msg.what) { 503 case MESSAGE_POST_RESULT: 504 // There is only one result 505 result.mTask.finish(result.mData[0]); 506 break; 507 case MESSAGE_POST_PROGRESS: 508 result.mTask.onProgressUpdate(result.mData); 509 break; 510 } 511 } 512 } 513 514 private abstract static class WorkerRunnable<Params, Result> implements Callable<Result> { 515 Params[] mParams; 516 517 WorkerRunnable() { 518 } 519 } 520 521 @SuppressWarnings({"RawUseOfParameterizedType"}) 522 private static class AsyncTaskResult<Data> { 523 final ModernAsyncTask mTask; 524 final Data[] mData; 525 526 AsyncTaskResult(ModernAsyncTask task, Data... data) { 527 mTask = task; 528 mData = data; 529 } 530 } 531} 532