For the past decade AysncTask has been a very popular approach for writing concurrent code in Android applications. However, the era of AsyncTask ended because, starting with Android 11, AsyncTask is deprecated.
In this post, I’ll review the official statement motivating AsyncTask’s deprecation, explain why it doesn’t make sense, and then share a list of the real problems with this framework that really justify its retirement. In addition, I’ll share my thoughts on the future of concurrency APIs in Android and suggest what you should do if you’ve got AsyncTasks spread all over your codebase.
Official Reason for Deprecation of AsyncTask
The official deprecation of AsyncTask, as well as the motivation for that decision, were introduced with this commit. The newly added paragraph of Javadoc states:
AsyncTask was intended to enable proper and easy use of the UI thread. However, the most common use case was for integrating into UI, and that would cause Context leaks, missed callbacks, or crashes on configuration changes. It also has inconsistent behavior on different versions of the platform, swallows exceptions from
doInBackground
, and does not provide much utility over usingExecutor
s directly.
While that’s the official statement by Google, there are several inaccuracies there which are worth pointing out.
Fist of all, AsyncTask has never been intended to “enable proper and easy use of the UI thread”. It was intended to offload long-running operations from UI thread to background threads, and then deliver the results of these operations back to UI thread. I know, I’m nitpicking here. However, in my opinion, when Google deprecates API that so many developers use, it would be appropriate to invest more effort into the deprecation message to prevent further confusion.
That said, the more interesting part of this paragraph is: “that would cause Context leaks, missed callbacks, or crashes on configuration changes”. So, Google basically states that the most common use case for AsyncTask automatically results in very serious problems. However, there are many high-quality applications out there which use AsyncTask and work flawlessly. Even some classes inside AOSP itself use AsyncTask. How come they don’t experience these problems?
To answer this question, let’s discuss the relationship between AsyncTask and memory leaks.
AsyncTask and Memory Leaks
This AsyncTask leaks the enclosing Fragment (or Activity) object forever:
private final AtomicInteger counter = new AtomicInteger(0); @Override public void onStart() { super.onStart(); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... voids) { while (true) { Log.d("AsyncTask", "count: " + counter.get()); counter.incrementAndGet(); } } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }
Looks like this example proves Google’s point: AsyncTask indeed causes memory leaks. We should probably use some other approach to write concurrent code!
Well, let’s give it a try. This is the same example, rewritten using RxJava:
@Override public void onStart() { super.onStart(); Observable.fromCallable(() -> { while (true) { Log.d("RxJava", "count: " + counter.get); counter.incrementAndGet(); } }).subscribeOn(Schedulers.computation()).subscribe(); }
It leaks the enclosing Fragment (or Activity) object in exactly the same manner.
Maybe new Kotlin Coroutines will help? That’s how I’d achieve the same functionality using Coroutines:
override fun onStart() { super.onStart() CoroutineScope(Dispatchers.Main).launch { withContext(Dispatchers.Default) { while (true) { Log.d("Coroutines", "count: ${counter.get()}") counter.incrementAndGet() } } } }
Unfortunately, it results in the same exact memory leak.
Looks like this feature leaks the enclosing Fragment (or Activity), regardless of the choice of multithreading framework. In fact, it would result in a leak even if I’d use a bare Thread class:
@Override public void onStart() { super.onStart(); new Thread(() -> { while (true) { Log.d("Thread", "count: " + counter.get()); counter.incrementAndGet(); } }).start(); }
So, it’s not about AsyncTask, after all, but about the logic that I write. To drive this point home, let’s modify the example that uses AsyncTask to fix the memory leak:
private AsyncTask mAsyncTask; @Override public void onStart() { super.onStart(); mAsyncTask = new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... voids) { while (!isCancelled()) { Log.d("AsyncTask", "count: " + counter.get()); counter.incrementAndGet(); } return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } @Override public void onStop() { super.onStop(); mAsyncTask.cancel(true); }
In this case, I use the dreadful AsyncTask, but there are no leaks. It’s magic!
Well, of course it’s not magic. It’s just a reflection of the fact that you can write safe and correct multithreaded code using AsyncTask, just like you can do it with any other multithreading framework. In reality, there is no direct connection between AsyncTask and memory leaks. Therefore, the common belief that AsyncTask automatically leads to memory leaks, as well as the new deprecation message in AOSP, are simply incorrect.
[Edit: the original version of this article used local counter
variable, instead of it being a member of the enclosing Activity or Fragment. As several readers correctly pointed out, as opposed to inner classes, lambdas do not capture references to parent objects if they don’t have to. Therefore, if counter
variable would be local, the above examples that use lambdas wouldn’t, in fact, leak the enclosing Activity or Fragment. So, I changed the examples a bit. Note, however, that since AsyncTask had been used in Android long before we could use lambdas, the fact that lambdas behave differently isn’t that important. In addition, I wouldn’t recommend relying on this property of lambdas as a mean to avoid memory leaks because if you do that, you’ll always be just a small code modification away from it.]
You might be wondering now: if this idea that AsyncTask leads to memory leaks is incorrect, why is it so widespread among Android developers?
Well, there is a built in Lint rule in Android Studio which warns you and recommends making your AsyncTasks static to avoid memory leaks. This warning and recommendation are incorrect too, but developers who use AsyncTask in their projects get this warning and, since it comes from Google, take it at face value.
In my opinion, the above Lint warning is the reason why the myth that AsyncTask causes memory leaks is so widespread: it’s being forced on developers by Google itself.
How to Avoid Memory Leaks in Multithreaded Code
Until now, we established that there is no automatic causal relationship between AsyncTask and memory leaks. In addition, you saw that memory leaks can happen with any multithreading framework. Consequently, you might be wondering now how to avoid memory leaks in your own applications.
I won’t answer this question in detail, because I want to stay on-topic, but I don’t want to leave you empty-handed either. Therefore, allow me to list the concepts that you need to understand to write correct multithreaded code in Android:
- Garbage collector
- Garbage collection roots
- Thread lifecycle in respect to garbage collection
- Implicit references from inner classes to parent objects
If you understand these concepts, you’ll be less likely to have memory leaks in your code. On the other hand, if you don’t understand these concepts and you write concurrent code, then it’s just a matter of time before you introduce memory leaks, whatever multithreading framework you’ll use.
[Since this is an important piece of knowledge for all Android developers, I decided to upload the first part of my Android Multithreading course to YouTube. It covers the basics of concurrency in great detail.]
AsyncTask Deprecated For No Reason?
Since AsyncTask doesn’t automatically lead to memory leaks, looks like Google deprecated it by mistake, for no reason. Well, not exactly.
For the past years, AsyncTask has already been “effectively deprecated” by Android developers themselves. Many of us openly advocated against using this API in applications and I, personally, feel sorry for developers who maintain codebases that use AsyncTask extensively. It was evident that AsyncTask is very problematic API. If you ask me, Google should’ve deprecated it sooner.
Therefore, while Google is still confused about their own creation, the deprecation itself is very much appropriate and welcome. At the very least, it will let new Android developers know that they don’t need to invest time into learning this API, and they won’t use it in their applications.
Said all that, you might still not understand why exactly AsyncTask was “bad” and why so many developers hated it so much. In my opinion, it’s very interesting and practically useful question. After all, if we don’t understand what AsyncTask’s issues were, there is no guarantee that we will not repeat the same mistakes again.
Therefore, in the next sections, I’ll explain the real problems with AsyncTask.
AsyncTask Problem 1: Makes Multithreading More Complex
One of the main “selling” points of AsyncTask has always been the promise that you won’t need to deal with Thread class and other multithreading primitives yourself. It should’ve made multithreading simpler, especially for new Android developers. Sounds great, right? However, in practice, this “simplicity” backfired.
AsyncTask’s class-level Javadoc uses the word “thread” 16 times. You simply can’t understand it if you don’t understand what a thread is. In addition, this Javadoc states a bunch of AsyncTask-specific constraints and conditions. In other words, if you want to use AsyncTask, you need to understand threads and you also need to understand the many nuances of AsyncTask itself. That’s not what “simpler” means, by any stretch of the imagination.
Furthermore, in my opinion, concurrency is one of the most complex topics in software in general (and, for that matter, hardware too). Unlike many other concepts, you can’t take shortcuts in multithreading because even the smallest mistake can lead to very serious bugs which will be extremely difficult to investigate. There are applications out there which has been affected by multithreading bugs for months even after developers had known that they exist. They just couldn’t find these bugs.
Therefore, in my opinion, there is simply no way to simplify concurrency and AsyncTask’s ambition was destined to fail from the very onset.
AsyncTask Problem 2: Bad Documentation
It’s not a secret that Android documentation is non-optimal (trying to be polite here). It improved over the years, but, even today, I wouldn’t call it good. In my opinion, unfortunate documentation was the leading factor for AsyncTask’s troubled history. If AsyncTask would be just over-engineered, complex and nuanced multithreading framework (as it is), but with good documentation, it could remain part of the ecosystem. But AsyncTask’s documentation was terrible.
The worst were the examples. They demonstrated the most unfortunate approaches to write multithreaded code: everything inside Activities, complete disregard of lifecycles, no discussion of cancellation scenarios, etc.. If you’d use these examples as they are in your own applications, memory leaks and incorrect behavior would be pretty much guaranteed.
AsyncTask Problem 3: Excessive Complexity
AsyncTask has three generic arguments. THREE! If I’m not mistaken, I’ve never seen any other class which required this many generics.
I still remember my first encounters with AsyncTask. By that time, I already knew a bit about Java threads and couldn’t understand why multithreading in Android is so difficult. Three generic arguments were very difficult to understand and felt awkward. In addition, since AsyncTask’s methods are called on different threads, I had to constantly remind myself about that, and then verify that I got it right by reading the docs.
AsyncTask Problem 4: Inheritance Abuse
The philosophy of AsyncTask is grounded in inheritance: whenever you need to execute a task in background, you extend AsyncTask. Combined with bad documentation, inheritance philosophy pushed developers in the direction of writing huge classes which coupled multithreading, domain and UI logic together in the most inefficient and hard to maintain manner.
“Favor composition over inheritance” rule from Effective Java, if followed, would make a real difference in case of AsyncTask.
AsyncTask Problem 5: Reliability
Simply put, default THREAD_POOL_EXECUTOR that backs AsyncTask is misconfigured and unreliable. Google tweaked its configuration at least twice over the years (this commit and this one), but it still crashed the official Android Settings application.
Most Android applications will never need this level of concurrency. However, you never know what will be your use cases a year from now, so using non-reliable solution is problematic.
AsyncTask Problem 6: Concurrency Misconception
This point is related to the bad documentation, but I think it deserves a bullet point on its own. Javadoc for executeOnExecutor() method states:
Allowing multiple tasks to run in parallel from a thread pool is generally not what one wants, because the order of their operation is not defined. […] Such changes are best executed in serial; to guarantee such work is serialized regardless of platform version you can use this function with SERIAL_EXECUTOR
Well, that’s just wrong. Allowing multiple tasks to run concurrently is exactly what you want in most cases when offloading work from UI thread.
For example, let’s say you send a network request and it times out for whatever reason. The default timeout in OkHttp is 10 seconds. If you indeed use SERIAL_EXECUTOR, which executes just one single task at any given instant, you’ve just stopped all background work in your app for 10 seconds. And if you happen to send two requests and both time out? Well, 20 seconds of no background processing. What’s worse, it’s the same for almost any other type of background tasks: database queries, image processing, computations, IPC, etc.
As stated in the documentation, the order of operations offloaded to, for example, a thread pool, is not defined. However, that’s not a problem. In fact, it’s pretty much the definition of concurrency.
Therefore, in my opinion, this statement in the official docs point to some very serious misconception about concurrency among AsyncTask’s authors. I just can’t see any other explanation for having such a misleading information in the official documentation.
The Future of AsyncTask
Hopefully I convinced you that deprecation of the AsyncTask API is a good move on Google’s part. However, for projects that use AsyncTask today, these aren’t good news. If you work on such a project, should you refactor your code now?
First, I don’t think that you need to actively remove AsyncTasks from your code. Deprecation of this API doesn’t mean that it’ll stop working. In fact, I won’t be surprised if AsyncTask will stick around for as long as Android lives. Too many applications, including Google’s own apps, use this API. And even if it’ll be removed in, say, 5 years, you’ll be able to copy paste its code into your own project and change the import statements to keep the logic working.
The main impact of this deprecation will be on new Android developers. Going forward, they’ll understand that they don’t need to invest time into learning AsyncTask and won’t use it in new applications (hopefully).
AsyncTask Alternatives
Since AsyncTask is deprecated now, it leaves a bit of a void that must be filled by some other concurrency approach. So, let’s discuss AsyncTask alternatives.
If you just start your Android journey and use Java, I recommend the combo of bare Thread class and UI Handler. Many Android developers will cringe at this proposal, but I myself used this approach for a while and it worked much, much better than AsyncTask ever could. To gather a bit more feedback on this technique, I created this Twitter poll. At the time of writing, these were the results:
Looks like I’m not the only one who used bare threads and most developers who tried this approach found it alright.
If you already have a bit of experience, you can replace bare Threads with a centralized ExecutorService. For me, the biggest problem with using Thread class was that I constantly forgot to start threads and then had to spend time debugging these silly mistakes. It’s annoying. ExecutorService solved this issue.
I, personally, prefer to use my own ThreadPoster library for multithreading in Java. It’s very lightweight abstraction over ExecutorService and Handler. This library makes multithreading more explicit and unit testing easier.
If you use Kotlin, then the above recommendations are still valid, but there is one more consideration to take into account. It looks like Coroutines framework is going to be the official Kotlin’s concurrency primitive. In other words, even though Kotlin in Android uses threads under the hood, Coroutines are going to be the standard in Kotlin applications.
[If you want to learn Coroutines, you might find my Coroutines course very helpful. It will provide you with all the knowledge and skills that you need to use Coroutines in your projects.]
Most importantly, regardless of which approach you choose, invest time into learning multithreading fundamentals. As you saw in this post, the correctness of your concurrent code isn’t determined by the frameworks, but by your understanding of the underlying principles.
Kotlin Coroutines in Android Course
Master the most advanced concurrency framework for Android development.
Go to CourseConclusion
In my opinion, deprecation of AsyncTask was long overdue. This API had had too many issues and have caused much trouble over the years.
Unfortunately, the official deprecation statement contains incorrect information, so it can confuse developers who will encounter AsyncTask in the future. I hope that this post clarified the situation around AsyncTask,and gave you some more advanced insights into concurrency in Android in general.
For projects that use AsyncTask today this deprecation is problematic, but doesn’t require any immediate action. AsyncTask isn’t going to be removed from Android any time soon, or, maybe, never.
I’ve been following your tweets and posts in r/androiddev on this topic. Thanks for speaking up, I work on a banking app that is full of Async Tasks and massive Activities, new stuff gets written in Kotlin/Coroutines but it’s complicated.
As I’ve also mentioned on that Twitter thread, it can be problematic to create too many Thread instances on some lower tier devices. I’d always recommend an Executor over bare Threads. I however no longer have access to the exact stack trace, but it happened in production, which is always unfortunate.
Yes, that’s definitely a potentional issue to keep in mind.
However, my experiments showed that a live thread occupies ~50kB of memory. Therefore, even devices having 1GB of RAM can support hundreds of concurrent threads.
BTW, if tou’ve got OOM due to thread allocation, it doesn’t necessarily mean the problem was related to threads. Maybe the app allocated/leaked too much memory in other places, and that last additional thread was simply the last straw.
Thanks, Im a beginner and was really confused by the asyncTask deprication. especially confused about the way forward.
This article is such a relief!
I’m always glad when my posts help others. Good luck on your Android journey!
AsyncTask allows to be able to cancel it later.
How can it be done using whatever that caused it to be deprecated?
Hey,
All frameworks allow you to cancel multithreaded flows in some way. Some of them provide a bit more assistance (e.g. RxJava and Coroutines), while with others you’ll need to take care of everything by yourself (e.g. my ThreadPoster).
The important point to remember is that cancellation of flows, especially concurrent ones, is a very delicate and error-prone process. Therefore, in most cases, I try to avoid cancelling anything.
Which one would you use inside RecyclerView, where for each binding you load something (query, load icon from file, do some calculation…), and then need to cancel it when you reach a recycled view?
Is there a way inside the code of the task to check if it’s cancelled, like on AsyncTask?
And what’s so bad about AsyncTask anyway in this case?
To me it seems perfect for it. Upon destroying of the Fragment/Activity, I also cancel all of them, and I also use a single thread pool for all of them in each Fragment/Activity, to avoid possible interference with others.
I wouldn’t write multithreading code inside either RecyclerView or its adapter. The only concurrency that I allow to happen in lists is loading images using third-party library.
As i wrote in the article, if you already use AsyncTask and it works for you, I don’t see a reason to refactor anything.
“The main impact of this deprecation will be on new Android developers. It’ll be clear to them that they don’t need to invest time into learning AsyncTask and won’t use it in new applications.”
Oh they will! Most of them will have to deal with “old” code and libraries which will demand them to understand AsyncTask at least to some extent for many many years to come.
Fran, thanks for your comment.
AsyncTask will indeed survive in existing projects for many years to come, but, to be honest, I don’t remember seeing it anywhere else in the past years. Definitely not in mainstream libraries. In fact, I remember just one library which exposed its functionality using AsyncTask, but it belonged to a small startup and wasn’t in active use or development.
You’re probably right. Excellent article, BTW.
Are you sure that the RxJava and Coroutine examples leak the enclosing activity? Their lambdas don’t implicitly capture the enclosing scope, unlike Java anonymous inner classes. Your point about the async work not getting canceled still stands though.
Hi Jon,
Thanks for your comment.
I’m pretty sure that all these examples leak memory (IIRC, I even verified that when I wrote this article). You can try it by yourself – it’s not that much code.
Edit: you were right and I was wrong. I fixed the article. Thanks for heads up!
My understanding is, this is not a leak, it is holding references for longer than it should but once the work is completed it will release them. A leak would be holding it indefinetely.
Your understanding is correct, once the underlying thread is dead, it’ll no longer constitute garbage collection root and the memory will be re-claimed. However, it’s Google’s terminology to call these leaks, so I just followed their line of reasoning.
It has been a long time since a had to start a new android app.
I was looking around how in hell we developers on a pandemic are suppose to deal with multithread code on Android in 2020… and THANK U for sharing this nice post… thank u for remember my self that I can use just a simple Thread for the job… no generic bizar parameters… AsyncTask just sucks a lot!
That all the gods bless you and your code.
So is there are follow up to this article how AsyncTasks can be removed from code? I am maintaining old code that uses AsyncTasks and I would like to rewrite that parts of code… There were some rumors flying around that AsyncTasks will be removed for Android 12.
Hey Andy,
As far as I know, there is no universal way to remove AsyncTasks. The way to approach this refactoring depends on the state of your specific codebase.
Thanks a lot , I found this interesting and learned a lot .I actually quite new to these and now , I have a decent idea on how things work 🙂 .
Thank you, for such a deep explanation of multithreading. I wanted just to know why AsyncTask is deprecated? because I’ve reached Lesson 7.1 (AsyncTask) in official “Android dev fundamental course (for Java, 10/2018). But I’ve got much more then unswer to my initial question! Thank you again =)
(https://google-developer-training.github.io/android-developer-fundamentals-course-concepts-v2/unit-3-working-in-the-background/lesson-7-background-tasks/7-1-c-asynctask-and-asynctaskloader/7-1-c-asynctask-and-asynctaskloader.html)
P.S. now I can breefly look at that lesson and move forward =)
Vasiliy,
Is your library stable and still being maintained? I noticed it’s at beta 0.8.3 and hasn’t been touched in awhile. Or, are you just waiting for feedback?
Thanks…
Hi,
ThreadPoster is stable and being maintained. I should probably just change the number because you’re not the first one to ask.
If you program an infinite loop, obviously no matters what tech/library or lenguage you use, a memory leak will be created.
The reason to deprecate AsynkTask is not clear. ¿Can you get a problem using it? Yes, of course. I suffered some strange ones. Concurrence has been always problematic on Java. I remember we had several problems using threads in a big Java project years ago and they becomes irresolvable. But the problem was in the core of Java, not in the usage we did.
So, the only “reasonable reasons” to deprecate AsyncTask are others:
1) They did find irresolvable bugs. Maybe.
2) An more important, for me the key, they want people moving to KOTLIN, declaring one of the most popular classes “deprecated”.
Why if memory leaks comes from a bad usage of programers? Well, in that case, give us a guide of good practices…
Google is becoming a dangerous actor in the software industry….
BTW, congrats for your job.