A Blueprint of Android Activity Lifecycle

Introduction

The Android Activity lifecycle is a sequence of state changes and callbacks that every Android Activity goes through from creation to destruction.

Understanding the Android Activity lifecycle is important not only for developers aiming to build efficiency in the applications, but also for security researchers and systems engineers who want to understand the internal workings on Android. Unlike typical lifecycle explanations focusing merely on callbacks like onCreate() or onResume(), in this post we’ll look at the Activity lifecycle in terms of AOSP internals, specifically using the Android 15 source code. We’ll break down each Android Activity lifecycle state, examine the underlying AOSP implementation, and explore how the OS interacts internally for the various stages.

Overview of the Activity Lifecycle

When an Activity is launched, it transitions through a well-defined sequence of states. Figure 1 below illustrates the typical lifecycle flow:

Image Reference: https://www.geeksforgeeks.org/activity-lifecycle-in-android-with-demo-app/

Each Activity passes through several well-defined stages as shown in the table below:

StageFunctionPurpose
CreationonCreate()Initialization and attachment
StartonStart()Making the Activity visible
ResumeonResume()Transitioning to the foreground
PauseonPause()Preparing to lose focus
StoponStop()Becoming fully hidden
DestroyonDestroy()Final cleanup
RestartonRestart()Preparing for a re-launch

At each step, the framework performs rigorous checks (like verifying that super is called) and manages resources to ensure stability and performance. Each callback marks a state transition: for example, after onPause() the Activity is paused (partially obscured or backgrounded), and after onStop() it is stopped (not visible, in background).

The system guarantees some ordering invariants – e.g. it will always call onPause() before onStop(), and onStop() before onDestroy() (except in rare cases like system kill). Proper handling of these callbacks ensures your Activity manages resources correctly as it gains or loses user focus.

Now, lets examine each lifecycle callback and its role in Android.

Lifecycle Callbacks and States

(i) onCreate(): Activity Initialization

The onCreate() function is called when the Activity is first created. This is where you initialize UI components (via setContentView()), set up view models or data binding, and perform one-time setup. In the AOSP Activity class, onCreate() is defined for you to override. Internally, Android ensures that your override calls the superclass implementation. If you fail to call super.onCreate(), the system will throw a SuperNotCalledException.

This enforcement happens in the Activity launch sequence inside ActivityThread: before invoking your Activity’s onCreate, the framework sets a flag mCalled = false. After returning from onCreate, it checks the flag, which the base class sets to true in its own implementation. Not calling super leaves it false and triggers an exception. For example, if we take a look at https://cs.android.com/android/platform/superproject/+/android15-qpr1-release:frameworks/base/core/java/android/app/ActivityThread.java we can see the following code in the Activity launch routine:

				
					// ActivityThread.performLaunchActivity (simplified)
activity.mCalled = false;
if (r.isPersistable()) {
    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
    mInstrumentation.callActivityOnCreate(activity, r.state);
}
if (!activity.mCalled) {
    throw new SuperNotCalledException(
        "Activity " + r.intent.getComponent().toShortString() +
        " did not call through to super.onCreate()");
}
				
			

Here, Instrumentation.callActivityOnCreate() ultimately invokes the Activity’s performCreate() method, which calls onCreate() on your Activity. The base class implementation of onCreate() doesn’t do much by itself but will mark the mCalled flag. In fact, the Activity base class uses a pattern like this for several lifecycle callbacks to enforce that the developer calls the superclass method. The link at https://cs.android.com/android/platform/superproject/+/android15-qpr1-release:frameworks/base/core/java/android/app/Instrumentation.java helps us understand that.

Similar checks exist for onStart, onResume, onPause, etc., as we’ll see later.

After onCreate(), the Activity is in “Created” state but not yet visible to the user.

(II) onStart(): Activity Becomes Visible
 

The onStart() function is called when the Activity is becoming visible to the user. If the Activity is being launched anew, onStart() follows onCreate(); if it was previously stopped, onRestart() is called first (we discuss onRestart() shortly). In onStart, you typically start animations or UI refresh that should happen as the Activity comes onscreen.

In the framework, the base Activity.onStart() implementation sets the mCalled flag to true and does some internal dispatching (e.g. to report the start to any Fragments attached). Let’s look at the code at https://cs.android.com/android/platform/superproject/+/android15-qpr1-release:frameworks/base/core/java/android/app/Activity.java:

				
					rotected void onStart() {
    if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this);
    mCalled = true;
    mFragments.doLoaderStart();
    dispatchActivityStarted();
    getAutofillClientController().onActivityStarted();
}
				
			

The mCalled = true here signals that the base class was reached. If your override omits super.onStart(), a SuperNotCalledException will be thrown when the framework calls Activity.performStart(). The performStart routine is analogous to performLaunchActivity above: it sets mCalled=false, calls onStart(), then checks the flag and throws if not set.

After onStart, the Activity is visible to the user, though not yet in the foreground if another activity is on top. At this point the Activity’s window is created and laid out. However, the user cannot interact with it until onResume() is called.

(III) onResume(): Activity Enters Foreground (Interactive)

The onResume() function is called when the Activity has become the foreground activity (the top of the stack, receiving user input). This is where the app begins interacting with the user – for example, start camera preview, resume a game loop, or otherwise exclusive resources that should only be active in the foreground. After onResume(), the Activity is considered running.

Like other callbacks, onResume must call super.onResume(). Internally, the framework ensures this via the Activity.performResume() method. You can see this in code at https://cs.android.com/android/platform/superproject/+/android15-qpr1-release:frameworks/base/core/java/android/app/Activity.java:

				
					// Activity.performResume() inside Activity.java
mCalled = false;
// mResumed will be set true by Instrumentation before calling onResume
mInstrumentation.callActivityOnResume(this);
EventLogTags.writeWmOnResumeCalled(mIdent, getComponentName().getClassName(), reason);
if (!mCalled) {
    throw new SuperNotCalledException(
        "Activity " + mComponent.toShortString() +
        " did not call through to super.onResume()");
}
// ... (additional logic for Activities without UI omitted)
mCalled = false;
mFragments.dispatchResume();
mFragments.execPendingActions();
onPostResume();
if (!mCalled) {
    throw new SuperNotCalledException(
        "Activity " + mComponent.toShortString() +
        " did not call through to super.onPostResume()");
}
				
			

A few things to note here: Instrumentation.callActivityOnResume(this) is what actually calls your Activity’s onResume() method. This can be seen at https://cs.android.com/android/platform/superproject/+/android15-qpr1-release:frameworks/base/core/java/android/app/Instrumentation.java. The Instrumentation class simply does:

				
					public void callActivityOnResume(Activity activity) {
    activity.mResumed = true;
    activity.onResume();
    if (mActivityMonitors != null) { ... }
}
				
			

So it marks the Activity as resumed and calls the override. The snippet above also shows the framework calling onPostResume() after resuming (with a similar super.onPostResume() check). onPostResume() is seldom overridden by apps. It is a callback you can use if you need to perform work after the normal resume process (the base implementation of onPostResume() just sets mCalled = true). In summary, when onResume() returns, your Activity is interactive and at the top of the screen.

(iV) onRestart: Previously Stopped Activity Enters Foreground

If an Activity was previously stopped (completely hidden) and is coming back to foreground, the system will call onRestart() before onStart(). This typically occurs if the user presses Back from a next activity to return to a previous one, or switches away and then back via Recent Apps or task switcher. By default, onRestart() doesn’t do much (and most apps don’t override it), but it’s an opportunity to refresh any state that was released in onStop(). Internally, performRestart() will call through to onStart() as needed. You can see this code at https://cs.android.com/android/platform/superproject/+/android15-qpr1-release:frameworks/base/core/java/android/app/Activity.java. The AOSP code calls performRestart(true, ...) to indicate the Activity is restarting and should invoke onStart()

ensuring super.onStart() is called as discussed.

(v) onPause(): Activity Loses Focus (Partial Obscurity)

The functiononPause() is called when the Activity is losing foreground focus but is still partially visible. This is typically when another activity is launching in front of it, or a translucent activity/dialog is covering it. An Activity in the Paused state has lost focus (no user input) but hasn’t fully stopped – often the next activity is in the process of starting. The system will always call onPause() before starting a new activity’s onResume(), thereby ensuring no two activities are in resumed state simultaneously.

You should use onPause() to pause ongoing actions that shouldn’t continue while the user is leaving – for example, pause a video, throttle sensor updates, or commit lightweight unsaved changes. However, onPause() is intended to be very brief. It should not do heavy work (like slow network calls or extensive database writes) because it blocks the incoming activity from resuming promptly. For more substantial saving of data or releasing of resources, use onStop(). While onStop() is not guaranteed to be called before the app might be killed in the background, it provides a more suitable place for those heavier operations compared to onPause().

The framework implementation of Activity.performPause() calls your onPause() and enforces the super call. You can see this code at https://cs.android.com/android/platform/superproject/+/android15-qpr1-release:frameworks/base/core/java/android/app/Activity.java.

				
					
final void performPause() {
    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performPause:" + mComponent.getClassName());
    dispatchActivityPrePaused();
    mDoReportFullyDrawn = false;
    mFragments.dispatchPause();
    mCalled = false;
    onPause();
    EventLogTags.writeWmOnPausedCalled(mIdent, getComponentName().getClassName(), "performPause");
    mResumed = false;
    if (!mCalled && getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.GINGERBREAD) {
        throw new SuperNotCalledException(
            "Activity " + mComponent.toShortString() +
            " did not call through to super.onPause()");
    }
    dispatchActivityPostPaused();
    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
				
			

Notice the sequence: it dispatches a “pre paused” event used by lifecycle observers, calls onPause(), then marks the Activity as not resumed (mResumed = false) and checks mCalled. The check is only enforced for apps targeting SDK >= GINGERBREAD (API 9) which is practically every modern app. After onPause(), the Activity may remain partially visible if the next activity is transparent or set to not cover the full screen, or it may go fully off-screen if the next activity is full-screen – in which case onStop() will follow.

(vI) onStop(): Activity Fully Hidden

The onStop() function is called when the Activity is no longer visible at all. This happens when a new activity covers the entire screen or when the user navigates to the home screen or another app. In onStop(), you should release or adjust resources that are not needed while the activity is not visible – for example, stop animations, release heavy resources (camera, receivers), and persist any state that needs to be saved. Once stopped, the Activity object still resides in memory in stopped state unless the system decides to terminate the process to reclaim resources.

				
					final void performStop(boolean preserveWindow, String reason) {
        if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performStop:"
                    + mComponent.getClassName());
        }
        mDoReportFullyDrawn = false;
        mFragments.doLoaderStop(mChangingConfigurations /*retain*/);

        // Disallow entering picture-in-picture after the activity has been stopped
        mCanEnterPictureInPicture = false;

        if (!mStopped) {
            dispatchActivityPreStopped();
            if (mWindow != null) {
                mWindow.closeAllPanels();
            }

            // If we're preserving the window, don't setStoppedState to true, since we
            // need the window started immediately again. Stopping the window will
            // destroys hardware resources and causes flicker.
            if (!preserveWindow && mToken != null && mParent == null) {
                WindowManagerGlobal.getInstance().setStoppedState(mToken, true);
            }

            mFragments.dispatchStop();

            mCalled = false;
            final long startTime = SystemClock.uptimeMillis();
            mInstrumentation.callActivityOnStop(this);
            final long duration = SystemClock.uptimeMillis() - startTime;
            EventLogTags.writeWmOnStopCalled(mIdent, getComponentName().getClassName(), reason,
                    duration);
            if (!mCalled) {
                throw new SuperNotCalledException(
                    "Activity " + mComponent.toShortString() +
                    " did not call through to super.onStop()");
            }

            synchronized (mManagedCursors) {
                final int N = mManagedCursors.size();
                for (int i=0; i<N; i++) {
                    ManagedCursor mc = mManagedCursors.get(i);
                    if (!mc.mReleased) {
                        mc.mCursor.deactivate();
                        mc.mReleased = true;
                    }
                }
            }

            mStopped = true;
            dispatchActivityPostStopped();
        }
        mResumed = false;
        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
    }
				
			

If we look at the above code from https://cs.android.com/android/platform/superproject/+/android15-qpr1-release:frameworks/base/core/java/android/app/Activity.java, we can see that the framework’s performStop() will call your onStop() and ensure super.onStop() was invoked. It also handles calls to onSaveInstanceState() which typically occur before onStop in earlier Android versions, or after onStop in newer versions. For apps targeting P and above, onSaveInstanceState is called after onStop. The ordering changed to improve API consistency, but as a developer you usually implement onSaveInstanceState(Bundle) to save UI state (it’s called either way during the stop process).

After onStop(), the Activity is stopped – not visible, not interacting, but still alive in memory. It can be brought back via onRestart()->onStart()->onResume if the user returns to it. Importantly, if the system needs memory, it might kill the process hosting a stopped activity without calling onDestroy() . The user wouldn’t notice until they return and the app is relaunched with the saved state.

(vII) onDestroy(): Activity Cleanup and Destruction

The onDestroy() function is the final callback in the lifecycle. It is called when the Activity is finishing (someone called finish(), or the user pressed Back), or when the system is permanently shutting down the activity’s process. In onDestroy(), you should clean up any resources not handled by other callbacks – e.g., dismiss dialogs, close database connections, or release binding to services.

If onDestroy() is called as part of an explicit finish, it will be preceded by onPause() and onStop(). One exception noted in the official docs: if you call finish() from within onCreate(), the system may directly invoke onDestroy() without calling onPause() and onStop() in between because the activity never fully started. Otherwise, the normal sequence is pause → stop → destroy for a finishing activity.

Just like the other functions, the code for this can be seen in Activity.java at https://cs.android.com/android/platform/superproject/+/android15-qpr1-release:frameworks/base/core/java/android/app/Activity.java. The framework enforces super.onDestroy() similarly by setting mCalled=false and checking after calling your override (throwing a SuperNotCalledException if needed). Once onDestroy() completes, the Activity instance is gone. The process may still remain (if other activities/services are running), or it may terminate if this activity was the last one.

Security Considerations in the Activity Lifecycle

The Activity lifecycle, if not handled properly, can introduce security risks. In this section we’ll talk about some key considerations and how modern Android mitigates them.

  1. Task Hijacking (Task Affinity Exploits):

    A known Android vulnerability class called “StrandHogg” involved malicious apps manipulating task affinity and reparenting to hijack tasks. By setting a fake Activity’s taskAffinity to that of a target app and using allowTaskReparenting="true", malware could trick the system into moving the malicious activity into the target’s task, on top of a real activity. For example, a malicious Activity could appear when the user thinks they are returning to a safe app, stealing credentials.

    In terms of Mitigations, Google patched this in Android 10 and 11. As of SDK 30+ (Android 11), the system prevents reparenting across apps in this manner. Additionally, Android 12 now requires that any Activity with an <intent-filter> explicitly declare android:exported="true" (or false). This prevents inadvertent exposure of activities.

    As a best practices, always set taskAffinity to your app’s package (the default) unless you intentionally need a different task grouping. Do not use allowTaskReparenting for activities that can be launched externally. If your app targets API<30, be aware of these risks on older OS versions; consider using singleTask or singleInstance launch modes appropriately to isolate tasks.

  1. Background Activity Launch Restrictions:

    In older Android, a malicious app could invisibly launch a phishing Activity on top of a victim app if certain conditions were met. Modern Android has restrictions on starting activities from the background.

    An application in the background generally cannot launch a new Activity unless it has a privileged permission or the user interacted to trigger it. This closes an exploit where a background service could suddenly launch an Activity, stealing focus. This was used in some attacks in the past. Now, if an app tries this, the system often throws a SecurityException or simply doesn’t bring the activity to front unless it’s an allowed case (like an incoming call UI, etc.). This means lifecycle transitions are more strictly controlled by the system based on app state.

  2. Lifecycle Event Order and UI Hijacking:

    The guarantee that an Activity’s onPause() will complete before the next one’s onResume() starts is not just for performance, but it’s also to ensure that one app can’t, for instance, remain in resumed state while another is also resumed (which could allow two apps to potentially receive input). The system’s global activity lock (the ActivityTaskManagerService.mGlobalLock as seen in https://cs.android.com/android/platform/superproject/+/android15-qpr1-release:frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java) is used to serialize these state changes and maintain invariants. This prevents scenarios where a malicious app might try to remain in focus while another app is starting.

  3. Input Capture and Focus Stealing:

    The InputManager will only deliver input to the focused Activity’s window. By managing focus through WMS and ATMS, Android ensures that when your Activity is not in front, it shouldn’t be receiving touch events. A malicious app cannot easily intercept input meant for another app’s Activity unless it uses an overlay with accessibility or draws over other apps. This is separate from the lifecycle, and mitigated by overlay permission and restrictions on gesture capture.

    From the lifecycle perspective, once your Activity is paused, the system won’t send it further input events. Developers should be careful with dialogs or Activities with transparent backgrounds. If you allow touch to pass through, you might inadvertently allow touches on an Activity behind. For truly secure input (e.g., password fields), consider using FLAG_SECURE on the window to prevent content from being captured or obscured by other apps.

  1. Sensitive Data in Lifecycle Transitions:

    Data exposure can occur if sensitive information is left on-screen or in memory when an activity goes to background. For instance, by default the system takes a screenshot of your Activity for the Recent Apps view when you pause. If your Activity displays sensitive info (like banking details), that could be visible in Recents.

    As a mitigation, use getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, ...) in onCreate() to prevent screenshots and Recents thumbnails for that Activity. Also, in onPause(), consider hiding or obfuscating sensitive data (e.g., clear input fields or blur sensitive images) if your app might go to background. Some apps also implement an “auto-lock” for example by requiring re-authentication if the app was paused for more than X minutes. This is done to mitigate risks of someone else picking up the device and seeing the last screen.

  2. Proper Super Calls (DoS prevention):

    It might sound basic, but failing to call super.onPause(), super.onStop(), etc., can not only crash your app (via SuperNotCalledException) but potentially interfere with the system’s ability to manage your Activity resulting in an inconsistent state. This is why the framework aggressively checks these calls for both a correctness and as a safety measure. Always ensure you call through to the superclass in each overridden lifecycle method. This allows the framework to do internal bookkeeping (like dismissing dialogs in onDestroy, as the documentation notes) and avoid resource leaks.

  3. Task Affinity and Intent Redirection:

    Another security aspect is ensuring that when your Activity is launched, it’s actually the one the user expects. There have been attacks where an app registers an <intent-filter> that matches a common action (like a VIEW for a certain scheme) and if your app incorrectly forwards intents or uses implicit intents, a malicious Activity could handle it. To mitigate this, prefer explicit intents when launching internal components. When your Activity is resumed from background via an implicit intent, Android 12+ will ensure the target Activity is exported. From the lifecycle perspective, just be aware of who can launch your Activity. If you have an Activity that should only be started by your app, set android:exported="false" in the manifest (and no intent-filter). This way, the system will prevent external apps from launching it via tasks.

  1. Lifecycle-aware security (crypto, etc.):

    If your Activity uses sensitive cryptographic material like holds a decrypted key in memory, consider locking it or wiping it on lifecycle events. For instance, some apps override onPause() to blank out sensitive fields so it is not visible in Recents, and use onResume() to restore them after re-authenticating the user. Always assume that once your Activity is paused or stopped, the OS or user might inspect its last state.

  1. Recent Tasks and Intent Spoofing:

    A malicious app could try to trick your Activity into launching in its task by using carefully crafted Intents/flags. Modern Android has tightened the rules on Intent flags like FLAG_ACTIVITY_NEW_TASK and FLAG_ACTIVITY_CLEAR_TOP when used by other apps. Also, PendingIntents now require explicit declaration whether the target component can be started by a foreign app. As a developer, ensure you validate any data coming in getIntent() during onCreate/onResume if your Activity can be launched by external intents. For example, if your Activity expects an extra from your own app, verify the caller’s identity (perhaps using ActivityManager.getRunningTasks() or the new IntentSender APIs with flags to ensure authenticity).

Conclusion

In this blog we took a detailed look at the AOSP codebase to understand the internals of the Android Activity lifecycle. From the initial attachment in ActivityThread.java to the final cleanup in Activity.java, every step is carefully managed and enforced. The use of message handlers, rigorous checks (via the mCalled flag), and device-specific optimizations in Android 15 for various devices makes sure that the OS is secure and has a high-performance.

By examining exact file paths, code excerpts, and detailed review commentary, you can gain a much deeper understanding of how lifecycle events are processed internally, and leverage this knowledge to build more responsive, and secure Android applications.

Additionally, as discussed in the Security Considerations section, the Android Activity lifecycle’s design already incorporates many security measures like enforcing super calls and controlling which app has focus. Android’s recent versions have hardened these further by restricting background launches and requiring explicit declarations for component exposure. Make sure to implement the mitigations mentioned in the blog. At a minimum here are actions that should be taken:

  • Keep activities private (exported=false) unless they need to be public.
  • Use explicit intents for internal navigation.
  • Handle lifecycle transitions of sensitive data (use FLAG_SECURE, clear data on pause, etc.).
  • Be mindful of task affinities and launch flags to avoid confusing the task stack.

Looking to elevate your expertise in Android Security?

Offensive Android Internals Training

365 Days of Access | Hands-On Learning | Self-Paced Training

Explore Our On-Demand Courses

If you’re interested in diving deeper into topics like kernel panic analysis, vulnerability research, and low-level system debugging, 8ksec Academy offers a wide range of on-demand courses tailored for security professionals and enthusiasts.

Visit academy.8ksec.io to explore our full catalog of courses. Whether you’re a beginner or an experienced professional, you’ll find resources to enhance your skills and stay ahead in the fast-evolving field of Mobile Security.

Feel free to reach out to us at support@ to ask any questions related to our blogs or any future blogs that you would like to see.

Have a great day !

On Trend

Most Popular Stories

A Blueprint of Android Activity Lifecycle

Introduction <p id=”1ae3bc9e-4b19-8036-aa7b-e15336ccd7ea”>The Android Activity lifecycle is a sequence of state changes and callbacks that every Android Activity goes through from creation to destruction.</p><p id=”1ae3bc9e-4b19-8018-9001-e358a02da61f”>Understanding

Reading iOS Sandbox Profiles

Sandbox Profiles In this blog, we will be talking about understanding how to read Sandbox Profiles in iOS. In iOS, Sandbox Profiles are configuration files

Subscribe & Get InFormation

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.