Android is a system that can run more than one application at the same time. When you play games, you may get an email notification or a DM on Twitter or your apps might be updating in the background. To enable this, memory and CPU time need to be divided among multiple applications.

Users

Linux assigns every user a unique ID. The user has access to resources based on its permissions. These are private to each user and cannot be accessed by any other user, except for root.

In Android, every application is given a unique user ID which is assigned to it when the application is installed on the device, and remains constant for the duration of its life on that device. This ID corresponds to a unique Linux user that cannot access another application’s resources. So every application runs in a sandbox or a logical container called a process.

Processes

At the kernel’s level of abstraction, processes are referred to as tasks. Each task is a large data structure, a struct called task_struct. This is also called a process descriptor and contains all the information about a specific process.

  • Open files
  • Pending signals
  • Address space
  • State
  • Threads of execution

The kernel stores the list of processes in a circular doubly linked list called the task list.

In Android, this translates to a process being a logical container that contains all the information about the app that runs within it like its name, the PID, priority, address space and so on.

Permissions

Permissions are enforced at the process level. Usually, no two apps can run in the same process since they need to run as different Linux users.

The way to achieve this is to use the sharedUserId attribute in the <manifest> tag of AndroidManifest.xml. Any apps that want to run in the same process should have the same user ID. https://developer.android.com/guide/topics/manifest/manifest-element#uid

For example:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="dev.voidmain.demo"
          android:sharedUserId="void_main_demo"
          android:sharedUserLabel="@string/user_label" >
		  ...
</manifest>

In the snippet above, we have added sharedUserId and sharedUserLabel to the <manifest> tag. Any other apps that define the same attributes will be treated as being the same application, with the same user ID and file permissions.

Note: sharedUserId is a string, while sharedUserLabel is a string resource that is just a user-readable label for the shared user ID.

A sharedUserId is useful for sharing data, processes, and other things. For example, if you’re a project management company that wants to build an app for managing projects and another for managing contacts, and need to share OAuth authentication. Or if you want to share user information between them using AccountManager like Facebook does with Messenger.

Note: Upgrading an app by adding a sharedUserId is not allowed by the Android operating system. Adding it will make the applications data in /data/data/<package_name>/ inaccessible.

Application Lifecycle

The application lifecycle is encapsulated within a Linux process, which, in Java, maps to the Application class. The Application object for each app starts when the runtime calls its onCreate() method. Ideally, the app terminates with a call from the runtime to its onTerminate(), but an application cannot rely upon this. The underlying Linux process may get killed before the runtime gets a chance to call onTerminate().

class ExampleApplication: Application() {
	override fun onCreate(...) {
		// called first by runtime
	}

  	override fun onTerminate(...) {
		// called last by runtime, but not guaranteed  
  	}
}

The Application object is the first component to be instantiated in a process and the last to be destroyed.

Application Start

An application is started when one of its components is initiated for execution. Any component can be the entry point for the application, and once the first component is triggered to start, a Linux process is started, unless it is already running, leading to the following startup sequence:

  • Start Linux process.
  • Create runtime.
  • Create Application instance.
  • Create the entry point component for the application.

Setting up a new Linux process and the runtime is not an instantaneous operation. It can degrade performance and have a noticeable impact on the user experience. Thus, the system tries to shorten the startup time for applications by using a special process called Zygote.

When your phone boots up, a process called Zygote is started by the system and it remains running. As soon an app is started by the user or after booting up, Android calls fork() on Zygote, which creates a copy of it. At the kernel level, the task is cloned().

Each new forked process gets:

  • A copy of the VM (Dalvik or ART), shared among all app processes via the copy-on-write memory.
  • A copy of the Android framework classes, also shared via the copy-on-write memory.
  • A copy of the classes loaded from the APK.

Application Termination

A process is created at the start of the application and finishes when the system wants to free up resources. Because a user may request an application at any later time, the runtime avoids destroying all its resources until the number of live applications leads to an actual shortage of resources across the system. Hence, an application isn’t automatically terminated even when all of its components have been destroyed.

When the system is low on resources, it’s up to the runtime to decide which process should be killed. To make this decision, the system imposes a ranking on each process depending on the application’s visibility and the components that are currently executing.

In the following ranking, the bottom-ranked processes are forced to quit before the higher-ranked ones. With the highest first, the process ranks are:

  • Foreground: Application has a visible component in front, Service is bound to an Activity in front in a remote process, or BroadcastReceiver is running.
  • Visible: Application has a visible component but is partly obscured.
  • Service: Service is executing in the background and is not tied to a visible component.
  • Background: A non-visible Activity. This is the process level that contains most applications.
  • Empty: A process without active components. Empty processes are kept around to improve startup times, but they are the first to be terminated when the system reclaims resources. In practice, the ranking system ensures that no visible applications will be terminated by the platform when it runs out of resources.

Kernel

Kernel is the core program which manages CPU resources, system memory, devices, including the file systems and networking, and is responsible for managing all the processes.

When an app is started, it is the kernel which loads the app into memory, creates the processes needed and starts the app. When an app needs memory, it is the kernel that allocates it. When an app wants networking, it is the kernel which does all the low level processing. When the app wants to perform a task in the background, it is the kernel which handles the background threads. When the app closes it is the kernel which cleans up all the memory and other resources that were used by the app.

TL;DR

The kernel handles multi-tasking, while the processes handle execution of the apps.


Hope you found this helpful. Follow me on Twitter for updates. Unit next time!