Android is an operating system with multitasking, allowing several applications to run at the same time. Developers coming from a different platform may find this a notable change when applying this concept. When starting to develop an Android application, it’s necessary to understand how it works in order to integrate it as well as possible; here we’ll talk about the design, the features, the use, and its development, as explained by Google engineer Dianne Hackborn:
Mobile devices have technical limitations that desktop or web systems don’t have. This article explains the four key principles behind the design of multitasking in Android.
- The choice was made to use memory management based on “temporal locality” (yes, something like a memory cache); in other words, apps aren’t actually closed when the user stops using them, since the tendency is to use a small number of applications throughout the day.
- Mobile devices don’t have the luxury of swap memory space, so they have very restrictive memory limits.
- Switching from one app to another needs to be instantaneous, in under 1 second to launch a new application. Users tend to use only a few apps — for example, they’re watching a video, get a call, and afterward want to keep watching the video. Delays of more than a second can cause users to reject the experience.
- The available API must be sufficient to fulfill Android’s famous philosophy that “all applications are created equal”, both “core” apps and “third-party” ones. This means background music playback, data syncing, GPS navigation, or use of the vibrator are all created without distinction.
The first two requirements are somewhat contradictory. The goal was for the user not to have to worry about closing the app, so it would seem like apps were always running. At the same time, mobile devices have strict limits on memory use, so a system degrades, or even starts failing very quickly, as soon as it needs more RAM. A desktop computer, by contrast, simply slows down its exchange rate with swap space. These considerations were key to Android’s implementation.
The Android Lifecycle
The fundamental mistake when talking about multitasking in Android is not distinguishing between a process and an application. In Android these aren’t tightly bound entities: several applications can share processes, or an application can make use of multiple processes depending on its needs.
The fact that you can see an application’s process “running” doesn’t mean the application is actually doing anything. It may simply be that the operating system has decided this for various reasons, and has decided to keep it around in case it needs it again soon. Likewise, it may leave an application inactive only to return to it a little later, right where it left off.
One of the keys to how Android manages applications is knowing what it does when a process isn’t closed cleanly. When the user exits an application, the process stays in the background, ready to run again if needed (for example, downloading web pages), and can immediately return to the foreground. As long as it doesn’t run out of memory, all processes will remain suspended.
Of course, there’s a limited amount of memory, and the operating system has to decide when to get rid of processes that aren’t needed. This is what’s called “the Android lifecycle“, the set of rules that decide which process should be removed. These rules are based both on the importance of a process, according to the user’s current experience, and on how it’s been used based on past statistics.
Once Android determines it needs to remove a process, it simply kills it. The kernel can immediately reclaim all the resources needed for the new process, without requiring the application to be well written or to respond correctly to an exit command. The operating system takes control so that memory failures don’t occur.
If a user reactivates an application that had been killed, Android has mechanisms to start it up again in the same previous state, in order to preserve the concept that “all apps are running all the time”. This is done by restoring the data visible to the user, the Activities (an activity is the screen we see in the foreground when we run an application). This last state is generated every time the user leaves the app, not when it’s been killed, so the kernel can later freely kill it without depending on the application.
In a way, the process manager can be seen as a form of swap: application processes represent a certain amount of memory in use; when little memory remains, some processes can be killed, and when those processes are needed again, they can be started from their last saved state
Running in the Background
So far, we already know how apps are implicitly used in the background. This is fine for completing downloads, for example, but what happens when we need to explicitly tell the operating system that it should run in the background? Data syncing, GPS, music playback…
The app needs some way to tell Android: ” hey, keep it low-key” . There are two essential mechanisms for background applications, represented by two types of components that can be declared explicitly in its manifest (we’ll talk about that another day): broadcast receivers and services.
BroadcastReceiver
A BroadcastReceiver allows an application to run in the background for a short period of time as a result of something that’s happening. It can be used in many ways to build higher-level facilities: for example, LocationManager can send a message when it detects interesting changes at a given location. Since information about the receiver is part of the application’s manifest (manifest.xml), Android can find and run the application very efficiently, even if it isn’t currently running.
When a “receive” event is handled, the application is given a time quota to finish. If it doesn’t complete within that time, the application is considered to be misbehaving, and its process is moved to suspension.
It’s a way for the app to tell the operating system: “I’ll be done in 10 seconds“.
In short, they’re used when an external stimulus is detected. In any case, they aren’t suitable for long, ongoing transmissions, such as network operations.
Services
A service allows an application to run background operations for longer, in practice. There are actually plenty of other functions that services provide, but here their fundamental purpose is for an application to say “hey, I’m going to be here for a while, I’ll let you know when I’m done” An app controls its service. It says when it runs and when it stops.
Services don’t provide a proper client-server model; their use is optional. When starting an application’s services, Android simply creates an instance of the component within the application’s process to provide its context.
Process management for services is different from BroadcastReceivers, since an unlimited number of services can request to run for an unknown length of time. There may not be enough RAM available to keep the services running, so there’s no guarantee they’ll complete successfully.
If RAM is low, services will be immediately killed, just as background processes are. However, in their case, Android can restart their process later, once more RAM is available.
Informally, you could say services are always acting like a “cry-baby” saying “I don’t want to die”, and in order not to kill it (when RAM is needed) Android wants to see that there’s user interaction — for example, when background music is playing, Android knows the user is aware that process exists thanks to the notification bar where the player can be seen, and therefore it won’t try to kill it.
Looking at it plainly, Android is a software stack for mobile devices that includes an operating system, middleware, and key applications. The Android SDK provides the tools and APIs needed to start developing applications for the Android platform using the Java programming language.
Memory in Android
As we’ve said more than once, Android is a Linux-based operating system running kernel 2.6.x, simplified to handle most tasks. It uses open native C libraries. All the operating system’s basic operations — I/O, memory management, and so on — are handled by the Linux kernel.
Memory management is fairly standard. Just like Java and .NET, Android uses the runtime and the virtual machine to manage the application’s memory. Unlike either of those two frameworks, the Android runtime also manages process lifetimes. Android keeps the app responsive by stopping and killing processes that get in the way of smooth performance, freeing up resources for higher-priority applications.
Every Android application runs in its own independent process, inside its own instance of Dalvik, handing off all responsibility for memory and process management.
Dalvik and the Android runtime sit on top of a Linux kernel that handles low-level hardware interaction, including drivers and memory management, while the API set provides access to all the low-level services, features, and hardware.
The Dalvik Virtual Machine is a register-based virtual machine that has been optimized to ensure a device can run multiple instances efficiently. It relies on the Linux kernel for low-level memory management.
The Dalvik Virtual Machine
One of the key elements of Android is the Dalvik virtual machine. Instead of using a traditional Java virtual machine (VM), such as Java ME (Java Mobile Edition), Android uses its own custom virtual machine designed to make sure multitasking runs efficiently on a single device.
The Dalvik virtual machine uses the underlying Linux kernel to handle low-level functionality, including security, process scheduling, and memory management.
All access to Android’s hardware and system services is managed through Dalvik as a middle layer. By using a virtual machine to organize application execution, developers get an abstraction layer that means they never have to worry about any particular piece of hardware.
The Dalvik virtual machine launches Dalvik executable processes, a format optimized to keep the memory footprint to a minimum. These .dex executables are created by transforming Java-language classes, compiled using the tools provided in the SDK.
Process priority
The order in which processes reclaim resources is determined by the stored priority of the applications. An application’s priority is equal to the highest priority of its components.
When two processes have been running equally long and both have the same priority, the process that has held a lower priority gets killed first. Process priority is also affected by dependencies between processes: if an application depends on a service or content provider supplied by a second application, the second application will have at least as high a priority as the one it’s supporting.
Every Android application stays running and in memory until the system needs its resources for other applications.
It’s important to make sure a process’s priority matches the work it’s doing. If it doesn’t, the process could be killed while it’s in the middle of something important.
Process types, ordered by priority:
– Active Processes
Active, or foreground, processes are the ones the user is actively interacting with. In general there are only a few of these at any given time, and they’re only killed as a last resort.
Active processes include:
-Activities in an “active” state, meaning they’re in the foreground and responding to user events.
-Activities, Services, or receivers currently running an OnReceive event handler.
-Services currently running OnStart, onCreate, or the OnDestroy event handler.
– Visible Processes
As the name suggests, visible activities are visible, but they’re not in the foreground and don’t respond to user events. This happens when an activity is only partially shown (a screen that isn’t full-size or that’s transparent). In general there are very few visible processes, and they’re only killed in extreme circumstances, to let active processes keep running.
As for services, those that support running active processes are given a noticeably lower priority than active processes themselves, since services don’t interact directly with the user.
– Background Processes.
Background processes cover activities that aren’t visible but are still running. In general this won’t be a large number of processes.
– Empty Processes
Empty processes are kept around to improve the system’s overall performance; Android often retains applications in memory even after they’ve reached the end of their lifecycle. This memory cache is kept in order to improve app startup response times.
How to use memory efficiently
Android manages open applications running in the background. This means it closes apps when the system needs more memory, so there’s no need to fall back on secondary storage. However, most Android users aren’t entirely happy with how this is managed, because it sometimes leaves too many processes running, causing the well-known slowdown compared to iOS (iPhone, iPad…). To fix this, we can always use a task killer
