Zygote
The big picturebeginner

Processes and UIDs: an app is a Linux user

The single decision that makes Android's security model work: every app gets its own Linux user identity, its own process, and its own sandbox. Everything else follows from that.

10 minAPI 1–37 · 17interactivequizverified 2026-10-27

On your laptop, you are the user and applications run as you. On Android, you are one user and every app is another. That inversion is the foundation of the platform's security, and it is why an app can be installed from an unknown source without being able to read your messages.

TL;DR#

  • Every installed app gets its own Linux UID, typically shown as u0_a123. Files it creates are owned by that UID, and the kernel refuses other apps access.
  • Every app runs in its own process, forked from Zygote, with its own virtual memory and its own instance of the Android Runtime.
  • Processes are not permanent. The system kills background ones to reclaim memory, and will recreate them later, restoring components as needed.
  • Communication is not shared memory. Apps reach each other and the system through Binder and through explicit permission grants, never by touching each other's files.
  • The model scales beyond apps: a work profile or a second user is just another UID range with its own storage and processes.

Mental model#

Picture an apartment building where the landlord changes the locks for every tenant, and every apartment has its own mailbox, its own electricity meter and its own door key.

The building is the kernel. It owns the walls and decides who can open which door.

Each apartment is an app's process: private space, private stuff, no access to the neighbours.

The apartment number is the UID. The building's records use it to decide everything.

The front desk is the system services. If a tenant needs something from outside the building — a delivery, a repair — they call the desk, which verifies who they are and handles it.

Where the analogy breaks#

In a building, a tenant can choose to leave the door open. On Android, an app cannot grant another app access to its files by accident, because the check is in the kernel, not in the app. And unlike a building, the "apartments" are copies of the same template — every app process starts life as a clone of Zygote. The isolation is real; the independence is not.

The big picture#

Loading diagram…
Text description of this diagram

The Android stack with the process each layer runs in. User apps run in their own sandboxed processes, each forked from Zygote. System apps such as SystemUI and the launcher run in their own processes but with privileged permissions. The framework services run together inside one process called system_server, which is itself forked from Zygote. Native daemons such as SurfaceFlinger, init and the audio server run in their own processes started by init. The kernel runs beneath all of them. The diagram makes clear that process boundaries do not map neatly onto layers: the framework is one process, while apps are many.

Deep dive#

The UID inversion#

On a conventional Unix system, /etc/passwd lists human users, and processes run as one of them. Android keeps the mechanism and changes the meaning: when an app is installed, the package manager allocates a fresh UID from a range reserved for apps. The app's data directory is created and owned by that UID, with permissions that exclude everyone else.

You can see this directly:

Find an app's UID

adb shell pm list packages | head -3 adb shell dumpsys package com.android.chrome | grep userId

The userId=10123 in the output corresponds to the u0_a123 you see in ps. The u0 is the Android user (user zero, the primary user), and the rest identifies the app.

Process lifecycle is a budget, not a promise#

An app process is not a daemon. The system creates it when a component needs it and destroys it when memory is needed. Between those events, the process may host an activity, a service, a broadcast receiver callback, or nothing at all.

The system sorts processes by importance. In rough order:

  1. Foreground — an activity the user is interacting with.
  2. Visible — an activity that is still visible but not focused, such as behind a dialog.
  3. Service — a foreground service, or a bound service in use.
  4. Cached — a process with nothing active, kept around because restarting is expensive.

When memory runs low, the least important processes are killed. That is not a failure mode; it is the design. An app that was killed while in the background is expected to restore itself when the user returns.

Two apps, two processes, one conversation#

If apps cannot read each other's files, how does the share sheet work? Through a content provider: an app exposes a URI, the receiving app asks the ContentResolver for it, and the framework checks that a permission or a one-time URI grant exists. The receiving app never sees a file path.

The same applies to everything else: intents start components, Binder calls reach services, and permissions gate each step. The isolation is what makes those mechanisms meaningful — without it, any app could just read the data directly.

The process tree#

Boot produces a specific tree, and every app hangs off it:

init (PID 1)
├── servicemanager
├── surfaceflinger
├── audioserver
├── zygote64
│   ├── system_server
│   ├── com.android.systemui
│   └── com.example.yourapp
└── … other native daemons

The fact that system_server and every app share the same parent is not a detail. Zygote is the mechanism by which the framework's classes and resources are loaded once and shared, and it is the subject of a whole page in the runtime module.

Multi-user, work profiles and private space#

The UID model generalises. Android can host several users, each with its own UID range, its own storage and its own running apps. A work profile is a managed secondary user; Private Space is a hidden, separately locked profile introduced in Android 15. Because they are profiles rather than special cases, the same kernel rules separate them.

How it evolved#

The UID-per-app model has been there since Android 1.0. What changed is how much the system layers on top of it.

2008–2012UID onlyKernel file permissions and install-time permissions were the whole story.API 1
2012–2014SELinuxMandatory access control adds a second boundary around every process.API 17
2015 →Runtime permissionsThe user, not the installer, decides what an app can reach.API 23
2018 →AppOps and privacyFine-grained operation control on top of manifest permissions.API 28
2024 →Profiles as productsWork profiles, Private Space and per-app language make profiles a user feature.API 35

Under the hood#

Curiosity corner#

  • The u0_a123 naming is not arbitrary. u0 is user zero, a marks an application UID, and the number is the app's slot in that range. A work profile app shows u10_a… instead.
  • Killing apps from recents removes useful cache. The system already keeps those processes for a reason: restarting is expensive.

Try it#

See the tree for yourself

adb shell ps -A -o USER,PID,PPID,NAME | head -20

Example output
USER      PID   PPID  NAME
root        1      0  init
system    863    610  system_server
u0_a123  4821    610  com.example.app

Find the PPID of your own app and of system_server. They are the same process.

Check yourself#

Check yourself

1 / 12

You run `adb shell ps -A` and see that `system_server` and your app share the same parent process. What is the parent?

0 answered · 0 correct

Sources#

Sources

Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.

  1. docsApplication sandboxsource.android.comaccessed 2026-10-27↩
  2. docsSELinuxsource.android.comaccessed 2026-10-27↩
  3. docsProcesses and app lifecycledeveloper.android.comaccessed 2026-10-27↩
  4. docsZygotesource.android.comaccessed 2026-10-27↩
  5. docsAndroid platform architecture (AOSP)source.android.comaccessed 2026-10-27↩