ModernAsyncTask.java
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.loader.content;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import androidx.annotation.NonNull;
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.atomic.AtomicBoolean;
/**
* Copy of the required parts of {@link android.os.AsyncTask} from Android 3.0 that is
* needed to support AsyncTaskLoader. We use this rather than the one from the platform
* because we rely on some subtle behavior of AsyncTask that is not reliable on
* older platforms.
*
* <p>Note that for now this is not publicly available because it is not a
* complete implementation, only sufficient for the needs of
* {@link android.content.AsyncTaskLoader}.
*/
abstract class ModernAsyncTask<Result> {
private static final String LOG_TAG = "AsyncTask";
private static Handler sHandler;
private final FutureTask<Result> mFuture;
private volatile Status mStatus = Status.PENDING;
@SuppressWarnings("WeakerAccess") /* synthetic access */
final AtomicBoolean mCancelled = new AtomicBoolean();
@SuppressWarnings("WeakerAccess") /* synthetic access */
final AtomicBoolean mTaskInvoked = new AtomicBoolean();
/**
* 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 android.os.AsyncTask#onPostExecute(Object)} has finished.
*/
FINISHED,
}
private static Handler getHandler() {
synchronized (ModernAsyncTask.class) {
if (sHandler == null) {
sHandler = new Handler(Looper.getMainLooper());
}
return sHandler;
}
}
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*/
ModernAsyncTask() {
Callable<Result> worker = new Callable<Result>() {
@Override
public Result call() {
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 FutureTask<Result>(worker) {
@Override
protected void done() {
try {
final Result result = get();
postResultIfNotInvoked(result);
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException(
"An error occurred while executing doInBackground()", e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
} catch (Throwable t) {
throw new RuntimeException(
"An error occurred while executing doInBackground()", t);
}
}
};
}
void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) {
postResult(result);
}
}
void postResult(final Result result) {
getHandler().post(new Runnable() {
@Override
public void run() {
finish(result);
}
});
}
/**
* Override this method to perform a computation on a background thread.
*
* @return A result, defined by the subclass of this task.
*
* @see #onPostExecute
*/
protected abstract Result doInBackground();
/**
* <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 #doInBackground
* @see #onCancelled(Object)
*/
protected void onPostExecute(Result result) {
}
/**
* <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and
* {@link #doInBackground()} has finished.</p>
*
* @param result The result, if any, computed in
* {@link #doInBackground()}, can be null
*
* @see #cancel(boolean)
* @see #isCancelled()
*/
protected void onCancelled(Result result) {
}
/**
* 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);
}
/**
* Executes the task with the specified parameters. The task returns
* itself (this) so that the caller can keep a reference to it.
*
* <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.
*
* <p>This method must be invoked on the UI thread.
*
* @param exec The executor to use.
*
* @throws IllegalStateException If already running or finished.
*/
public final void executeOnExecutor(@NonNull 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)");
default:
throw new IllegalStateException("We should never reach this state");
}
}
mStatus = Status.RUNNING;
exec.execute(mFuture);
}
void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
}