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.
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#
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:
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:
- Foreground — an activity the user is interacting with.
- Visible — an activity that is still visible but not focused, such as behind a dialog.
- Service — a foreground service, or a bound service in use.
- 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 daemonsThe 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.
Under the hood#
- Process.javathe constants for UIDs and process priorities
- ZygoteInit.javawhere app processes are forked
- ProcessList.javahow processes are created, tracked and killed
Curiosity corner#
- The
u0_a123naming is not arbitrary.u0is user zero,amarks an application UID, and the number is the app's slot in that range. A work profile app showsu10_a…instead. - Killing apps from recents removes useful cache. The system already keeps those processes for a reason: restarting is expensive.
Try it#
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.appFind the PPID of your own app and of system_server. They are the same process.
Check yourself#
Check yourself
You run `adb shell ps -A` and see that `system_server` and your app share the same parent process. What is the parent?
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.
- docsApplication sandboxsource.android.comaccessed 2026-10-27↩
- docsSELinuxsource.android.comaccessed 2026-10-27↩
- docsProcesses and app lifecycledeveloper.android.comaccessed 2026-10-27↩
- docsZygotesource.android.comaccessed 2026-10-27↩
- docsAndroid platform architecture (AOSP)source.android.comaccessed 2026-10-27↩