Mobile Core Concepts
Platform Knowledge
Activity / Fragment / Service lifecycles, Application + ProcessLifecycleOwner, the ART runtime, and the OS-level constraints (process model, Doze, App Standby) every modern Android API assumes you understand.
Almost every interview question about Android assumes a shared vocabulary: what an Activity is, what survives a configuration change vs a process kill, when the OS will throw your work overboard, and how the bytecode you ship actually runs on the device. This page covers that shared vocabulary - lifecycles, the process model, the ART runtime, and the OS-level background restrictions (Doze, App Standby buckets) that shape every design from the data layer up. Background scheduling itself (WorkManager / FGS / JobScheduler) lives in its own Core Concept; this entry just frames why those constraints exist.
The Android process model
An app on Android is a process, but the lifetime of that process is owned by the system, not by the app. The user "closing" an app by swiping it away does not necessarily end the process; conversely, the OS can kill a backgrounded process at any moment to reclaim RAM. State that has to survive must be explicitly persisted; state that lives only in memory will eventually disappear.
Five process states determine kill priority:
| State | Triggered by | OS behavior |
|---|---|---|
| Foreground | UI visible, or running foreground service | Won't be killed except in extreme low-memory |
| Visible | Activity partially visible (over a dialog) | Killed only if foreground processes need RAM |
| Service | Background service running | Killed under moderate memory pressure |
| Cached (visible recently) | Backgrounded, kept warm for resume | First to be killed |
| Empty / unused | No active components | Killed freely |
The OS doesn't tell the app it's about to be killed - it just sends SIGKILL. Anything not persisted is gone. This is why SavedStateHandle exists, why Room over in-memory caches is the default, and why "process death" is a routinely-tested scenario in interview questions.
Activity, Fragment, and Service lifecycles
The three components every Android engineer should be able to draw cold:
Activity
onPause is the contract for "stop interacting with the user" - it must return quickly because the next activity can't start until it does. onStop is "no longer visible" - safer place for heavier teardown. onDestroy is not guaranteed to be called (process kill skips it), so it's not a reliable cleanup hook.
Configuration changes (rotation, locale, dark mode toggle, font scale) destroy and recreate the Activity by default. Anything held in the Activity is rebuilt; ViewModel is the canonical home for state that should survive this.
Fragment
Same shape plus a view lifecycle that runs independently of the Fragment instance lifecycle. The Fragment can outlive its view (onDestroyView without onDestroy) when on the back stack. The footgun: holding view references in Fragment fields leaks the view when it's destroyed but the Fragment isn't.
Use viewLifecycleOwner (not this) when collecting Flows in a Fragment - otherwise the collection outlives the view and crashes on re-creation.
Service
A Service is a component without UI that can run on the main thread (for IPC) or spawn its own threads (rare today - prefer WorkManager). Bound, started, and foreground services have different lifetime rules:
| Type | Lifetime | Use for |
|---|---|---|
| Bound | While at least one client is bound | IPC with another process |
| Started | Until stopSelf() or stopService() | Mostly obsolete; use WorkManager |
| Foreground | While the notification is visible | User-perceived ongoing work (music, navigation, upload) |
On Android 14+, foreground services must declare a type (dataSync, mediaPlayback, location, phoneCall, etc.). Mismatched types are a runtime crash.
Application + ProcessLifecycleOwner
Application is the singleton that owns the process. onCreate() runs once per process - including after process death and re-creation. Don't put initialization here that depends on user state; that gets re-run on every cold start. Heavy synchronous init here blocks the first frame on every launch, so defer it with the App Startup library or a coroutine that doesn't gate first frame.
ProcessLifecycleOwner exposes a Lifecycle for the whole process, not any single Activity. Its ON_START fires when the app enters foreground (any Activity in any task becomes visible); ON_STOP fires when the last Activity goes away. This is the right hook for "user backgrounded the app" logic - a single Activity's onStop fires too often (it also fires when navigating between screens in the same app).
ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) { analytics.appForegrounded() }
override fun onStop(owner: LifecycleOwner) { analytics.appBackgrounded() }
})Sources: Application lifecycle · ProcessLifecycleOwner
ART, AOT, and the bytecode runtime
Your app ships as dex bytecode. ART (Android Runtime, since Android 5; previously Dalvik) is the VM that runs it. The interesting question is how it runs it, and the answer has changed every couple of Android releases.
The current model (Android 9+) layers three sources of compiled output:
| Layer | Source | When applied |
|---|---|---|
Baseline Profile (baseline-prof.txt) | You ship it in the APK / AAB | At install - immediate |
| Play cloud profile | Aggregated from real users by Play | At install - grows with the user base |
| Device JIT profile | ART records hot methods at runtime | Applied during background dexopt (idle + charging) |
Profiled methods are AOT-compiled to native; unprofiled methods run interpreted until JIT or the next dexopt window. Compiled output (/data/app/<package>/oat/arm64/base.odex) is device-specific - APK-shipped AOT is impossible because the binary is CPU-arch + OS-version specific.
The practical interview point: shipping a Baseline Profile collapses "first launch is slow until ART learns the app" from a multi-day window to launch 1. App-size and delivery covers this in more depth; what to know here is that it exists and what problem it solves.
Sources: Baseline Profiles · ART overview
Doze, App Standby, and background limits
The OS aggressively restricts what backgrounded apps can do. Three overlapping systems, all interview-relevant:
Doze (Android 6+) - when the device is idle (stationary, screen off, unplugged), the OS defers network access, wakelocks, alarms, and jobs. Brief "maintenance windows" let queued work run periodically. The deeper the doze, the longer between maintenance windows.
App Standby buckets (Android 9+) - per-app, based on recent usage. An app the user opens daily lives in Working Set; one opened monthly lands in Rare. Bucket determines how often a backgrounded app's jobs can run. The four standard buckets (Active, Working Set, Frequent, Rare) arrived with App Standby in Android 9 (API 28); the Restricted bucket was added later, in Android 11 (API 30).
| Bucket | Network / job frequency | When |
|---|---|---|
| Active | Unrestricted | App in foreground recently |
| Working Set | Frequent | Daily use |
| Frequent | Less frequent | Weekly use |
| Rare | Minimal | Monthly use |
| Restricted (API 30+) | Heavy restriction | Flagged or never-used |
Foreground service exemption - a foreground service (with its required visible notification) is exempt from Doze for its duration. This is why long uploads and ongoing media playback wrap themselves in an FGS.
You should almost never implement Doze handling yourself. WorkManager picks the right scheduling layer (JobScheduler / AlarmManager / FGS) and respects the OS constraints automatically. The Background work + scheduling Core Concept covers WorkManager's contract; this entry just clarifies why those constraints exist.
Sources: Power management restrictions
See also
- Concurrency - lifecycle-scoped coroutines (
viewModelScope,lifecycleScope,ProcessLifecycleOwner's scope) are the canonical glue between this entry and async work. - Background Work and Scheduling - WorkManager is what you actually call to schedule work that respects Doze and App Standby.
- State Management -
SavedStateHandleis the only ViewModel-adjacent thing that survives process kill. - App Size and Delivery - Baseline Profiles and their delivery via AAB.
Used in
- Crash Reporter SDK - the JVM uncaught-exception handler runs synchronously on a dying process; the breadcrumb buffer and report writer must be safe under abrupt termination. The next-launch upload depends on
Application.onCreaterunning before the worker fires. - Photo Gallery App -
UploadWorkerwithsetForeground()is the FGS-exemption pattern in practice; the upload survives Doze + swipe-away because the OS treats it as user-visible work. - Messenger App - WebSocket reconnection on app foregrounding hooks
ProcessLifecycleOwner.ON_START, not Activity-level callbacks. - Ride-Sharing App - foreground service of type
locationis the only legal way to keep GPS active in background since Android 10. - Newsfeed App -
ContentObserverregistration isApplication-scoped so it survives backgrounding; per the Photo Gallery cross-link, that's the same pattern as device-photo detection.
Done reading? Mark it so it sticks in your dashboard.