App processes and startup — overview
The most important rule
Android owns your application process. It creates the process when an app component needs to run and may remove a background process when the device needs resources.
Your application therefore has no permanent, app-controlled main() process. An Activity, Service, Receiver, or Provider can be the reason Android starts it.
Installed application ≠ running process
The app can be installed for months while its process is created and killed many times.
What a process contains
A running app process contains:
- its own virtual address space and managed heap;
- ART runtime state;
- a main thread and any worker threads;
- loaded application and library classes;
- static fields, Kotlin
objectinstances, and dependency graph objects; - live Android component instances.
When the process dies, all of those in-memory objects disappear together.
Process lifetime and component lifetime differ
Process created
├─ Application created
├─ Activity A created
├─ Activity B created
├─ Activity A destroyed
└─ process later killed
Destroying one Activity does not imply process death. Process death destroys every object in the process, even if a final callback such as onDestroy() was never delivered.
Startup states
Android’s startup documentation distinguishes three useful states:
| State | What is already available? | Work typically required |
|---|---|---|
| Cold | No app process | Create process, Application, Activity, UI, first frame |
| Warm | Some process/task state may exist, Activity needs creation | Recreate Activity and UI |
| Hot | Process and Activity remain | Bring existing Activity forward |
These are performance categories, not guarantees about your exact object graph. Optimize and test cold start because it includes the most work.
Simplified cold-start path
User taps icon / notification / deep link
↓
System resolves component and task
↓
App process requested from Zygote
↓
Main thread and runtime prepared
↓
Application object created
↓
App ContentProviders initialized
↓
Entry Activity created
↓
First frame drawn (TTID)
↓
Critical content becomes usable (TTFD)
This ordering explains why an SDK initialized by a ContentProvider can delay startup even when you never called it from Application.onCreate().
TTID and TTFD
- Time to initial display (TTID): time until the first frame appears.
- Time to full display (TTFD): time until the experience is meaningfully ready for interaction.
A fast splash/empty frame can improve TTID while data remains unusable for seconds. Measure both. Product quality is not achieved by gaming the first-frame metric.
Real scenario: notification tap after process death
The user receives an order notification, leaves the phone overnight, and taps it in the morning. The process no longer exists.
❌ Fragile design
object SelectedOrder {
lateinit var order: Order
}
class OrderActivity : AppCompatActivity() {
override fun onCreate(state: Bundle?) {
super.onCreate(state)
render(SelectedOrder.order)
}
}
The singleton is a new empty instance in the new process. Accessing order crashes.
✅ Process-safe entry point
class OrderActivity : AppCompatActivity() {
override fun onCreate(state: Bundle?) {
super.onCreate(state)
val orderId = intent.getStringExtra("orderId")
?.takeIf { it.matches(Regex("ORD-[0-9]+")) }
?: return finish()
viewModel.load(orderId)
}
}
The Intent carries a small stable identifier. The ViewModel asks a repository to reload durable truth.
What belongs where?
| State | Example | Appropriate home |
|---|---|---|
| Ephemeral UI | Expanded card, selected tab | rememberSaveable/SavedStateHandle when restoration matters |
| Screen state | Loading/error/content derived for UI | ViewModel, reconstructed from saved/durable inputs |
| Durable local business data | Draft order, downloaded messages | Room/DataStore/file according to structure |
| Server authority | Payment result, inventory | Backend plus local synchronized representation |
| Process cache | Decoded image, computed lookup | Memory cache; correctness cannot depend on survival |
Multiple processes
Manifest android:process can place a component in another process. That process has another heap, Application initialization, singleton set, threads, and failure lifetime.
❌ Incorrect assumption
object LoginSession { var token: String? = null }
Two processes have two LoginSession objects.
✅ Architectural rule
Use multiple processes only for a concrete isolation or integration requirement. Define process-safe storage/IPC and ensure libraries do not initialize incorrectly in every process.
Android creates an application process when a component needs to run and can later kill a background process without final cleanup callbacks. Cold startup creates the process, Application, component, UI, and first frame; warm and hot starts reuse progressively more state. I treat all in-memory objects as disposable, pass stable IDs into entry points, save only small restorable UI state, persist durable business data, and keep eager startup work minimal.