Correct thread management in Android apps

by Ivan.Memruk  on  14. July 2010 04:18

Android supports almost the API of Java SE almost completely (except the Swing classes and some other minor features). Among all the core classes, there is the fabulous Thread class and other concurrency-related classes of JDK 5. You are absolutely allowed to use multiple threads in your Android apps. In fact, most non-trivial apps can be challenging to implement without threads - developers who worked on single-threaded platforms, such as Flash/Flex, will be the first to understand me.

However, the traditional mindset of multi-threaded application development that you could have acquired when working on desktop or server Java apps is not very good for Android apps. The problem is the complex correlation between the lifecycle of the OS process that hosts your app and the lifecycle of your app components (such as activities). To give an example: when an activity A opens and covers a previously opened activity B, B will remain active (that is still in VM memory) for unspecified time - thus, it will either still be alive when you come back to it, or it might be killed to free resources for other activities - in that case it will be recreated when you come back to it.

Another more relevant example is an activity that launches a thread in onCreate():

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
        new Thread() {
            @Override
            public void run() {
                int number = new Random().nextInt(10);
                while (true) {
                    Log.d("***", "Thread #" + number + " running");
                    SystemClock.sleep(3000);
                }
            }
        }.start();
    }
 

Launch this app, and you will see the thread's pings in the log. Then, just change the orientation of your device, either by rotating it or by sliding out the keyboard etc. What you will see is that now you have 2 threads running at the same time, although from the user's point of view, he is still running one and the same application. You will also notice that if you close the activity by pressing the back button, the thread(s) will still be running. In order to avoid that problem, we need to do something like this:


    private volatile boolean requestedToExit = false;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        new<;/span> Thread() {
            @Override
            public void run() {
                int number = new Random().nextInt(10);
                while (!requestedToExit) {
                    Log.d("***", "Thread #" + number + " running");
                    SystemClock.sleep(3000);
                }
                Log.d("***", "Thread #" + number + " is exiting by request");
            }
        }.start();
    }

    @Override
    protected void onDestroy() {
        requestedToExit = true;

        super.onDestroy();
    }
 

As you can see, the thread has to check for the exit flag from time to time. Setting the exit flag from onDestroy() guarantees it will always be set when the activity is being destroyed. To understand this better, you should take a look at the Activity lifecycle diagram in the official Android documentation.

Another common challenge is updating your UI from the threads that you launch. The Android UI framework is not thread safe and can only be invoked from the UI thread that is automatically created and launched by the framework itself. Thus, you can't do anything like this:

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        new Thread() {
            @Override
            public void run() {
                while (!requestedToExit) {
                    TextView text = (TextView) findViewById(R.id.text);
                    text.setText("" + Math.random());
                   
                    SystemClock.sleep(3000);
                }
            }
        }.start();
    }
 

You will get a pretty bad runtime exception if you try it.

Instead, you have to do something like this:

    private Handler handler;
   
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        handler = new Handler();
       
        new Thread() {
            @Override
            public void run() {
                while (!requestedToExit) {
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            TextView text = (TextView) findViewById(R.id.text);
                            text.setText("" + Math.random());
                        }
                    });                
                    SystemClock.sleep(3000);
                }
            }
        }.start();
    }
 

Introducing a Handler allows you to execute code in the UI thread. (To learn more about Handlers, you can read my article that discusses them in detail, and, of course the official Android docs.)

As you can feel at this point, the complexity of activity lifecycle can make background processing really quite difficult to implement using raw Threads. This is why the platform has a nice abstraction designed specifically for UI-related tasks that need to be done in the background - the AsyncTask class. AsyncTask has the following features:

 

  • Ability to return values of custom type to the UI thread when the task is finished
  • Ability to execute some code in the UI thread before the background task begins and after it finished
  • Ability to push updates to the UI thread during the execution of the background task
  • Automatic under-the-hood thread management

 

In our case, we could use an AsyncTask instead of our Thread like this:

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        new AsyncTask<Void, Double, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                while (! requestedToExit) {
                    publishProgress(Math.random());
                    SystemClock.sleep(3000);
                }
               
                return null;
            }

            @Override
            protected void onProgressUpdate(Double... values) {
                TextView text = (TextView) findViewById(R.id.text);
                text.setText("" + values[0]);
            }
        }.execute();
    }
 

Although this solution might seem more heavyweight and less readable than the thread, the good separation between UI and background code, as well as the convenient features that AsyncTask has do actually pay off when you have a more complex use case.

In conclusion, I recommend that you read the official docs on AsyncTask and try to always use it for background / multi-threaded processing in your Android apps. If it doesn't work for your specific case, you can always use Thread, but be careful and don't spawn any threads without clearly understanding how you're going to shut them down.


Author:
Ivan Memruk
mindtherobot.com

Related articles

Should Android market be more regulated?
Android is undeniably one of the hottest products available to customers living in this era of tec...
15 Android features iPhone users will envy
The Android platform dubbed as the “The one of the most liberal platforms in the world&rdquo...
Android Beginners: Intro to Resources and Assets
Managing application resources and assets, such as bitmap files, sounds and other static data that...

blog comments powered by Disqus
� 2010 WiseAndroid lightdir.com