Explain Codes LogoExplain Codes Logo

The AsyncTask API is deprecated in Android 11. What are the alternatives?

java
thread-management
concurrency
kotlin-coroutines
Alex KataevbyAlex Kataev·Oct 6, 2024
TLDR

For a quick AsyncTask replacement in your projects, switch to using a combination of ViewModel, LiveData, and Kotlin Coroutines for lifecycle-aware background operations:

class MyViewModel : ViewModel() { private val mutableLiveData = MutableLiveData<ResultType>() fun getLiveData(): LiveData<ResultType> { // Inside the launch() is where AsyncTask is getting replaced. Bye bye AsyncTask! viewModelScope.launch(Dispatchers.IO) { val result = doBackgroundWork() // Do whatever your AsyncTask used to do here. // See ya on the UI thread, pals! mutableLiveData.postValue(result) } return mutableLiveData } private suspend fun doBackgroundWork(): ResultType { // It's so lonely here without AsyncTask... just kidding! return ResultType() } }

Now, in your Activity or Fragment:

// Hello, LiveData! Nice to see ya in place of AsyncTask! myViewModel.getLiveData().observe(this, Observer { result -> // UI, meet your new best friend! })

Next, you need to add appropriate dependencies to support ViewModel, LiveData, and Coroutines. Put these lines in your build.gradle file:

implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:latest_version' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:latest_version' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:latest_version'

Voila! You just replaced AsyncTask with ViewModel, LiveData, and Coroutines. This way, your operations are lifecycle-aware and get automatically cancelled if the ViewModel is cleared—no more memory leaks!

Digging into the toolbox

Going concurrent with ExecutorService and Handlers

ExecutorService and Handler offer powerful and versatile replacements for AsyncTask. You can run tasks in a background thread with ExecutorService and post results on the UI thread using Handler. For long-running operations, Callable interface is your new pal.

ExecutorService executorService = Executors.newSingleThreadExecutor(); Handler handler = new Handler(Looper.getMainLooper()); executorService.submit(new Callable<ResultType>() { @Override public ResultType call() throws Exception { // AsyncTask goes away, real world gets in! return new ResultType(); } }).thenAcceptAsync(result -> { handler.post(() -> { // UI, here I come! }); }, executorService);

For Java 8 and above, simplify your codes with lambda expressions and method references. Remember, compatibility with older APIs is crucial—works fine with minSdkVersion 16.

Structured concurrency: Embrace Kotlin Coroutines

In Kotlin, leverage lifecycleScope or viewModelScope for structured concurrency. It makes sure your coroutines get cancelled properly when the scope is cleared, avoiding any potential memory leaks.

lifecycleScope.launch(Dispatchers.IO) { // Bye AsyncTask! Your background work goes here. }.invokeOnCompletion { error -> if (error == null) { runOnUiThread { // The spotlight's on UI. All set for an update! } } }

When dealing with Context in inner classes or threads, pass it explicitly or use the Application context instead of using WeakReference<Context>.

LiveData for the rescue

Embed LiveData within a ViewModel to observe data changes and monitor task progression. This comes in particularly handy when combined with Coroutines like shown above.

Coding conduct: Proper Thread management and exception handling

Code defensively! Threading issues like thread leakage and improper exception handling can bring your application's performance down. Learn to master your threads and exceptions to create a robust and responsive application.

Going beyond the basics

Better decisions for better performance

Memory management and performance should be your top considerations when replacing AsyncTask. For concurrent network calls or database operations, a ThreadPoolExecutor can suit your needs better. In any case, always ensure proper task cancellation to prevent memory leaks.

Threads: Sometimes simple is better

For operations that are not very complex, using Threads directly might be a simpler option. However, be vigilant with thread management—it's key. To update UI from a background thread, runOnUiThread can be your go-to.

Kotlin extension functions for better structure

Kotlin offers extension functions which you can use to structure your background tasks better. It promotes cleaner and more readable code while enhancing reusability.

A TaskRunner class for structured task execution

Consider creating a TaskRunner class that encapsulates Executor and Handler, offering a structured way for task execution and communicating with the main thread:

public class TaskRunner<ResultType> { private final ExecutorService executorService = Executors.newSingleThreadExecutor(); private final Handler handler = new Handler(Looper.getMainLooper()); public void executeAsync(Callable<ResultType> callable, Consumer<ResultType> callback) { executorService.submit(callable).thenAcceptAsync(callback, handler); } }

Dispatchers in Kotlin: Pick the right playground for your coroutines

Make use of different Dispatchers in Kotlin to specify which thread your coroutine should run on. Dispatchers.IO for the background work and Dispatchers.Main for the UI updates will be the perfect fit.