| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.base.task; |
| |
| import android.os.Binder; |
| import android.os.Process; |
| import android.support.annotation.MainThread; |
| import android.support.annotation.WorkerThread; |
| |
| import org.chromium.base.ThreadUtils; |
| import org.chromium.base.TraceEvent; |
| import org.chromium.base.annotations.DoNotInline; |
| |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.CancellationException; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.FutureTask; |
| import java.util.concurrent.RejectedExecutionHandler; |
| import java.util.concurrent.ThreadPoolExecutor; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| |
| /** |
| * A Chromium version of android.os.AsyncTask. |
| * |
| * The API is quite close to Android's Oreo version, but with a number of things removed. |
| * @param <Result> Return type of the background task. |
| */ |
| public abstract class AsyncTask<Result> { |
| private static final String TAG = "AsyncTask"; |
| |
| /** |
| * An {@link Executor} that can be used to execute tasks in parallel. |
| */ |
| public static final Executor THREAD_POOL_EXECUTOR = new ChromeThreadPoolExecutor(); |
| |
| /** |
| * An {@link Executor} that executes tasks one at a time in serial |
| * order. This serialization is global to a particular process. |
| */ |
| public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); |
| |
| private static final StealRunnableHandler STEAL_RUNNABLE_HANDLER = new StealRunnableHandler(); |
| |
| private final Callable<Result> mWorker; |
| private final FutureTask<Result> mFuture; |
| |
| private volatile Status mStatus = Status.PENDING; |
| |
| private final AtomicBoolean mCancelled = new AtomicBoolean(); |
| private final AtomicBoolean mTaskInvoked = new AtomicBoolean(); |
| |
| private static class StealRunnableHandler implements RejectedExecutionHandler { |
| @Override |
| public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { |
| THREAD_POOL_EXECUTOR.execute(r); |
| } |
| } |
| |
| /** |
| * Indicates the current status of the task. Each status will be set only once |
| * during the lifetime of a task. |
| */ |
| public enum Status { |
| /** |
| * Indicates that the task has not been executed yet. |
| */ |
| PENDING, |
| /** |
| * Indicates that the task is running. |
| */ |
| RUNNING, |
| /** |
| * Indicates that {@link AsyncTask#onPostExecute} has finished. |
| */ |
| FINISHED, |
| } |
| |
| @SuppressWarnings("NoAndroidAsyncTaskCheck") |
| public static void takeOverAndroidThreadPool() { |
| ThreadPoolExecutor exec = (ThreadPoolExecutor) android.os.AsyncTask.THREAD_POOL_EXECUTOR; |
| exec.setRejectedExecutionHandler(STEAL_RUNNABLE_HANDLER); |
| exec.shutdown(); |
| } |
| |
| /** |
| * Creates a new asynchronous task. This constructor must be invoked on the UI thread. |
| */ |
| public AsyncTask() { |
| mWorker = new Callable<Result>() { |
| @Override |
| public Result call() throws Exception { |
| mTaskInvoked.set(true); |
| Result result = null; |
| try { |
| Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); |
| result = doInBackground(); |
| Binder.flushPendingCommands(); |
| } catch (Throwable tr) { |
| mCancelled.set(true); |
| throw tr; |
| } finally { |
| postResult(result); |
| } |
| return result; |
| } |
| }; |
| |
| mFuture = new NamedFutureTask(mWorker); |
| } |
| |
| private void postResultIfNotInvoked(Result result) { |
| final boolean wasTaskInvoked = mTaskInvoked.get(); |
| if (!wasTaskInvoked) { |
| postResult(result); |
| } |
| } |
| |
| private void postResult(Result result) { |
| ThreadUtils.postOnUiThread(() -> { finish(result); }); |
| } |
| |
| /** |
| * Returns the current status of this task. |
| * |
| * @return The current status. |
| */ |
| public final Status getStatus() { |
| return mStatus; |
| } |
| |
| /** |
| * Override this method to perform a computation on a background thread. |
| * |
| * @return A result, defined by the subclass of this task. |
| * |
| * @see #onPreExecute() |
| * @see #onPostExecute |
| */ |
| @WorkerThread |
| protected abstract Result doInBackground(); |
| |
| /** |
| * Runs on the UI thread before {@link #doInBackground}. |
| * |
| * @see #onPostExecute |
| * @see #doInBackground |
| */ |
| @MainThread |
| protected void onPreExecute() {} |
| |
| /** |
| * <p>Runs on the UI thread after {@link #doInBackground}. The |
| * specified result is the value returned by {@link #doInBackground}.</p> |
| * |
| * <p>This method won't be invoked if the task was cancelled.</p> |
| * |
| * @param result The result of the operation computed by {@link #doInBackground}. |
| * |
| * @see #onPreExecute |
| * @see #doInBackground |
| * @see #onCancelled(Object) |
| */ |
| @SuppressWarnings({"UnusedDeclaration"}) |
| @MainThread |
| protected void onPostExecute(Result result) {} |
| |
| /** |
| * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and |
| * {@link #doInBackground()} has finished.</p> |
| * |
| * <p>The default implementation simply invokes {@link #onCancelled()} and |
| * ignores the result. If you write your own implementation, do not call |
| * <code>super.onCancelled(result)</code>.</p> |
| * |
| * @param result The result, if any, computed in |
| * {@link #doInBackground()}, can be null |
| * |
| * @see #cancel(boolean) |
| * @see #isCancelled() |
| */ |
| @SuppressWarnings({"UnusedParameters"}) |
| @MainThread |
| protected void onCancelled(Result result) { |
| onCancelled(); |
| } |
| |
| /** |
| * <p>Applications should preferably override {@link #onCancelled(Object)}. |
| * This method is invoked by the default implementation of |
| * {@link #onCancelled(Object)}.</p> |
| * |
| * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and |
| * {@link #doInBackground()} has finished.</p> |
| * |
| * @see #onCancelled(Object) |
| * @see #cancel(boolean) |
| * @see #isCancelled() |
| */ |
| @MainThread |
| protected void onCancelled() {} |
| |
| /** |
| * Returns <tt>true</tt> if this task was cancelled before it completed |
| * normally. If you are calling {@link #cancel(boolean)} on the task, |
| * the value returned by this method should be checked periodically from |
| * {@link #doInBackground()} to end the task as soon as possible. |
| * |
| * @return <tt>true</tt> if task was cancelled before it completed |
| * |
| * @see #cancel(boolean) |
| */ |
| public final boolean isCancelled() { |
| return mCancelled.get(); |
| } |
| |
| /** |
| * <p>Attempts to cancel execution of this task. This attempt will |
| * fail if the task has already completed, already been cancelled, |
| * or could not be cancelled for some other reason. If successful, |
| * and this task has not started when <tt>cancel</tt> is called, |
| * this task should never run. If the task has already started, |
| * then the <tt>mayInterruptIfRunning</tt> parameter determines |
| * whether the thread executing this task should be interrupted in |
| * an attempt to stop the task.</p> |
| * |
| * <p>Calling this method will result in {@link #onCancelled(Object)} being |
| * invoked on the UI thread after {@link #doInBackground()} |
| * returns. Calling this method guarantees that {@link #onPostExecute(Object)} |
| * is never invoked. After invoking this method, you should check the |
| * value returned by {@link #isCancelled()} periodically from |
| * {@link #doInBackground()} to finish the task as early as |
| * possible.</p> |
| * |
| * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this |
| * task should be interrupted; otherwise, in-progress tasks are allowed |
| * to complete. |
| * |
| * @return <tt>false</tt> if the task could not be cancelled, |
| * typically because it has already completed normally; |
| * <tt>true</tt> otherwise |
| * |
| * @see #isCancelled() |
| * @see #onCancelled(Object) |
| */ |
| public final boolean cancel(boolean mayInterruptIfRunning) { |
| mCancelled.set(true); |
| return mFuture.cancel(mayInterruptIfRunning); |
| } |
| |
| /** |
| * Waits if necessary for the computation to complete, and then |
| * retrieves its result. |
| * |
| * @return The computed result. |
| * |
| * @throws CancellationException If the computation was cancelled. |
| * @throws ExecutionException If the computation threw an exception. |
| * @throws InterruptedException If the current thread was interrupted |
| * while waiting. |
| */ |
| @DoNotInline |
| public final Result get() throws InterruptedException, ExecutionException { |
| Result r; |
| if (getStatus() != Status.FINISHED && ThreadUtils.runningOnUiThread()) { |
| StackTraceElement[] stackTrace = new Exception().getStackTrace(); |
| String caller = ""; |
| if (stackTrace.length > 1) { |
| caller = stackTrace[1].getClassName() + '.' + stackTrace[1].getMethodName() + '.'; |
| } |
| try (TraceEvent e = TraceEvent.scoped(caller + "AsyncTask.get")) { |
| r = mFuture.get(); |
| } |
| } else { |
| r = mFuture.get(); |
| } |
| return r; |
| } |
| |
| /** |
| * Executes the task with the specified parameters. The task returns |
| * itself (this) so that the caller can keep a reference to it. |
| * |
| * <p>This method is typically used with {@link #THREAD_POOL_EXECUTOR} to |
| * allow multiple tasks to run in parallel on a pool of threads managed by |
| * AsyncTask, however you can also use your own {@link Executor} for custom |
| * behavior. |
| * |
| * <p><em>Warning:</em> Allowing multiple tasks to run in parallel from |
| * a thread pool is generally <em>not</em> what one wants, because the order |
| * of their operation is not defined. For example, if these tasks are used |
| * to modify any state in common (such as writing a file due to a button click), |
| * there are no guarantees on the order of the modifications. |
| * Without careful work it is possible in rare cases for the newer version |
| * of the data to be over-written by an older one, leading to obscure data |
| * loss and stability issues. Such changes are best |
| * executed in serial; to guarantee such work is serialized regardless of |
| * platform version you can use this function with {@link #SERIAL_EXECUTOR}. |
| * |
| * <p>This method must be invoked on the UI thread. |
| * |
| * @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a |
| * convenient process-wide thread pool for tasks that are loosely coupled. |
| * |
| * @return This instance of AsyncTask. |
| * |
| * @throws IllegalStateException If {@link #getStatus()} returns either |
| * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}. |
| */ |
| @SuppressWarnings({"MissingCasesInEnumSwitch"}) |
| @MainThread |
| public final AsyncTask<Result> executeOnExecutor(Executor exec) { |
| if (mStatus != Status.PENDING) { |
| switch (mStatus) { |
| case RUNNING: |
| throw new IllegalStateException("Cannot execute task:" |
| + " the task is already running."); |
| case FINISHED: |
| throw new IllegalStateException("Cannot execute task:" |
| + " the task has already been executed " |
| + "(a task can be executed only once)"); |
| } |
| } |
| |
| mStatus = Status.RUNNING; |
| |
| onPreExecute(); |
| |
| exec.execute(mFuture); |
| |
| return this; |
| } |
| |
| private void finish(Result result) { |
| if (isCancelled()) { |
| onCancelled(result); |
| } else { |
| onPostExecute(result); |
| } |
| mStatus = Status.FINISHED; |
| } |
| |
| class NamedFutureTask extends FutureTask<Result> { |
| NamedFutureTask(Callable<Result> c) { |
| super(c); |
| } |
| |
| Class getBlamedClass() { |
| return AsyncTask.this.getClass(); |
| } |
| |
| @Override |
| protected void done() { |
| try { |
| postResultIfNotInvoked(get()); |
| } catch (InterruptedException e) { |
| android.util.Log.w(TAG, e); |
| } catch (ExecutionException e) { |
| throw new RuntimeException( |
| "An error occurred while executing doInBackground()", e.getCause()); |
| } catch (CancellationException e) { |
| postResultIfNotInvoked(null); |
| } |
| } |
| } |
| } |