- Android AsyncTask Deprecated, Now What?
- Official Reason for Deprecation of AsyncTask
- AsyncTask and Memory Leaks
- How to Avoid Memory Leaks in Multithreaded Code
- AsyncTask Deprecated For No Reason?
- AsyncTask Problem 1: Makes Multithreading More Complex
- AsyncTask Problem 2: Bad Documentation
- AsyncTask Problem 3: Excessive Complexity
- AsyncTask Problem 4: Inheritance Abuse
- AsyncTask Problem 5: Reliability
- AsyncTask Problem 6: Concurrency Misconception
- The Future of AsyncTask
- AsyncTask Alternatives
- Conclusion
Android AsyncTask Deprecated, Now What?
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 using Executor 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:
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:
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:
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:
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:
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.
Conclusion
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.
Источник