Background:
AsyncTask is deprecated from Android 11. Google has recommened to use
Executors and Handlers to achieve the background computation and UI updates.
Challenge:
For larger enterprise applications/projects (with more AsyncTasks),
replacing the asynctask with Executors/Handlers, could be an uphill task.
It would be even more challenging, if the existing async task does lot
of complex background computations and more UI interactions and the
author/SME is not around.
Migrating them would require more code changes, more testing and
it involves more risk of breaking the functionality.
If your project is still in JAVA, please continue reading here.
For Kotlin, you might want to leverage the benefits of coroutines,
which is not explained here.
Solution:
Create a base class that exposes similar AsyncTask API but internally
it uses executors and handlers. For migration, instead of extending
AsyncTask, just extend the new base class.
GitHub:
https://github.com/sudhans/ExecutorAsyncTask
Limitations/Differences:
The above snippet is not strict like the async task in throwing
exceptions if the execute() method is called after the asynctask is complete.
Implement a ThreadFactory, if you would like to give the custom
thread names for the executor.
Code Snippet:
import android.os.Handler;import android.os.Looper;import android.util.Log;import androidx.annotation.AnyThread;import androidx.annotation.MainThread;import androidx.annotation.Nullable;import androidx.annotation.WorkerThread;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;import java.util.concurrent.atomic.AtomicBoolean;public abstract class AsyncTaskV2<Params, Progress, Result> {public enum Status {FINISHED,PENDING,RUNNING}// This handler will be used to communicate with main threadprivate final Handler handler = new Handler(Looper.getMainLooper());private final AtomicBoolean cancelled = new AtomicBoolean(false);private Result result;private Future<Result> resultFuture;private ExecutorService executor;private Status status = Status.PENDING;// Base class must implement this methodprotected abstract Result doInBackground(Params params);// Methods with default implementation// Base class can optionally override these methods.protected void onPreExecute() {}protected void onPostExecute(Result result) {}protected void onProgressUpdate(Progress progress) {}protected void onCancelled() {}protected void onCancelled(Result result) {onCancelled();}@MainThreadpublic final Future<Result> execute(@Nullable Params params) {status = Status.RUNNING;onPreExecute();try {executor = Executors.newSingleThreadExecutor();Callable<Result> backgroundCallableTask = () -> doInBackground(params);// Execute the background taskresultFuture = executor.submit(backgroundCallableTask);// On the worker thread - wait for the background task to completeexecutor.submit(this::getResult);return resultFuture;} finally {if (executor != null) {executor.shutdown();}}}private Runnable getResult() {return () -> {try {if (!isCancelled()) {// This will block the worker thread, till the result is availableresult = resultFuture.get();// Post the result to main threadhandler.post(() -> onPostExecute(result));} else {// User cancelled the operation, ignore the resulthandler.post(this::onCancelled);}status = Status.FINISHED;} catch (InterruptedException | ExecutionException e) {Log.e("Exception while trying to get result ", e.getMessage());}};}@WorkerThreadpublic final void publishProgress(Progress progress) {if (!isCancelled()) {handler.post(() -> onProgressUpdate(progress));}}@MainThreadpublic final void cancel(boolean mayInterruptIfRunning) {cancelled.set(true);if (resultFuture!= null) {resultFuture.cancel(mayInterruptIfRunning);}}@AnyThreadpublic final boolean isCancelled() {return cancelled.get();}@AnyThreadpublic final Status getStatus() {return status;}}