Mobile Development 18 min read

In-depth Source Code Investigation of FLAG_ACTIVITY_NEW_TASK Causing Activity Launch Failure

This article analyzes why adding the FLAG_ACTIVITY_NEW_TASK flag can prevent an Android Activity from starting, examines the underlying framework source code—including startActivityInner, getReusableTask, recycleTask, and complyActivityFlags—and provides practical solutions to resolve the launch failure.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
In-depth Source Code Investigation of FLAG_ACTIVITY_NEW_TASK Causing Activity Launch Failure

In-depth Source Code Investigation of FLAG_ACTIVITY_NEW_TASK Causing Activity Launch Failure

Android provides four launch modes for Activities, but the actual start behavior is also heavily influenced by Intent flags such as android:launchMode and android:taskAffinity . When a Service tries to start an Activity, the framework requires the FLAG_ACTIVITY_NEW_TASK flag, and the interaction of this flag with launch modes can lead to situations where an Activity never gets created.

Basic LaunchMode Behavior

standard : always creates a new Activity instance.

singleTop : reuses the top Activity and calls onNewIntent() if it is already at the top.

singleTask : clears Activities above an existing instance and calls onNewIntent() .

singleInstance : runs in its own task and also calls onNewIntent() when reused.

These modes work only when no additional flags are added and taskAffinity is unchanged.

Service Starting an Activity

When a Service starts an Activity, the framework throws an AndroidRuntimeException if the FLAG_ACTIVITY_NEW_TASK flag is missing, because a non‑Activity context may not have an existing task stack.

if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
        && (targetSdkVersion < Build.VERSION_CODES.N || targetSdkVersion >= Build.VERSION_CODES.P)
        && (options == null || ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) {
    throw new AndroidRuntimeException(
        "Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag."
        + " Is this really what you want?");
}

Unable to Start a standard Activity

Reproduction Steps

Service adds FLAG_ACTIVITY_NEW_TASK and starts AActivity .

AActivity starts BActivity .

Service again adds FLAG_ACTIVITY_NEW_TASK and starts AActivity .

Result: AActivity never receives onCreate , onResume or onNewIntent . BActivity remains unchanged.

Cause Speculation

Removing the finish() call before the second launch makes AActivity start successfully, indicating that the combination of FLAG_ACTIVITY_NEW_TASK , the standard launch mode, and the existing task stack prevents a new instance from being created.

Source Code Tracing

startActivityInner

int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
        IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
        int startFlags, boolean doResume, ActivityOptions options, Task inTask,
        boolean restrictedBgActivity, NeededUriGrants intentGrants) {
    setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
            voiceInteractor, restrictedBgActivity);
    // compute launching flags
    computeLaunchingTaskFlags();
    // find reusable task
    final Task reusedTask = getReusableTask();
    final Task targetTask = reusedTask != null ? reusedTask : computeTargetTask();
    final boolean newTask = targetTask == null;
    mTargetTask = targetTask;
    computeLaunchParams(r, sourceRecord, targetTask);
    int startResult = isAllowedToStart(r, newTask, targetTask);
    if (startResult != START_SUCCESS) {
        return startResult;
    }
    // ... further processing ...
}

getReusableTask

private Task getReusableTask() {
    // Conditions for putting into an existing task:
    // 1. Flags contain FLAG_ACTIVITY_NEW_TASK but not FLAG_ACTIVITY_MULTIPLE_TASK
    // 2. launchMode is singleInstance or singleTask
    boolean putIntoExistingTask = ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 &&
            (mLaunchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
            || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK);
    putIntoExistingTask &= mInTask == null && mStartActivity.resultTo == null;
    ActivityRecord intentActivity = null;
    if (putIntoExistingTask) {
        if (LAUNCH_SINGLE_INSTANCE == mLaunchMode) {
            intentActivity = mRootWindowContainer.findActivity(mIntent, mStartActivity.info,
                    mStartActivity.isActivityTypeHome());
        } else if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
            intentActivity = mRootWindowContainer.findActivity(mIntent, mStartActivity.info,
                    !(LAUNCH_SINGLE_TASK == mLaunchMode));
        } else {
            intentActivity = mRootWindowContainer.findTask(mStartActivity, mPreferredTaskDisplayArea);
        }
    }
    return intentActivity != null ? intentActivity.getTask() : null;
}

recycleTask

int recycleTask(Task targetTask, ActivityRecord targetTaskTop, Task reusedTask,
        NeededUriGrants intentGrants) {
    if (targetTask.mUserId != mStartActivity.mUserId) {
        mTargetStack = targetTask.getStack();
        mAddingToTask = true;
        return START_SUCCESS;
    }
    // ... many branches ...
    if ((mLaunchFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
        if (!mMovedToFront && mDoResume) {
            mTargetStack.moveToFront("intentActivityFound");
        }
        resumeTargetStackIfNeeded();
        return START_RETURN_INTENT_TO_CALLER;
    }
    // ...
    if (mAddingToTask) {
        return START_SUCCESS;
    }
    // ...
    return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
}

complyActivityFlags

private void complyActivityFlags(Task targetTask, ActivityRecord reusedActivity,
        NeededUriGrants intentGrants) {
    ActivityRecord targetTaskTop = targetTask.getTopNonFinishingActivity();
    final boolean resetTask = reusedActivity != null &&
            (mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0;
    if (resetTask) {
        targetTaskTop = mTargetStack.resetTaskIfNeeded(targetTaskTop, mStartActivity);
    }
    if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) ==
            (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
        targetTask.performClearTaskLocked();
        targetTask.setIntent(mStartActivity);
        mAddingToTask = true;
    } else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
            || isDocumentLaunchesIntoExisting(mLaunchFlags)
            || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
        final ActivityRecord top = targetTask.performClearTaskForReuseLocked(mStartActivity,
                mLaunchFlags);
        if (top != null) {
            if (top.isRootOfTask()) {
                top.getTask().setIntent(mStartActivity);
            }
            deliverNewIntent(top, intentGrants);
        } else {
            mAddingToTask = true;
        }
    } else if ((mLaunchFlags & FLAG_ACTIVITY_REORDER_TO_FRONT) != 0 &&
            !mAddingToTask) {
        ActivityRecord act = targetTask.findActivityInHistory(mStartActivity.mActivityComponent);
        if (act != null) {
            Task task = act.getTask();
            task.moveActivityToFrontLocked(act);
            act.updateOptionsLocked(mOptions);
            deliverNewIntent(act, intentGrants);
            mTargetStack.mLastPausedActivity = null;
        } else {
            mAddingToTask = true;
        }
    } else if (mStartActivity.mActivityComponent.equals(targetTask.realActivity)) {
        if (targetTask == mInTask) {
            // same task, do nothing
        } else if ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0 ||
                LAUNCH_SINGLE_TOP == mLaunchMode &&
                targetTaskTop.mActivityComponent.equals(mStartActivity.mActivityComponent) &&
                mStartActivity.resultTo == null) {
            if (targetTaskTop.isRootOfTask()) {
                targetTaskTop.getTask().setIntent(mStartActivity);
            }
            deliverNewIntent(targetTaskTop, intentGrants);
        } else if (!targetTask.isSameIntentFilter(mStartActivity)) {
            mAddingToTask = true;
        } else if (reusedActivity == null) {
            mAddingToTask = true;
        }
    } else if (!resetTask) {
        mAddingToTask = true;
    } else if (!targetTask.rootWasReset) {
        targetTask.setIntent(mStartActivity);
    }
}

Reason Summary

The combination of FLAG_ACTIVITY_NEW_TASK with the standard launch mode, together with the current task‑stack state (especially after a finish() call), leads the framework to select a reusable task, skip the creation of a new Activity, and ultimately return START_DELIVERED_TO_TOP without invoking any lifecycle callbacks.

Solutions

Force the framework to treat the launch as a new task by adding FLAG_ACTIVITY_MULTIPLE_TASK together with FLAG_ACTIVITY_NEW_TASK , or by removing FLAG_ACTIVITY_NEW_TASK when it is not required.

Ensure that complyActivityFlags reaches a branch where mAddingToTask becomes true , e.g., by using FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_CLEAR_TOP as appropriate.

Avoid calling finish() on the previous Activity if it would change the task’s root component, or adjust the launch mode of the target Activity (e.g., use singleTask instead of standard ).

Reproducing the Anomaly

Steps

Service starts AActivity with FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK and launchMode="singleInstance" .

AActivity (optionally after finish() ) starts BActivity with a plain startActivity() .

Service repeats step 1, then step 2.

Result: BActivity is not recreated; instead onRestart() of the original instance is invoked.

Further Source Tracing

The framework automatically adds FLAG_ACTIVITY_NEW_TASK in three situations (no source Activity, source launchMode is singleInstance , or target launchMode is singleInstance / singleTask ), which explains why AActivity receives the flag and triggers the problematic path.

private void computeLaunchingTaskFlags() {
    if (mInTask == null) {
        if (mSourceRecord == null) {
            if ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && mInTask == null) {
                Slog.w(TAG, "startActivity called from non-Activity context; forcing " +
                        "Intent.FLAG_ACTIVITY_NEW_TASK for: " + mIntent);
                mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
            }
        } else if (mSourceRecord.launchMode == LAUNCH_SINGLE_INSTANCE) {
            mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
        } else if (isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
            mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
        }
    }
}

Solution for the Second Scenario

Start BActivity with FLAG_ACTIVITY_MULTIPLE_TASK to prevent the framework from reusing the existing task and to force a fresh instance.

finish();
startActivity(
    Intent(this, BActivity::class.java)
        .apply { addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) }
);

Conclusion

Activity launching in Android is highly complex; a single flag such as FLAG_ACTIVITY_NEW_TASK can interact with launch modes, task affinity, and the current task stack to produce hard‑to‑detect bugs. By tracing the framework source (especially startActivityInner , getReusableTask , recycleTask , and complyActivityFlags ) and adjusting flags or launch modes, developers can resolve these elusive launch failures.

debuggingAndroidActivityLaunchModeTaskStackFLAG_ACTIVITY_NEW_TASK
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.