What Actually Happens Between the Power Button and Your App’s First Frame
The Android boot sequence, end to end
What Actually Happens Between the Power Button and Your App’s First Frame
The Android boot sequence, end to end

The full chain — bootloader → kernel → init → Zygote → system_server → launcher → your app.
Press power on an Android phone and your app is the last thing to happen, not the first.
By the time a single line of your code runs, the operating system has already been awake for several seconds, parsed a stack of config files, forked a special template process, and started hundreds of system services in a fixed, deliberate order. Your launcher — the home screen you think of as “the phone” — is itself just an app that this freshly-booted system decided to start. Your app is even later than that.
Most of us carry a backwards mental model: that our app, or at least the launcher, drives the device. It’s the other way around. So let’s walk the real chain — power button to your first frame — and name the actual classes at each step. Once you’ve seen it, a lot of “why is X available in onCreate but Y isn't" stops being mysterious.
You never start Android. Android finishes booting — hundreds of services, in a fixed order — and then, almost as an afterthought, it starts you.
What runs before there’s even an “Android”?
Two layers you’ll almost never touch, but you should know they exist.
First the bootloader: a tiny program in flash that verifies and loads the Linux kernel. (This is also what enforces verified boot and what you unlock when you flash a custom ROM.) Then the Linux kernel boots: it initializes hardware, sets up memory and drivers, mounts the early filesystems, and then does the one thing that matters for us — it starts the first userspace process, **init, and hands it PID 1**.
Everything from here up is Android. None of it is your app yet. We’re still seconds away.
Who is PID 1, and what is it reading?
init is the root of all userspace. Its job is to bring the system up by reading a set of .rc scripts — init.rc and the files it imports from the system, vendor, and product partitions — written in Android's own init language. Those scripts declare services (long-running processes init should start and keep alive) and actionstriggered by events (on early-init, on boot, and so on).
One of those service declarations is the most important line in the whole boot, because it’s the doorway from “Linux” into “the Android runtime”:

init is an event machine: triggers fire, and each class of services starts in turn. One of those services is the Zygote.

system/core/rootdir/init.zygote64.rc on cs.android.com — the actual service entry that launches the Zygote.
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
class main
priority -20
user root
group root readproc reserved_disk
socket zygote stream 660 root system
Read that exec line carefully. init runs app_process64 — the binary that hosts the Android runtime — and passes it two flags that decide the next two stages of boot: --zygote (become the app template) and **--start-system-server** (and, once you're the template, immediately fork the brain of the OS). class main is what ties this service to the point in boot where main-class services start; priority -20 gives it the most favorable scheduling priority Linux offers.
Why does Android boot a “Zygote” before anything else?
app_process with --zygote lands in ZygoteInit.main(). The Zygote's whole reason to exist is startup speed: it loads the expensive, shared stuff once so every future app can inherit it instead of paying for it. In main() that's the preload(...) call — preloading common framework classes and resources into this one process.
Then, because init passed --start-system-server, the Zygote does the pivotal thing:

ZygoteInit.main() on cs.android.com — after preload(...), it calls forkSystemServer(...); the child runs that Runnable and becomes system_server, while the parent stays behind as the Zygote.
// frameworks/base/core/java/com/android/internal/os/ZygoteInit.java (main)
preload(bootTimingsTraceLog);
// ...
if (startSystemServer) {
Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);
// We are in the child (system_server): run its entry point and stop being a zygote.
if (r != null) {
r.run();
return;
}
}
// Otherwise we are the zygote: wait for fork requests, forever.
caller = zygoteServer.runSelectLoop(abiList);
Two processes now exist where there was one. The child is system_server. The parent falls into runSelectLoop() and sits on a socket waiting for fork requests — which is exactly how your app will be born later, when you tap its icon. That fork-from-a-warm-template trick is the subject of the next episode, so I'll leave its internals there; for the boot timeline, all you need is: the Zygote preloads, then forks the OS's main process.
What is system_server actually doing for those seconds?
system_server is the single most important process on the device. It hosts the services your app talks to all day — ActivityManagerService, PackageManagerService, PowerManagerService, WindowManagerService, InputManagerService, and dozens more. The forked child runs SystemServer.main(), which calls run(), sets up the system Context, creates a SystemServiceManager, and then starts everything in ordered phases:

system_server brings the OS up in phases — bootstrap services first (the ones everything else depends on), then core, then the long tail of everything else.

SystemServer.run() on cs.android.com — the real, ordered startup block.
// frameworks/base/services/java/com/android/server/SystemServer.java (run)
try {
t.traceBegin("StartServices");
startBootstrapServices(t);
startCoreServices(t);
startOtherServices(t);
startApexServices(t);
} catch (Throwable ex) {
Slog.e("System", "******* Failure starting system services", ex);
throw ex;
} finally {
t.traceEnd(); // StartServices
}
The order is not cosmetic — it’s a dependency graph made literal:
**startBootstrapServices** brings up the services nothing else can live without — includingActivityManagerService,PackageManagerService, andPowerManagerService. Everything downstream assumes these exist.**startCoreServices** adds the next tier (battery, usage stats, the WebView update service, and friends).**startOtherServices** starts the long tail —WindowManagerService,InputManagerService, connectivity, and the rest — and is where the system UI gets kicked off.**startApexServicesstarts services delivered by APEX modules. (Note: the famous "three phases" you'll read about in older write-ups is now four** —startApexServiceswas added later. This is exactly why you read the source instead of a 2018 blog.)
As services come up they’re also walked through named boot phases (system services ready, activity manager ready, third-party apps can start, boot completed), so each service can do work that depends on others already being initialized. By the end of this, the entire operating system — every API behind getSystemService — is alive.
When does your launcher (and then your app) finally appear?
Only now. Once the services are up and the system reaches “boot completed,” system_server fires the Home intent, and the launcher — an ordinary app, started the same way yours will be — is brought to the foreground. The status bar and navigation you see are SystemUI, itself started during system_server's boot. The lock screen, the wallpaper, the home grid: all apps, all launched by the system that just finished assembling itself.

The handoff: once system_server reports "boot completed," your process is forked off the waiting Zygote — the OS was fully running before you existed.
When you then tap your icon, your process is forked from that waiting Zygote, and ActivityManagerService (running in the system_server that booted seconds ago) orchestrates the launch. That tap-to-onCreate path is its own episode — but notice it can only work because every dependency it needs already booted.
So what does this mean for Application.onCreate?
Here’s the payoff, and the answer to the question every Android dev has half-asked: “Application.onCreate runs at start — but the start of what?"
The start of your process — which is the very end of the chain you just read. By the time your onCreate runs:
- Everything in the OS is already up. Every
getSystemService(...)call returns instantly because it's binding to a service that's been running insystem_serversince seconds after the kernel booted. You are never "too early" for the system itself. - But your own UI does not exist yet. There is no
Activity, noWindow, no view hierarchy whenApplication.onCreateruns — those come later in the launch sequence. Reaching for an Activity-scoped resource here is reaching for something the system hasn't built for you yet. - And some code already ran before you. Your auto-initialized
ContentProviders have already had theironCreatecalled beforeApplication.onCreate— which is how libraries quietly initialize themselves and quietly inflate your cold start. (That trick gets its own episode.)
The mental model to keep: your app is the last guest to a party the OS has been throwing for a while. The lights, the music, the kitchen — all running before you walked in. Your job in onCreate isn't to set up the world; it's to do the least possible work before the first frame, because the world is already there.
Next episode we zoom into the single strangest step on this timeline: that fork()from the Zygote. Your app isn't started — it's cloned from a warm template. Why that's the reason your cold start isn't brutal is where we go next.
메타데이터
- post_id
- 8747f91cbd28
- slug
- what-actually-happens-between-the-power-button-and-your-apps-first-frame-8747f91cbd28
- url
- https://medium.com/@promode7/what-actually-happens-between-the-power-button-and-your-apps-first-frame-8747f91cbd28
- canonical_url
- https://medium.com/@promode7/what-actually-happens-between-the-power-button-and-your-apps-first-frame-8747f91cbd28
- author_url
- https://medium.com/@promode7
- status
- ok
- fetched_at
- 2026-06-25 07:00:49