Process concepts

From Android Wiki

Jump to: navigation, searcha

Being based on a Linux kernel, Android supports standard POSIX-style processes and threads. However, the user doesn’t typically see applications in terms of these; instead, the Android UI works in terms of tasks and activities.

Typically, all the activities defined by an app run in a single process, and different apps run in different processes. However, this can be controlled by the programmer in various ways, by specifying the launchMode and taskAffinity for an activity. Also, the application process attribute allows two apps that trust each other (are signed by the same private key) to run in the same process, if the programmer desires.

Note that, in the Android docs where it says that an activity is “stopped” while in the background, all that means is that its onStop method has been called, and its UI is no longer considered to be active. Any processes and threads associated with the activity keep right on running as normal, and can continue performing any actions they like, though of course the only recommended UI-related operations would be the sending of notifications indicating that they would like some user attention.

And of course such background processes/threads are near the top of the priority queue for being killed if system resources run low.


The UI (Main) Thread

Note that your app process initially starts with a single thread. In particular, all UI calls must happen on this thread. With the obvious exception of the relevant Handler methods, and Activity.runonUiThread, the Android UI classes (views, widgets etc) are not thread-safe, and must not be invoked on any other thread. You are free to create additional processes and threads; the easiest way to manage background activities that need to coordinate with the UI (display progress updates, handle cleanup on cancellation etc) is to use an AsyncTask.

The UI thread runs a Looper, which you can also instantiate to run on your own threads. If the UI Looper does not get some CPU time within 5(?) seconds of a user event, you see the dreaded “Application Not Responding” (ANR) alert.

If you need to run short tasks that will not hold up the UI thread for too long, you can queue them to the Looper via Handlers (see the post and postAtTime methods). More time-consuming operations that cannot be split up into short-duration tasks should be relegated to a background thread.

Programmers familiar with Java from other platforms may be accustomed to using a TimerTask to schedule timed events, but these run on their own thread, so they are not safe for directly making UI calls. You can, however, call Activity.runonUiThread from a background thread.

Tasks And The Back Stack

A task is anything started by tapping an icon in the launcher. The launcher’s app tray is automatically populated with appropriate entries (i.e. main activities) from all installed apps, and these entries can be copied to home screens. It is also possible to add shortcuts to home screens. Typically a task is started in a new process, and if such a process is already running, then tapping the icon simply brings the existing process to the front. However, it is possible to alter both these behaviours.

The first (root) activity in a task can launch additional activities as appropriate. Indeed, any application you write other than the very simplest is likely to consist of more than one activity. Tasks can also launch other, entirely separate tasks, the difference being that these show up in the recent-tasks list as separate entries, whereas app-internal activities do not. As each new activity is started, it becomes the topmost one visible to the user, and the one previously topmost has its UI deactivated. These additional activities usually run in the same process as the one that started them, but again, this behaviour can be varied.

Pressing the standard Android Back key normally terminates the topmost activity, and makes the previously-topmost one active. Terminating the root activity of a task terminates the task and returns the user to whatever task was running before (perhaps the launcher).

Activities within a task are never reordered as long as they are running; the only way to bring an activity that is not at the top of the back stack of a task to the top is to terminate all the activities above it in that task. However, tasks can be reordered, since any of the most recent eight tasks launched can be brought to the front at any time simply by leaning on the Home key and tapping on one of the entries that appear.

Activities are launched via some varient of the startActivity calls available to subclasses of a Context (which includes your own Activities). The main argument to each of these calls is an intent.

Memory Usage

On an unrooted Android device, Java code is strictly limited in how much memory it can use. Java objects are restricted to a heap size of about 20MB, while Bitmap objects are limited to their own heap area of a similar size. Oh, and contrary to what it says on that page about the recycle method being “an advanced call, and normally need not be called”, I would recommend you always call recycle on bitmaps when you have finished with them; failure to do this is liable to make your app crash at some point with the dreaded message “java.lang.OutOfMemoryError: bitmap size exceeds VM budget”.

Native code is not arbitrarily limited in its memory usage, and is free to allocate all available memory.

Save/Restore Instance State

Activities and views can implement calls to save and restore their “instance state”. This is not the same as saving/restoring user data; this is purely part of the mechanism to make it look like your process has been running all along, even though the system needed to kill it either because it was running low on memory, or to relaunch it on an orientation change.

If your activity is frontmost, and the user presses the Back button, then that is generally considered to be a request to terminate the activity. The next time the user starts your activity, they will not normally expect it to resume precisely as though it had never terminated. In this situation, onSaveInstanceState and onRestoreInstanceState are not called.

If your activity is frontmost, and the user launches some other activity on top of it, then your onSaveInstanceState methods will be called along with onPause. If the user returns to your activity while it is still running, then onResume will be called as usual, but there is no need to call onRestoreInstanceState.

If, however, the system ran short of memory while your activity was not frontmost, it can be killed without the user noticing. Then, when the user returns to it, it will be relaunched, and onRestoreInstanceState will be called so you can make it look like you were running all along.

Also if the device changes between portrait and landscape orientation while your activity is frontmost, the default action, if your code does not specifically handle this itself, is for the system to kill and relaunch you. Provided your layouts are properly designed to handle both cases, this is often good enough.

Note that what constitutes “instance state” is entirely up to you. For example, a map viewer app might always revert to a fully-zoomed-out view every time the user launches it. So the scroll position and zoom magnification would be saved and restored as part of the instance state. Or you might decide that the app will remember its scroll offset and zoom magnification every time it is quitted and relaunched regardless, in which case save/restore instance state doesn’t have anything to do at all. As the writer of the app, it’s your choice.

For more details, see Save/restore instance state.

You Can’t Kill Threads

Note that it is not possible for one thread to kill or abort another through the Java API: the relevant Thread.stop/destroy methods are not implemented, because of the risk they can leave the Dalvik VM in an inconsistent state.

This also applies to classes that build on Thread, like AsyncTask: once the doInbackground method starts executing on the background thread, it will run to completion, regardless of whether you pass true to cancel or not.

Of course, native code can bypass this restriction, with all the dangers that implies. And also, processes can always be killed in their entirety, because once the entire process state is gone, we don’t care about its consistency.

Personal tools