The short answerAndroid runs your app on its own terms: no main() of yours. It installs the package under a Linux user of its own. It starts a process when you tap, drives each screen, and kills the process when memory runs short.
Nobody calls your main()
Every app carries a manifest, the list of its parts. Android reads it, and calls each part when it needs it. What is not declared does not exist.
AndroidManifest.xml as merged into the APK
<manifest >
<uses-sdk />
<uses-permission />
<application>
<activity android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category />
</intent-filter>
</activity>
</application>
</manifest>
The manifest
Press a highlight, or a note.
activity · LAUNCHER
Your entry point: tapping the icon opens this screen. No main() of yours runs.
package
The app's identity, on the phone and in the store; in your project, the applicationId in app/build.gradle.kts. Two apps cannot share it.
targetSdkVersion
The Android version the app targets; in your project, targetSdk in app/build.gradle.kts. It decides which of the platform's newer rules apply.
uses-permission
What the app may ask for. What is not declared cannot be asked for later.
Read a manifest, and you know what an app can do before reading its code.
Go deeper Components, and the manifest in the APK 1 min
The parts are called components, and there are four kinds. Activities are screens. Services are work with no screen. Broadcast receivers react to system events, and can also be registered from code while the app runs. Content providers offer data to other apps.
The manifest in your project is not the one in the APK. The build merges it with your libraries' manifests, and writes package from the applicationId in app/build.gradle.kts. Android Studio's APK Analyzer shows the merged one.
The targeted version decides which of the platform's newer behaviour changes apply to the app; a few apply to every app. Google Play requires a recent one: API 36 for new apps and updates since 31 August 2026.
Strictly, every app process does start in a main(): the framework's ActivityThread.main(), which loops on the main thread and calls your code. None of it is yours.
Installing creates a user for your app
An app travels as an APK, a zip with a fixed layout. At install, Android creates a Linux user for it, and a folder only that user can read: the sandbox.
Notes.apkholdsAndroidManifest.xml,classes.dex,res/,assets/andlib/.- At install, Notes gets the Linux user
u0_a231and the folder/data/data/com.example.notes/. - Notes reads the bank's folder, owned by
u0_a198: Permission denied.
No app reads another's files. Apps talk through the system, or not at all.
Go deeper The numbers, the bundle, SELinux 1 min
u0_a160 reads: user 0 of the phone, app 160, Linux user ID 10160. The package manager records it at install: for a fresh Expo app, dumpsys package showed appId=10217, and ps showed u0_a217.
On Google Play you upload an app bundle, an AAB. From it, Play builds APKs for each phone: a base APK and configuration APKs that hold only what that phone needs. The phone still installs APKs.
The sandbox is the Linux kernel's ordinary user and file permissions. Since Android 5, SELinux rules that the app cannot change reinforce it.
A fresh Expo app's APK holds 1,146 files: two .dex files, lib/ for four processor types, and assets/index.android.bundle, your JavaScript compiled to Hermes bytecode. Rename any .apk to .zip and open it.
Checkpoint
Your notes app stores a draft in its private folder. Another app wants to read it. What happens?
See the answer
The folder belongs to one Linux user, and only that user and the system can open it. A storage permission covers shared storage, such as photos and downloads, never another app's private folder. Sharing goes through the system: a copy, or a temporary grant to one file.
The system kills without warning
When the user leaves, the process stays cached, so coming back is instant. When memory runs short, Android kills cached processes first. The app is not asked, and is not told.
Foreground → visible → service → cached. The lowest rank is killed first, and no callback runs in a killed process.
Next time, a new process starts. It gets back only what the app saved.
Go deeper zygote, the freezer, the ranking 1 min
Every app process is forked from zygote, a process started at boot with the framework already loaded. That is the picture at the top of this page; step 1 of Try it shows it on your own phone.
Since Android 14, on phones that support it, a cached process is also frozen about ten seconds after it becomes cached: it keeps its memory but gets no processor time.
The system prints its own ranking with adb shell dumpsys activity lru. Here, the launcher is in front, Chrome was just left, and a demo app waits in the cache:
You should see #59: fg TOP LCMNFUAT 1097:com.google.android.apps.nexuslauncher/u0a184 act:activities|recents
#58: prev LAST -------- 3167:com.android.chrome/u0a160 act:activities|recents
#56: cch CAC -------- 6607:com.tmimiexplains.flutterdemo/u0a218 act:activities|recents
Rotating the phone destroys the screen
The screen you see is an activity. The system creates it, shows it and takes it away, calling your code at each step.
- onCreatecreated; set up the screen
- onStartbecoming visible
- onResumein the foreground, interactive
- onPausepartly hidden, by another activity on top; keep it brief
- onStopno longer visible: save here
- onDestroyfinished, or rotated; skipped when the process is killed
Leave and come back: down to onStop, back up through onStart. Rotate: all the way down, then a new activity starts at onCreate. Killed: the chain is cut, and onDestroy never runs.
Save what matters by the time the activity stops. Rebuild from it in onCreate.
Go deeper onPause, saved state, and the frameworks 1 min
onPause must stay brief. It runs when another activity covers yours, even a translucent one such as the permission dialog. A dialog your own app draws does not pause it. Since Android 10, apps side by side in split screen all stay resumed.
Save by onStop at the latest, and use onSaveInstanceState for the screen's small state; in an app that targets Android 9 or later, it runs after onStop. A ViewModel survives a rotation, but not the death of the process.
Expo, Flutter and Ionic templates declare configChanges: their activity handles a rotation itself and is not recreated. The Expo template also locks the app in portrait. For them, the kill is the test that matters.
Jetpack Compose builds screens differently, but it stands on the same activity and the same lifecycle. Since Android 12, Back on an app's first screen moves it to the background instead of destroying it.
Three more rules, three later capsules
- 08
Background
An app the user left cannot keep ordinary work running. What must continue is declared.
- 09
Permissions
Declared, then asked at runtime. The user can say no, and the app must still work.
- 10
Signature
Every APK is signed, with no authority involved. An update must carry the same key.
Keep the last one for the next capsule: on iOS, Apple must issue the certificate.
Go deeper The three rules, in detail 2 min
The background. What must continue after the user leaves, a download, a recording, a trip being tracked, is a foreground service with a notification the user can see, or a job scheduled through WorkManager that the system runs when it allows. "It worked while I was testing" is the usual symptom: the test keeps the app in front, the user does not.
Permissions. Declared in the manifest; the sensitive ones, such as camera, microphone, location and contacts, are asked at runtime through a dialog the system shows. The user can refuse, or revoke them later in Settings, and Android resets them by itself for an app left unused for a few months.
The signature. The certificate is self-signed, from a key the developer generates; Android Studio even makes a debug key for you. It says who the app comes from, not that it is safe. On Google Play, Google keeps the app's signing key and signs the APKs it delivers: that is Play App Signing. You sign what you upload with a separate upload key. A new key can replace the old one only if the old key vouches for it: key rotation, since Android 9.
A phone also installs APKs from outside the store when the user allows it. Google is tying installs on certified devices to developers it has verified: since 30 September 2026 for apps installed from Google Play and the main phone makers' stores in Brazil, Indonesia, Singapore and Thailand, and for all apps, everywhere, from 2027.
A free limited distribution account lets a student share an app with up to 20 devices without an ID check. An advanced flow still lets a determined user install an unverified app, and installing from your own computer with adb does not change.
With knowledge · in class
Ask for an app that survives the system
An assistant gives you an app that runs on the first try, in front of you, phone upright. The kill, the rotation and the refused permission happen later, in a user's hands.
Paste first, whatever the family
This app runs on Android: the system may pause it, rotate it or kill it at any time. Save state so that a new process can rebuild the screen, and tell me where each piece of state is saved.
All families
Where the app's contract lives
- Native:
AndroidManifest.xmlandapp/build.gradle.kts. - Expo:
app.json, until a build writes the manifest for you. In Expo Go, the package name and the process are Expo Go's. - Ionic:
android/app/src/main/AndroidManifest.xml, afternpx cap add android. - PWA: none of this. Its web manifest names the app for the browser, not for Android.
Choose your family above: its prompt and its checks.
Native
Say it in the prompt
- State:
the screen must survive rotation and process death: persist the draft as the user types and restore it on launchExpect a ViewModel with SavedStateHandle for the screen, and DataStore or Room for the draft itself. - Background:
the upload must continue if the user leaves the app: use WorkManager, not a thread in the activity
Check the project
"Don't keep activities" destroys each activity as soon as you leave it, but keeps the process, so static fields survive. For the real thing, kill the process: step 3 of Try it.
Expo
Say it in the prompt
- State:
the draft must survive the app being killed in the background: save it with AsyncStorage as the user types, and restore it on launchAsyncStorage runs in Expo Go; MMKV needs a development build. - Background:
the upload must continue if the user leaves the app; tell me which system mechanism you used, and why it survives the app leaving the screen
Check the project
Ionic
Say it in the prompt
- State:
save the draft with Capacitor Preferences as the user types, not in localStorage, and restore it on launchThe OS can reclaim a WebView's localStorage when the phone runs low on space. - Background:
the upload must continue if the user leaves the app; tell me which system mechanism you used, and why it survives the app leaving the screen
Check the project
PWA
Say it in the prompt
- State:
save the draft in IndexedDB as the user types, and restore it when the page loads - Background:
the upload must continue if the user leaves the app; tell me which system mechanism you used, and why it survives the app leaving the screen
Check the project
Try it on your phone · 10 minutes
See the sandbox and the kill on a real phone
Before class Set up adb once 5 min
Turn on Developer options, then USB debugging. Install the Android SDK Platform-Tools: adb devices must list your phone. Since Android 11, Wireless debugging works without a cable.
Copy the commands as they are. The quotes make the filter run on the phone, so the same lines work from Windows, macOS and Linux.
No cable? Settings → Apps → any app → Permissions shows what it asked for and what you granted.
-
Who runs Chrome. Open Chrome, press Home, then list its processes.
adb shell "ps -A -o USER,PID,PPID,NAME | grep -e USER -e zygote64 -e com.android.chrome"You should seeUSER PID PPID NAME root 489 1 zygote64 u0_a160 7531 489 com.android.chrome_zygote u0_a160 7975 489 com.android.chrome u0_i17 8017 7531 com.android.chrome:sandboxed_process0:… u0_a160 8043 489 com.android.chrome:privileged_process0This proves Chrome runs as its own Linux user, and zygote, PID 489, forked it.
-
Its private folder. Ask the shell to list it.
adb shell ls /data/data/com.android.chromeYou should seels: /data/data/com.android.chrome: Permission deniedThis proves The shell's user is not Chrome's user, and the kernel refuses.
-
Leave, kill, come back. Three moves, a command after each.
Open Chrome, then clear the log.
adb logcat -b events -cPress Home, then kill Chrome.
adb shell am kill com.android.chromeReopen Chrome, then read the log.
adb logcat -b events -d -v brief -s wm_on_paused_called wm_on_stop_called wm_on_destroy_called am_kill am_proc_start wm_on_create_called
You should seeI/wm_on_paused_called( 7975): [221999946,…ChromeTabbedActivity,performPause,8] I/wm_on_stop_called( 7975): [221999946,…ChromeTabbedActivity,STOP_ACTIVITY_ITEM,23] I/am_kill ( 697): [0,7975,com.android.chrome,700,kill background,237640] I/am_proc_start( 697): [0,8558,10160,com.android.chrome,next-top-activity,{…}] I/wm_on_create_called( 8558): [221999946,…ChromeTabbedActivity,performCreate,228]This proves Home ran onPause and onStop. The kill ran nothing, not even onDestroy.
-
Rotate. Turn on auto-rotate and open the Clock app.
Clear the log.
adb logcat -b events -cTurn the phone once, then read the log.
adb logcat -b events -d -v brief -s wm_on_paused_called wm_on_stop_called wm_on_destroy_called wm_on_create_called
You should seeI/wm_on_paused_called( 8215): [206975589,com.android.deskclock.DeskClock,performPause,0] I/wm_on_stop_called( 8215): [206975589,com.android.deskclock.DeskClock,handleRelaunchActivity,1] I/wm_on_destroy_called( 8215): [206975589,com.android.deskclock.DeskClock,performDestroy,6] I/wm_on_create_called( 8215): [206975589,com.android.deskclock.DeskClock,performCreate,85]This proves A rotation destroys the activity and creates a new one, in the same process.
If Clock prints nothing or does not turn, run a new Android Studio project instead: by default, its activity is recreated.
Go deeper Notes on the four steps 1 min
Step 1. The u0_i lines are isolated helpers: users with no permissions at all. Chrome's PID, here 7975, comes back in step 3.
Step 2. On your own debug build, adb shell run-as followed by your applicationId and ls -la works: it becomes the app's user. A debug demo app's folder:
You should seedrwx------ 7 u0_a216 u0_a216 4096 2026-10-13 19:53 .
Step 3. Reopening started process 8558 at onCreate, and Chrome rebuilt its tab from what it had saved. For your own app, kill its applicationId.
Step 4. Try Chrome: nothing appears. It handles rotation itself, as the Expo and Ionic templates do.
Real outputs, captured on 14 October 2026 on an Android 16 emulator: Pixel 7 profile, API 36, Google APIs image, no root. Logs trimmed to the app's own lines; … marks a shortened name. Your PIDs and user numbers will differ; the order will not.
Quick check
From capsule 01
Layout bounds are on. Your Expo app shows a box around every button. Who draws those buttons?
See the answer
Each outlined button is a native view, which Android draws because React Native asked for it. Hermes runs the JavaScript but draws nothing, and a WebView would show one box.
The user taps your app's icon. What tells Android which screen to open?
See the answer
Android reads the manifest and starts the activity declared with the LAUNCHER category: no main() of yours runs. Your source folders mean nothing to the phone; what is not declared does not exist.
An assistant's code asks for the camera at runtime, but AndroidManifest.xml declares no CAMERA permission. What happens?
See the answer
A permission must be declared in the manifest before it can be asked for at runtime; without it, no dialog appears and the request comes back denied. Nothing adds it at install: the merged manifest is fixed when the APK is built.
With a cable, you run adb shell ls /data/data/com.android.chrome. What do you see?
See the answer
The folder belongs to Chrome's Linux user and the shell runs as another one, so the kernel refuses. Owning the phone does not open the sandbox; run-as does it only for your own debug build.
The user rotates the phone while a form is half filled. By default, what does Android do with the activity?
See the answer
A rotation is a configuration change: by default, Android destroys the activity and creates a new one, in the same process. Saved instance state and a ViewModel bring the half-filled form back.
A React Native app has been in the background for an hour. The user comes back and it starts from its first screen. What happened?
See the answer
A cached process is killed when memory is needed, and no code of the app runs, not even onDestroy. Nothing crashed: the screens lived only in the JavaScript's memory, so the app starts over.
Which line should you give the assistant so that the draft survives what Android does to the app?
See the answer
Android may kill the process with no callback, so the draft must already be saved when it happens. onDestroy is skipped when the process is killed, and an app the user left cannot simply keep running.
An assistant uploads a file from a thread started in the activity. In every one of your tests, it finishes. Why can it fail for a user?
See the answer
Your tests keep the app in front, the user does not: once the app is cached, the system can kill the process, thread included. What must continue is declared, a foreground service or WorkManager: capsule 08.
Your PWA saves a draft. On Android, which process do you kill to test that the draft survives?
See the answer
An installed PWA still runs inside Chrome, with Chrome's process, user and sandbox: kill com.android.chrome, then reopen. Its web manifest names the app for the browser, not a process for Android.
A classmate installed your lab APK from a file. You lost your key, and sign the next version with a new one. What happens when they install it?
See the answer
An update must carry the same key, and no authority can vouch for a new one: your classmate must uninstall first, data included. Key rotation, since Android 9, works only if the old key vouches for the new one.
Recap
- No main() of yours. The system calls the components the manifest declares.
- A user per app. Installed under its own Linux user: that is the sandbox.
- Killed without a word. Cached processes go first, and no callback runs.
- Rotation destroys the screen. Save by onStop at the latest; restore in onCreate.
- Still the system's call. Background work, permissions and the signature: capsules 08 to 10.
Sources
- Android Developers: Application fundamentals, the APK, the Linux user per app, the components, the manifest; AOSP: Application sandbox, the kernel permissions and SELinux.
- The activity lifecycle, Processes and app lifecycle, the importance ranking and the kill, and Overview of memory management, zygote and the cache.
- Handle configuration changes, why rotation recreates the activity; Save UI states, what survives what; Multi-window support, every visible activity resumed since Android 10; Android 12 behavior changes, Back on an app's first screen.
- AOSP: Cached apps freezer, the freeze ten seconds after a process becomes cached.
- Permissions on Android, install-time and runtime permissions; App hibernation, the automatic reset; Target API level requirements for Google Play, API 36 since 31 August 2026; Background execution limits, and Background tasks.
- Sign your app, Play App Signing and the upload key; AOSP: Application signing, self-signed certificates, no central authority, and key rotation; Android developer verification, the 2026–2027 timeline, and its guides, the stores covered in 2026, limited distribution and the advanced flow.
- Expo: app config,
android.permissionsandandroid.blockedPermissions; Capacitor: Storage, why Preferences rather than localStorage.