Wednesday, May 25, 2016

AsyncTasks and You

Let's talk about threading (again), and about AsyncTasks. I am not going to explain them, but rather assume that you are an Android developer who has just stumbled across this class in the framework and are trying to use them to do all the things.

As with all parts of the framework, you must always understand where it ties into the lifecycle. So first, let's leak some memory (prepare for some handwritten, from memory code).

public class MyActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        new AsyncTask<Void, Void, Void>() {
        
            @Override
            protected Void doInBackground(Void... params) {
                while (true) {
                    // Loop forever.
                }
                return null;
            }
        }.execute();
    }
}

Alright, so this leaks. If the instance of MyActivity is destroyed, the AsyncTask does not stop doing its background task. The AsyncTask holds an implicit reference to the MyActivity context, which leaks the entire activity. Blah blah, all things you've heard before.

You've also probably heard to avoid AsyncTask because they are bad and all that other fun stuff too. Well, sometimes you can't just use something else, or ship with RxJava or anything nice like that. Sometimes, all you've got is the Android framework. So take a moment to learn about AsyncTasks.

One of the easiest things you can do is to never create anonymous, non-static AsnycTask instances. Instead, you can create a static inner class which extends AsyncTask, or create the anonymous AsyncTask in a static function. This will prevent the AsyncTask from capturing the implicit reference to your Activity and should plug the hole on a major source of AsyncTask memory leaks.

The other thing would be to add checks during any long running doInBackground() code to see if the AsyncTask isCancelled(). If it is, you can stop the background operation and the AsyncTask would be able to be cleaned up at that point. In order to make this worthwhile though you would need to hold a strong reference to the AsyncTask itself in your Activity and call cancel() on it during onDestroy(). It is more work, and this should generally only be used if you really need to reference something in your Activity itself like an ImageView, and it should always be used along with the advice above about static classes or functions.

Finally, if you are holding an ImageView for example in your AsyncTask so that you can modify it onPostExecute(), you should always try to not hold a strong reference to it. The ImageView will also be held by the Activity itself, so your AsyncTask can get away with holding a WeakReference to it. If you are holding a Context, see if you can hold the Application context instead of the Activity context.

Finally finally, remember that on modern Android, AsyncTasks are run one after the other. This means that there may be cases where your task leaks just because it is waiting for the other one to finish its long running operation. Sometimes you are able to apply a parallel executor to the AsyncTasks, sometimes you are not. There is no silver bullet for AsyncTasks, you must remember to test and verify that your AsyncTasks work as intended and do not leak any precious memory.


========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my FaceBook Page
Follow my Google+ Page
=========================