Activity Lifecycle: Source-Level Choreography
When you invoke startActivity() to open a page, if you assume the system simply uses reflection to new an Activity object and callbacks onCreate(), you are fundamentally mistaken.
An Activity is a Contractual Window interacting between your App process and the system_server process. Understanding the lifecycle is essentially understanding how core system services (ATMS) grasp your App's memory state, UI visibility, and input focus across process boundaries.
This article dispenses with simple API enumerations and instead plunges into the AOSP source code to dissect the hardcore, end-to-end pipeline of an Activity instance: from nothingness, through deep cross-process invocations, down to the App's foundation that ultimately triggers onCreate().
1. Design Philosophy: Inversion of Control and the Marionette
Why doesn't Android—like a standard C++ game engine—give you a main() function and let you handle rendering loops and input events with a while(true)?
Because in a mobile operating system, memory and focus resources are exceptionally precious. The system must maintain absolute authority (e.g., forcefully killing background apps to reclaim memory, or stripping screen focus when a phone call arrives). To achieve this passive, strict control, Android adopted an Inversion of Control (IoC) architecture.
You can envision the Activity lifecycle as a highly realistic massive theater troupe and cinema regulatory model:
- The Supreme Theater Director:
ActivityTaskManagerService(ATMS, stationed in the system kernel-level processsystem_server, possessing a global, imperial view over all cinema lines). - The Long-Distance Cross-Zone Hotline:
BinderIPC communication mechanism. - The Backstage Dispatcher at Each Theater:
ActivityThread(running on your App process's main thread, responsible for rigidly answering the Director's calls and executing orders). - The Actors Themselves: The
onCreate,onResume, etc., lifecycle methods you override are merely the "fixed poses" these actors are forced to strike according to the Dispatcher's mandatory commands.
2. Macro Architecture: A Panoramic View of Cross-Process Dispatch
Before stepping through the source code, let's look at the entire "Supreme Theater Director" system from a class-diagram perspective. There is a rigid architectural boundary mapping between the system_server process and our App process:
┌──────────────────────────────────────────────────────────────────────────┐
│ System Process (system_server) │
└──────────────────────────────────────────────────────────────────────────┘
[ActivityTaskManagerService] (ATMS Director, for apps to make cross-boundary requests)
│
│ (1. Delegates verification and order taking)
▼
[ActivityStarter] (Audit Dept: Verifies Manifest licenses, handles LaunchMode)
│
│ (2. Triggers stack-top focus seizure and transition)
▼
[RootWindowContainer] (Cinema Supervisor: Supreme ruler scheduling all display stages)
│
│ (3. Arranges task collections)
▼
[Task] (Stage Performance Area: Commonly known as the Activity Task Stack)
│
│ (4. Creates dossier record card, docked on the system side)
▼
[ActivityRecord] (Actor's Electronic Dossier: Holds the communication credential for the corresponding actor in the App)
│
│ (5. Crosses the process boundary, firing packed Transaction top-secret script letters)
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈│┈┈┈┈┈┈┈┈ Binder IPC Communication Line (Deep Sea Kernel Space) ┈┈┈┈┈┈┈┈┈┈┈┈┈┈
│
┌─────────────────│────────────────────────────────────────────────────────┐
│ ▼ Application Process (App) │
└─────────────────│────────────────────────────────────────────────────────┘
[ApplicationThread] (Troupe Customer Service: Binder's server-side stub, dedicated to receiving system verdicts)
│
│ (6. Relays via Handler to the main queue)
▼
[ActivityThread] (Troupe Backstage Engine Room! Dispatches final commands like performLaunchActivity)
│
│ (7. Awakens the Troupe's Manager/Agent)
▼
[Instrumentation] (The App's exclusive agent, intercepting all component creations and reports)
│
│ (8. Reflectively invokes no-arg constructor via ClassLoader.loadClass().newInstance)
▼
[Activity] (Your component class; right now, it's just an empty shell formed from mud)
│
│ (9. Invokes the magical attach() method to connect all vital meridians)
▼
[ContextImpl] & [PhoneWindow] (Clothes the shell in magnificent stage costumes, granting it the power to query databases and draw on the screen)
As the diagram illustrates, internally, the system has no concept of a physical Activity body under its direct control. Its avatar is the ActivityRecord title held tightly by the system. The system_server process issues commands from thousands of miles away via the IApplicationThread thread.
3. Full-Pipeline Source Code Tree: A Cross-Process "Theatrical Premiere"
A mundane startActivity has an exhaustive underlying trajectory that crosses a macroscopic inter-process chasm. Let's use the analogy of "an actor applying to premiere on stage," paired with minimalist core source code, to dismantle the dozens of thrilling steps laden with severe thread-suspension defenses!
Phase 1: The Actor Demands to Premiere (App Process Initiates Jailbreak Request)
The sequence must begin with your command on the App side, utilizing the troupe's agent, Instrumentation, to force a message through the dedicated line.
[Execution Summary]:
Activity.startActivity(Intent)Activity.startActivityForResult()Instrumentation.execStartActivity(): The agent holds the dedicated line (acquires the ATMS Binder Proxy).ActivityTaskManager.getService().startActivity(...): The signal breaches the IPC communication wall.
[Source Code Penetration & Architectural Essence]:
// fwks/.../app/Instrumentation.java
public ActivityResult execStartActivity(...) {
try {
// ⚡️ [Long-Distance Origin]: The agent actively dials the Cinema Director's (ATMS) top-secret number
int result = ActivityTaskManager.getService().startActivity(...);
// If the theater checks its database and finds you suspected of "performing without an official license (not registered in Manifest)"
// ATMS immediately returns a ban code. Here, it forces the app to throw a fatal exception and crash!
checkStartActivityResult(result, intent);
} catch (RemoteException e) { ... }
}
[Why design it this way?] Inversion of Control and Security Defense. The system strictly forbids any App from secretly "drawing the curtains" to start application components on its own turf. All actions must be reported to the chief steward and handed over to the OS IPC layer for permission issuance and display memory pool management, preventing rogue monopolization and massive security privilege escalations.
Phase 2: Heavy Scrutiny and Clearing the Stage (system_server Headquarters)
When the call reaches the theater headquarters (system_server), ATMS never handles it personally. It fully delegates it to the ActivityStarter taskforce for identity verification and dispatch orders.
[Execution Summary]:
5. ATMS.startActivity(): Headquarters reception desk.
6. ActivityStarter.execute(): First stop is the PMS (PackageManagerService) to verify if the actor has a legal performance license. Once cleared, it stamps and issues an official virtual avatar card (ActivityRecord).
7. ActivityStarter.startActivityUnchecked(): Handles the highly complex entry position mode calculations (LaunchMode, Flags). Decides exactly which stage position to place him on (the Task stack collection).
8. RootWindowContainer.resumeFocusedTasksTopActivities(): Issues the command to begin lifting the stage curtain at the highest peak.
9. Task.resumeTopActivityUncheckedLocked(): The governor is shocked to find that the center stage is still occupied by an old actor, A, from the previous performance!
[Source Code Penetration & Architectural Essence]:
// fwks/.../wm/ActivityStarter.java
int execute() {
// [Audit Bureau Dispatch] Find PackageManagerService to sweep the Manifest inventory!
ActivityInfo aInfo = mService.resolveActivityInfoForIntent(...);
// Once admission is approved, the system won't care about your App-side classes yet.
// It builds a card in the high-tier memory pool for you — ActivityRecord.
ActivityRecord r = new ActivityRecord(mService, ...);
// Inference Decision Area: Will he open a new stage or run into an old set stack...
return startActivityUnchecked(r, ...);
}
// fwks/.../wm/Task.java
boolean resumeTopActivityInnerLocked(...) {
// Sharp Alarm: "Denied! Clear the area! Due to single-screen display rules, the frontline stage is still occupied by an old page (like A)!"
if (mResumedActivity != null) {
// Decapitate the Old Guard: Issue a death warrant signaling the old page A to forcibly step down! (This triggers cross-IPC notification shortly)
startPausingLocked(userLeaving, false /* uiSleeping */, top);
// ⚠️⚠️ EXTREMELY FURIOUS AND LETHAL DEADLOCK POINT:
// After this code executes, it ruthlessly blocks and exits immediately with `return true;` !!
// This signifies that the system's massive internal operational pipeline is completely suspended and locked here!
// System demand: WAIT! Only when A finishes running on its machine, retreats, and sends back a message, will I unlock and let B render.
return true;
}
}
[Why design it this way?] This is a rigorous, high-pressure anti-concurrency screen-tearing sequence lock. Without a synchronized write-back lock waiting for the old page to step down, heavy graphical overlay chaos would occur during underlying double-starts or sudden memory depletion, because in a single-display physical world, there can only be one king on the summit.
Phase 3: The Old King is Forced to Retire and the Deadlock Thaws (App Front-End Life-or-Death Standoff)
The system sends an order back to the original App troupe via Binder: The PauseActivityItem clearance parcel.
[Execution Summary]:
10. Task.startPausingLocked() -> Initiates IPC cross-process counterattack!
11. ApplicationThread.scheduleTransaction() : Troupe front-desk customer service receives the eviction notice.
12. ActivityThread.H: Routes via internal Handle thread to the main hall's core consumption queue.
13. TransactionExecutor.execute(): The registrar begins verifying the system scriptbook.
14. ActivityThread.handlePauseActivity()
15. Activity.performPause() -> onPause(): Old actor A triggers the disrobing and exit action!
16. ActivityTaskManagerService.activityPaused() : The troupe front desk immediately sends a pigeon back to the highly anxious Emperor ATMS: "Report! A has successfully removed his crown and left the stage center!!"
[Source Code Penetration & Architectural Essence]:
// fwks/.../app/ActivityThread.java
@Override
public void handlePauseActivity(...) {
// This line unreasonably pierces the kernel, ultimately triggering the protective wall
// Activity.onPause() manually written by you unfortunate developers!!
performPauseActivity(r, finished, reason, pendingActions, ...);
// (Pay attention!) After your onPause() finishes its antics, this line is the most important unlocking artifact:
// The App actively calls the system's major long-distance Binder interface, surrendering the military tally:
// "The old minister has completely rolled out of the frame! Your Majesty can arrange for that new kid to take the stage!"
ActivityTaskManager.getService().activityPaused(r.token);
}
[Why design it this way? And the Epic Landmine Prevention]
Why is onPause the ultimate taboo during the transition process, and why will doing disk synchronization inside it completely ruin the next interface?
Because if you forcefully perform a 500-millisecond I/O network stall here (in old actor A's exit function), you cause the subsequent "safe arrival activityPaused long-distance call" to ATMS to be delayed entirely.
And did you see in the previous step? ATMS is completely rigid in the system layer under a domineering return true black lock, not daring to advance a single step. The stutter happens right here in the delayed transition of power between the new and old kings!
Phase 4: The Emperor Bestows the Sedan, The New King Arrives! Parcel Airdrop (ATMS Unchains and Approves)
ATMS finally receives the ice-breaking notification ring it has been waiting half a day for, and the master switch lock snaps open! It begins demanding the system create this long-awaited new actor. Packages the parcel and ships it to the civilian sector!
[Execution Summary]:
17. ATMS revives after unchaining and sprints until reaching: ActivityStackSupervisor.realStartActivityLocked().
18. ClientLifecycleManager.scheduleTransaction(): Boxes an "atomic composite mega-parcel" named ClientTransaction, hurling a nuclear bomb cross-process toward the troupe (App process).
[Source Code Penetration & Architectural Essence]:
// fwks/.../wm/ActivityStackSupervisor.java
void realStartActivityLocked(ActivityRecord r, WindowProcessController proc, ...) {
// [Package the Imperial Edict]: Based on the new system revolution mechanism, pack an extremely standardized and self-driving assembly script transaction package (ClientTransaction)
final ClientTransaction clientTransaction = ClientTransaction.obtain(
proc.getThread(), r.appToken);
// Action Assembly Box 1: Load gunpowder "Entity Creation Command" (LaunchActivityItem) -> Contains hidden stakes forcing the object to be spawned from nothing in the App, ultimately demanding its internal onCreate be called!
clientTransaction.addCallback(LaunchActivityItem.obtain(...));
// Action Assembly Box 2: Set the aiming crosshair "Ultimate Long-Term Goal" (ResumeActivityItem) -> I don't care how you stumble in between, this package dictates: your final lifeline stopping marker MUST reach the foreground display stage of onResume!
clientTransaction.setLifecycleStateRequest(ResumeActivityItem.obtain(...));
// ⚡️ Hurl the massive IPC communication laden with countless expectations and destiny gears! The script becomes reality!
mService.getLifecycleManager().scheduleTransaction(clientTransaction);
}
[Why design it this way?] In the extremely harsh environments of the Android 8 era and before, this step was not packaged so beautifully. It frantically fired several scattered scheduleLaunch parcels at the bottom level, and even later fired a scheduleResume parcel. Imagine the extreme sequence errors and lifecycle crossing collapses caused by dropped connections and internal CPU snatching. Using a massive, declarative, atomic ClientTransaction package operation is the ultimate foundation for elevating lifecycle robustness to a unified standard!
4. The Hatching Miracle of the Backstage Workshop: From Unboxing to the Manifestation of onCreate()
Having finally survived near-death, the grand script LaunchActivityItem flows back into our familiar App home environment's ActivityThread. It must officially begin creating a living person out of thin air (entering the deep space exploration engine method performLaunchActivity()). The substance of all great origins is enveloped by these three micro-megastructures:
① Reflective Body Creation (Molding Humanoid from Thin Air)
No matter how heaven-defying the four major components are, if they want to become physical objects, they first need a fleshy body:
// fwks/.../app/ActivityThread.java
try {
// Appalling Operation: Force the current memory ClassLoader to dryly generate the entity
// in the VM heap area via pure no-arg reflection!
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
}
[Metaphorical Truth]: Blindly sketching the empty shell of a person out of mud. At this point, it is utterly insignificant for calling grand methods: if you try to write getString, it will error out! Because right now, it lacks any meridian environment on its body; it wanders outside the three realms with no authority or magic power to speak of!
② Enacting Laws, Injecting Resources, and Granting Godhood (Context / Window Synthesis)
To make it not just issue commands, but most importantly, become an immortal capable of drawing the foreground.
try {
// Create an extremely hardcore, exclusive single-log neural tree system for him: ContextImpl !!
ContextImpl appContext = createBaseContextForActivity(r);
// [Turn the Tide!!]: The Epic Saga Method Call: Forcibly implanting the source from the crown of the head! attach clears the meridians!!
activity.attach(appContext, this, getInstrumentation(), r.token, ...);
}
[Metaphorical Truth]: Penetrating into Activity.attach() is enormously complex underneath, but it does nothing more than those three heaven-connecting tasks:
- Base Binding Blood Transfusion: Forcibly stuffs the highly capable
ContextImplobtained above into its own root base class, acquiring the ultimate magic cubeContextWrapperthat can shine and demand resources and scheduling from the system. - Scroll Conferment: Stuffs a canvas instance into this class, the sole entity used to host and proxy all drawing and touch screens in the world:
mWindow = new PhoneWindow(...). - Inner Court Token Pendant: Takes the sole official system-issued credential passed from far away in step 4—the
IBinder Token—and hangs it close to the chest. This is for holding up as evidence that it carries the imperial edict and must not be indiscriminately killed or penalized during future passive purging.
③ Firing the Starting Gun to Open the Stage (The Violent Eruption of onCreate!)
The feast is prepared. The system bottom layer gathers its seals and exposes the final, truly compliant, controllable first breath of air code segment to you:
// All torso equipment is ready, possessing divine creation power, the proxy manager
// lets the actor stride out, burst through the curtain, and open the grand door!
mInstrumentation.callActivityOnCreate(activity, r.state);
After this stroke of divine intervention peals back the layers to the bone, the final massive explosion it triggers is your hand-written override: protected void onCreate(...).
When this earth-shattering drumbeat strikes the main thread, the setContentView(...) you called in the first line of code finds that the massive canvas entity PhoneWindow hidden behind it has already finished loading! Consequently, this causes all sorts of bizarre views and layouts to take root and resolve here!!
5. Extreme Algorithm: How to Patch the Plummeting Fragmented Engine State Machine
We carefully and fearfully realize that in the flow above, after completing the so-called Launch, the code unexpectedly cuts off quietly at onCreate and probes no further?
Don't forget that earlier the troupe explicitly signed a life-or-death target decreeing it MUST run to the foreground declaration terminus of ResumeActivityItem!! How is this considerable gap patched up?!
We can only gaze up in wonder at the extremely advanced, Alien-spider-like deductive engine auto-path-builder logic hidden deep within the Android Framework engine source code:
// fwks/.../app/servertransaction/TransactionExecutor.java
private void executeLifecycleState(ClientTransaction transaction) {
// Retrieve the supreme red-line hard target carved into that parcel
final ActivityLifecycleItem lifecycleItem = transaction.getLifecycleStateRequest();
// [Probe own current anchor position]: Because the creation clapperboard above exhausted itself running onCreate, leaving it stranded in the pitiful deep position like Level 1 Area.
int currentState = r.getLifecycleState();
// [Check the distant tier hanging high at the destination]: Written rigidly here, the grand goal MUST push into the Resumed constant high-dimensional realm, like Level 3 Area.
int targetState = lifecycleItem.getTargetState();
// ! The invincible calculative engine patcher boots up: Forcibly climbing from Phase 1, compelled to match Phase 3! It must rely on deduction to grab that incredibly terrifying bridge set containing missing signposts and fault lines!
// The calculated result is that the required road sign array set is: [ON_START, ON_RESUME]!
final IntArray lifecycleItemRequest = mHelper.getLifecyclePath(
currentState, targetState, excludeLastState);
// Gripping the calculated fragmented list, the system forcibly opens an overbearing hitchhiker massive FOR loop! Utilizing the underlying reflection to madly take over the short line and detonate it along the way!
performLifecycleSequence(r, lifecycleItemRequest);
}
Within that massive black box performLifecycleSequence(): The engine pumps blood and fiercely pursues! It hits the first calculated missing slot ON_START, and immediately intercepts and proxies it to this person's corresponding handleStartActivity(); continuing its wild sprint, it hits the final calculated hard-coded column ON_RESUME, violently pouring the switchover to handleResumeActivity() to achieve the supreme grand-slam compliant closed-loop clue.
[The Architectural Aesthetics of Dimensionality Reduction]: Why have this seemingly supernatural self-driving land-reclamation operator system?! This is to prevent the extremely disjointed, ruined landscape where parts of the phases ran but the foreground still pulled a black curtain, resulting from parcels being tossed back and forth across extremely distant dual processes. This method of declaring a proud goal but using bottom-edge forced detection of fault lines, followed by forcing a rigid leak-filling mechanism to compel the difference array!, totally eradicated the disastrous spacetime chaos of ancient times caused by single-shot Binder sending congestion or terrible underlying hardware flying blind! It is one of the most rigorously sealed gear-core heartbeats of the modern mobile polymorphic positioning engine!
6. LMK's Hyperspeed Guillotine and the Ultimate Deception of onDestroy
As the true bedrock-level anti-landmine conclusion of this article, we must eradicate a perennial massive cognitive fallacy.
You always think about placing critical saved variables at the absolute tail end of the lifecycle to clean up and zero out, utterly unaware that in an operating system with high-pressure system control, there has NEVER existed a promise that guarantees onDestroy() will be awakened 100% of the time! Everything is just built upon a high-level lie of your sequential wishes.
Only when your program calls finish(), or when a constitutionally compliant configuration environment order is issued (switching black and white, or screen geometry failing due to a sudden forceful unfold of a foldable screen), will the system act like a mild-mannered etiquette master: Following the procedural hierarchy, it breathes out and clears first, letting the life Stop, waiting for it to run out and clean the background to execute the final elegy Destroy, properly taking care of the actor's last traces.
However, the real-world combat you encounter is far bleaker than this: When you swipe away the background, or when a high-energy chaotic memory battle causes a severe drop in process rank, the background Linux kernel directly treats it as garbage and relegates it to the lowest realm (the core lifeline oom_adj is violently dropped to the absolute bottom). If it happens to be pressured by a high-memory, high-definition game or a heavy-load main task urgently demanding memory slots, the system's extreme shortage crisis will reverse its tentacles, inciting the ultimate Battle Royale level mass-extermination system: The LMK (Low Memory Killer) brings down its great dragon-slaying sword and casts a massive net over the entire underlying void memory pool!!
Once it is activated by this cruel killing judgment.
What drops is the purest physical, most upstream severing ban of the highest authority, icy and absolute, without a single extra word of approval: kill -9 (SIGKILL).
This ultimate nuclear severing order, which completely strips away any life-defense mechanisms, will relentlessly and instantly pierce horizontally and directly down from the top-dimension, thoroughly bypassing and shattering your entire massive, mythical JVM/ART execution domain escort seal.
One femtosecond! In a mere femtosecond, it turns the entire application parcel process memory, along with all your proudly executing Java engine cores, into utter dust and powder against the deep black background of this grand universe, grand computer.
Under the system's silent, furious verdict, it is impossible for it to spare even 0.1 nanoseconds of leisure to permit you to struggle in any form of trivial pendant thread remnants like ShutDownCallBack suspended in system memory, let alone consider executing the bottom-line callback function onDestroy() that the system supposedly sent back to the engine dispatcher to hand over to you, devoid of all life, for closing the door and offering final condolences. All fantasies of betting on system release pressure or planning to save behavior when it finally acts, will, without exception, evaporate in this instant along with the process, resulting in missing, broken, and eternally hanging page-fault heavenly-sin BUGs that completely crash your business and cannot be restored.
Only by seeing through all these macroscopic process tearing match-ups, and understanding the distant routing engine and the irreversible downward physical guillotine chop of the giant sword suspended high above and swung down at any given moment. When you encounter screen-switch loss, severe stuttering resulting in locked tables, and other cloudy disasters and dead-end condemnations. You must grip the unshakeable and unambiguous defensive retreat in the source code—retreating to the true, strong anti-decay, broken-string, lightning-rod preservation line: the complete retreat to the true front of the onPause / onStop / ViewModel closed loop.
Only then! Can you forge and write the great-vehicle, disaster-preventing, supremely robust, absolute-evasion code truth that is firmer than the coldest, thickest steel plate, the most bottom-level, most thoroughly steady, safe, and immortal within the boundaries of this crisis-ridden, terrifying, massive virtual deep-sea sandbox grand chessboard.