AsyncTask is Deprecated, Now What?

By | 2019-11-26T11:49:22+00:00 November 15th, 2019|Android development|15 Comments

For the past decade, AysncTask has been one of the most widely used solutions for writing concurrent code in Android. However, it earned very controversial reputation. On the one hand, AsyncTask powered, and still powers, many Android applications. On the other hand, most professional Android developers openly dislike this API.

All in all, I’d say that Android community has love-hate relationship with AsyncTask. But there are big news: the era of AsyncTask is about to end because a commit that deprecated it had just landed in Android Open Source Project.

In this post I’ll review the official statement motivating AsyncTask’s deprecation, as well as the real reasons why it had to be deprecated. As you’ll see, these are different sets of reasons. In addition, towards the end of this article, I’ll share my thoughts on the future of Android’s concurrency APIs.

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 first 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 Executors 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 they themselves invented and promoted for years, it would be more respectful towards developers who use this API today, and will continue to use for years to come, to invest more effort into deprecation message to prevent further confusion.

That said, the more interesting part of this deprecation message is this: “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 comes they don’t experience these problems?

To answer this question, let’s discuss the relationship between AsyncTask and memory leaks in details.

AsyncTask and Memory Leaks

This AsyncTask leaks the enclosing Fragment (or Activity) object forever:

    @Override
    public void onStart() {
        super.onStart();
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... voids) {
                int counter = 0;
                while (true) {
                    Log.d("AsyncTask", "count: " + counter);
                    counter ++;
                }
            }
        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    }

Looks like this example proves Google’s point: AsyncTask indeed causes memory leaks. We should 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(() -> {
            int counter = 0;
            while (true) {
                Log.d("RxJava", "count: " + counter);
                counter ++;
            }
        }).subscribeOn(Schedulers.computation()).subscribe();
    }

It leaks the enclosing Fragment (or Activity) object too.

Maybe new and shiny 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) {
                var counter = 0
                while (true) {
                    Log.d("Coroutines", "count: $counter")
                    counter++
                }
            }
        }
    }

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 bare Thread class:

    @Override
    public void onStart() {
        super.onStart();
        new Thread(() -> {
            int counter = 0;
            while (true) {
                Log.d("Thread", "count: " + counter);
                counter++;
            }
        }).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) {
                int counter = 0;
                while (!isCancelled()) {
                    Log.d("AsyncTask", "count: " + counter);
                    counter ++;
                }
                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. There is no special relationship 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.

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 project 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 ties AsyncTask to 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 usage of 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 details, 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 memory leaks free multithreaded code in Java and Kotlin:

  • 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 in details, you’ll lower the probability of having memory leaks in your code considerably. 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.

Edit:

Since this is basic and important knowledge for all Android developers, I decided to upload the first part of my Android Multithreading Masterclass course to YouTube. It covers the aforementioned topics in much more details than the official documentation. You can watch it here for free.

Was 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 usign this API in applications. I, personally, feel sorry for developers who maintain codebases that use AsyncTask extensively. It had been like that for years, and it has been evident that AsyncTask is very problematic API. Therefore, AsyncTask’s deprecation is only logical. If you ask me, Google should’ve deprecated it long ago.

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 probably still don’t understand why exaclty 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 exactly were AsyncTask’s issues, there is no guarantee that we will not repeat the same mistakes again.

Therefore, let’s me list what I personally see as the real AsyncTask’s deficiencies.

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 manyfold.

AsyncTask’s class-level Javadoc uses the word “thread” 16 times. You simply can’t understand it if you don’t understand what’s thread. 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. It’s not “simpler” by any stretch of imagination.

Furthermore, multithreading is intrinsically complex topic. In my opninion, it’s one of the most complex topics in software in general (and, for that matter, hardware too). Now, 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 decent. In my opinion, unfortunate documentation was the leading factor to determine 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. After all, there is no shortage of ugly APIs that Android developers got accustomed to. But AsyncTask’s documentation was terrible and made all its other deficiencies worse.

The worst were the examples. They demonstrated the most unfortunate approaches to write multithreading code: all code 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.

In addition, AsyncTask’s documentation didn’t contain any explanation of the core concepts related to multithreading (the ones I listed earlier, and others). In fact, I think no part of the official documentation did. Not even just a link to JLS Chapter 17: Threads and Locks to refer developers who really wanted to understand concurrency to the “official” reference (the quotes are needed because Oracle documentation isn’t official for Android).

By the way, in my opinion, the aforementioned Lint rule in Android Studio, the one that spread the myth about memory leaks, is also part of the documentation. Therefore, not only the docs were insufficient, but they also contained incorrect information.

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 made me very nervous. 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.

Today, when I know much more about concurrency and Android’s UI thread, I can probably reverse-engineer this information. However, this level of understanding came to me much later in my career, after I had already ditched AsyncTask completely.

And with all these complications, you still must call execute() from UI thread only!

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. After all, why not? That’s what the API of AsyncTask lends itself to.

“Favor composition over inheritance” rule from Effective Java, if followed, would make a real difference in case of AsyncTask. [Interestingly, Joshua Bloch, the author of Effective Java, worked at Google and was involved in Android at a relatively early stage]

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 relying on non-reliable solution is problematic.

AsyncTask Problem 6: Concurrency Misconception

This point is related to 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 exaclty 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. Now, timing out network requests are not any kind of exception, it’s the same for almost any other use case: database queries, image processing, computations, IPC, etc.

Yes, as stated in the documentation, the order of operations offloaded to, for example, thread pool, is not defined because they run concurrently. However, it’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 to such a misleading information found in the official documentation.

The Future of AsyncTask

Hopefully I convinced you that deprecation of AsyncTask 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 of all, 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. 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.

The Future of Multithreading in Android

AsyncTask’s deprecation leaves a bit of a void that must be filled by some other multithreading approach. What should it be? Let me share with you my personal opinion on this subject.

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 object, 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 and most developers who tried this approach found it alright.

If you already got a bit of experience, you can replace manual instantiation of 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.

[By the way, if you’re reading this and want to leave a comment concerning performance, please make sure that your comment includes actual performance metrics.]

Now, I, personally, prefer to use my own ThreadPoster library for multithreading in Java. It’s very light abstraction over ExecutorService and Handler. This library makes multithreading more explicit and unit testing easier.

If you use Kotlin, then the above recommendations 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 minimal level of abstraction in language’s documentation and tutorials.

To me, personally, Coroutines feel very complicated and immature at this point, but I always try to choose tools based on my predictions for the ecosystem two years from now. By this criteria, I’d choose coroutines for a new Kotlin project. Therefore, I recommend all developers who use Kotlin to ramp up and migrate to Coroutines.

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 frameworks, but by your understanding of the underlying principles.

Conclusion

In my opinion, deprecation of AsyncTask was long overdue and it cleared multithreading landscape in Android ecosystem. This API had many issues and had caused much trouble over the years.

Unfortunately, the official deprecation statement contains incorrect information and can potentially confuse developers who use AsyncTask today, or will inherit codebases with AsyncTask in the future. Hopefully, this post clarified some points about AsyncTask specifically, and also gave you food for thought about concurrency in Android in general.

For projects that use AsyncTask today this deprecation is problematic, but doesn’t require any immediate action. AsyncTask won’t be removed from Android any time soon.

By the way, if you want to learn multithreading in Android in depth, check out my new course about multithreading in Android. It covers everything professional Android developers need to know about concurrency: from hardware origins, through Thread class, to Kotlin Coroutines.

As usual, thank you for reading and please leave your comments and questions below.

If you liked this post, then you'll surely like my courses

Subscribe for new posts!

15 Comments

  1. Kent November 15, 2019 at 2:02 pm - Reply

    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.

  2. Gabor Varadi November 15, 2019 at 3:50 pm - Reply

    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.

    • Vasiliy November 15, 2019 at 4:21 pm - Reply

      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.

  3. Khouder November 17, 2019 at 6:56 am - Reply

    Thanks, Im a beginner and was really confused by the asyncTask deprication. especially confused about the way forward.
    This article is such a relief!

    • Vasiliy November 17, 2019 at 3:58 pm - Reply

      I’m always glad when my posts help others. Good luck on your Android journey!

  4. AD_LB November 17, 2019 at 5:31 pm - Reply

    AsyncTask allows to be able to cancel it later.
    How can it be done using whatever that caused it to be deprecated?

    • Vasiliy November 19, 2019 at 9:03 am - Reply

      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.

      • AD_LB November 19, 2019 at 9:08 am - Reply

        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.

        • Vasiliy November 19, 2019 at 9:46 am - Reply

          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.

  5. Fran November 26, 2019 at 10:13 am - Reply

    “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.

    • Vasiliy November 26, 2019 at 10:43 am - Reply

      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.

      • Fran November 26, 2019 at 5:59 pm - Reply

        You’re probably right. Excellent article, BTW.

  6. Jon December 2, 2019 at 3:56 pm - Reply

    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.

    • Vasiliy December 2, 2019 at 4:23 pm - Reply

      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.

  7. Arinze December 8, 2019 at 6:30 pm - Reply

    I also watched your video on concurrency. It’s very educative. I think you’re amazing.

    I noticed your email subscription form sees a valid email as invalid when there are trailing spaces. Consider fixing that.

    Thanks

Leave A Comment