Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Understanding the Activity Launch Flow in Android Framework

Notes 1

When you call startActivity() from an Activity, a complex chain of framwork components comes into play to handle the transition to a new screen. This article examines how the launch request propagates through the system and what happens at each stage.

Entry Point in Activity

The public startActivity(Intent) method delegates to an internal implementation with optional parameters:

public class Activity extends ContextThemeWrapper {
    
    @Override
    public void startActivity(Intent intent, @Nullable Bundle options) {
        startActivityForResult(intent, -1, options);
    }

    public void startActivityForResult(Intent intent, int requestCode,
                                       @Nullable Bundle options) {
        if (mParent == null) {
            Instrumentation.ActivityResult ar =
                    mInstrumentation.execStartActivity(
                            this, 
                            mMainThread.getApplicationThread(), 
                            mToken, 
                            this,
                            intent, 
                            requestCode, 
                            options);
            
            if (requestCode >= 0) {
                mStartedActivity = true;
            }
        } else {
            mParent.startActivityFromChild(this, intent, requestCode, options);
        }
    }
}

The call eventually reaches the Instrumentation class, which serves as the central coordinator for application operations.

Instrumentation Layer

The Instrumentation.execStartActivity() method performs several important tasks before delegating to the system service:

public class Instrumentation {
    
    public ActivityResult execStartActivity(
            Context who, 
            IBinder contextThread, 
            IBinder token, 
            Activity target,
            Intent intent, 
            int requestCode, 
            Bundle options) {
        
        IApplicationThread whoThread = (IApplicationThread) contextThread;
        
        Uri referrer = target != null ? target.onProvideReferrer() : null;
        if (referrer != null) {
            intent.putExtra(Intent.EXTRA_REFERRER, referrer);
        }
        
        // Check activity monitors (for testing purposes)
        if (mActivityMonitors != null) {
            synchronized (mSync) {
                for (ActivityMonitor am : mActivityMonitors) {
                    if (am.match(who, null, intent)) {
                        am.mHits++;
                        if (am.isBlocking()) {
                            return requestCode >= 0 ? am.getResult() : null;
                        }
                    }
                }
            }
        }
        
        try {
            intent.migrateExtraStreamToClipData(who);
            intent.prepareToLeaveProcess(who);
            
            int result = ActivityTaskManager.getService().startActivity(
                    whoThread,
                    who.getOpPackageName(),
                    who.getAttributionTag(),
                    intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()),
                    token,
                    target != null ? target.mEmbeddedID : null,
                    requestCode,
                    0,
                    null,
                    options);
            
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }
}

The critical line is the call to ActivityTaskManager.getService().startActivity(), which transitions the request from the application process to the system server.

ActivityTaskManagerService

The service-side handling begins with the entry method that validates and routes the request:

public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
    
    @Override
    public final int startActivity(IApplicationThread caller, 
                                   String callingPackage,
                                   String callingFeatureId, 
                                   Intent intent, 
                                   String resolvedType,
                                   IBinder resultTo, 
                                   String resultWho, 
                                   int requestCode,
                                   int startFlags, 
                                   ProfilerInfo profilerInfo,
                                   Bundle bOptions) {
        return startActivityAsUser(caller, callingPackage, callingFeatureId, 
                intent, resolvedType, resultTo, resultWho, requestCode,
                startFlags, profilerInfo, bOptions,
                UserHandle.getCallingUserId());
    }

    private int startActivityAsUser(IApplicationThread caller, 
                                    String callingPackage,
                                    @Nullable String callingFeatureId, 
                                    Intent intent, 
                                    String resolvedType,
                                    IBinder resultTo, 
                                    String resultWho, 
                                    int requestCode,
                                    int startFlags, 
                                    ProfilerInfo profilerInfo, 
                                    Bundle bOptions,
                                    int userId, 
                                    boolean validateIncomingUser) {
        
        enforceNotIsolatedCaller("startActivityAsUser");
        
        return getActivityStartController().obtainStarter(intent, "startActivityAsUser")
                .setCaller(caller)
                .setCallingPackage(callingPackage)
                .setCallingFeatureId(callingFeatureId)
                .setResolvedType(resolvedType)
                .setResultTo(resultTo)
                .setResultWho(resultWho)
                .setRequestCode(requestCode)
                .setStartFlags(startFlags)
                .setActivityOptions(bOptions)
                .setUserId(userId)
                .execute();
    }
}

The builder patern is used here to construct the start request with all necessary parameters before executing it.

ActivityStarter Execution

The ActivityStarter class handles the actual logic of resolving and launching activities:

class ActivityStarter {
    
    int execute() {
        try {
            final LaunchingState launchingState;
            synchronized (mService.mGlobalLock) {
                final ActivityRecord caller = ActivityRecord.forTokenLocked(mRequest.resultTo);
                
                if (mRequest.activityInfo == null) {
                    mRequest.resolveActivity(mSupervisor);
                }
            }

            int result;
            synchronized (mService.mGlobalLock) {
                result = executeRequest(mRequest);
                
                mSupervisor.getActivityMetricsLogger().notifyActivityLaunched(
                        launchingState, result, 
                        mStartActivity == mLastStartActivityRecord, 
                        mLastStartActivityRecord, 
                        originalOptions);
                
                return getExternalResult(result);
            }
        } finally {
            onExecutionComplete();
        }
    }
}

The executeRequest() method performs extensive validation and preparation before the actual launch:

private int executeRequest(Request request) {
    // Validate caller and extract process information
    WindowProcessController callerApp = null;
    if (caller != null) {
        callerApp = mService.getProcessController(caller);
        if (callerApp != null) {
            callingPid = callerApp.getPid();
            callingUid = callerApp.mInfo.uid;
        }
    }

    // Check for permission issues
    if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) {
        err = ActivityManager.START_INTENT_NOT_RESOLVED;
    }

    if (err == ActivityManager.START_SUCCESS && aInfo == null) {
        err = ActivityManager.START_CLASS_NOT_FOUND;
    }

    // Permission and security checks
    boolean abort = !mSupervisor.checkStartAnyActivityPermission(
            intent, aInfo, resultWho, requestCode, callingPid, 
            callingUid, callingPackage, callingFeatureId, 
            request.ignoreTargetSecurity, inTask != null, callerApp, 
            resultRecord, resultRootTask);

    // Handle runtime permission review if needed
    if (aInfo != null && mService.getPackageManagerInternalLocked()
            .isPermissionsReviewRequired(aInfo.packageName, userId)) {
        // Redirect to permission review activity
        intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName);
    }

    // Create ActivityRecord for the target
    final ActivityRecord r = new ActivityRecord.Builder(mService)
            .setCaller(callerApp)
            .setLaunchedFromPid(callingPid)
            .setLaunchedFromUid(callingUid)
            .setIntent(intent)
            .setResolvedType(resolvedType)
            .setActivityInfo(aInfo)
            .setConfiguration(mService.getGlobalConfiguration())
            .setResultTo(resultRecord)
            .build();

    return startActivityUnchecked(r, sourceRecord, voiceSession,
            request.voiceInteractor, startFlags, true, checkedOptions,
            inTask, inTaskFragment, restrictedBgActivity, intentGrants);
}

Task Selection and Management

The startActivityUnchecked() method handles task affinity and launch mode resolution:

private int startActivityUnchecked(final ActivityRecord r, 
                                   ActivityRecord sourceRecord,
                                   int startFlags, 
                                   boolean doResume, 
                                   ActivityOptions options,
                                   Task inTask) {
    
    setInitialState(r, options, inTask, doResume, startFlags, sourceRecord);
    computeLaunchingTaskFlags();
    mIntent.setFlags(mLaunchFlags);

    final Task reusedTask = getReusableTask();
    final Task targetTask = reusedTask != null ? reusedTask : computeTargetTask();
    final boolean newTask = targetTask == null;
    
    if (targetTask != null && targetTask.getTopNonFinishingActivity() != null) {
        startResult = recycleTask(targetTask, targetTaskTop, reusedTask);
        if (startResult != START_SUCCESS) {
            return startResult;
        }
    }

    if (newTask) {
        setNewTask(taskToAffiliate);
    }

    if (mTargetRootTask == null) {
        mTargetRootTask = getLaunchRootTask(mStartActivity, mLaunchFlags, targetTask);
    }

    return START_SUCCESS;
}

Window Container Orchestration

After the task is determined, the RootWindowContainer coordinates resuming activities:

class RootWindowContainer {
    
    boolean resumeFocusedTasksTopActivities(
            Task targetRootTask, 
            ActivityRecord target, 
            ActivityOptions targetOptions,
            boolean deferPause) {
        
        if (!mTaskSupervisor.readyToResume()) {
            return false;
        }

        boolean result = false;
        if (targetRootTask.isTopRootTaskInDisplayArea()
                || getTopDisplayFocusedRootTask() == targetRootTask) {
            result = targetRootTask.resumeTopActivityUncheckedLocked(
                    target, targetOptions, deferPause);
        }

        // Handle other displays
        for (DisplayContent display : getDisplays()) {
            display.forAllRootTasks(rootTask -> {
                if (rootTask == targetRootTask) {
                    return;
                }
                if (rootTask.isFocusableAndVisible()) {
                    topRunningActivity.makeActiveIfNeeded(target);
                }
            });
        }

        return result;
    }
}

Task and Activity Resume

The Task class manages the resume sequence for activities within its stack:

class Task {
    
    boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, 
                                              ActivityOptions options,
                                              boolean deferPause) {
        if (mInResumeTopActivity) {
            return false;
        }

        boolean someActivityResumed = false;
        try {
            mInResumeTopActivity = true;

            if (isLeafTask() && isFocusableAndVisible()) {
                someActivityResumed = resumeTopActivityInnerLocked(prev, options, deferPause);
            } else {
                for (Task child : getChildTasks()) {
                    if (child.isTopActivityFocusable()) {
                        someActivityResumed |= child.resumeTopActivityUncheckedLocked(
                                prev, options, deferPause);
                    }
                }
            }
        } finally {
            mInResumeTopActivity = false;
        }

        return someActivityResumed;
    }

    private boolean resumeTopActivityInnerLocked(ActivityRecord prev, 
                                                ActivityOptions options,
                                                boolean deferPause) {
        final ActivityRecord topActivity = topRunningActivity(true);
        if (topActivity == null) {
            return resumeNextFocusableActivityWhenRootTaskIsEmpty(prev, options);
        }

        final TaskFragment topFragment = topActivity.getTaskFragment();
        resumed[0] = topFragment.resumeTopActivity(prev, options, deferPause);
        return resumed[0];
    }
}

Activity Lifecycle Coordination

The TaskFragment.resumeTopActivity() method orchestrates the pause/resume sequence:

class TaskFragment {
    
    final boolean resumeTopActivity(ActivityRecord prev, 
                                    ActivityOptions options,
                                    boolean deferPause) {
        ActivityRecord next = topRunningActivity(true);
        if (next == null || !next.canResumeByCompat()) {
            return false;
        }

        // Skip if already resumed
        if (mResumedActivity == next && next.isState(RESUMED)
                && taskDisplayArea.allResumedActivitiesComplete()) {
            executeAppTransition(options);
            return false;
        }

        // Wait for current pause to complete
        if (!mRootWindowContainer.allPausedActivitiesComplete()) {
            return false;
        }

        boolean pausing = !deferPause && taskDisplayArea.pauseBackTasks(next);
        
        if (mResumedActivity != null) {
            pausing |= startPausing(mTaskSupervisor.mUserLeaving, 
                                    false, next, "resumeTopActivity");
        }

        if (pausing) {
            // Update process priority while waiting
            if (next.attachedToProcess()) {
                next.app.updateProcessInfo(true, true, false, false);
            } else if (!next.isProcessRunning()) {
                final boolean isTop = this == taskDisplayArea.getFocusedRootTask();
                mAtmService.startProcessAsync(next, false, isTop, 
                        isTop ? "pre-top-activity" : "pre-activity");
            }
            return true;
        }

        // Activity already attached and running
        if (next.attachedToProcess()) {
            next.setState(RESUMED, "resumeTopActivity");
            
            // Send transaction to client
            final ClientTransaction transaction = ClientTransaction.obtain(
                    next.app.getThread(), next.appToken);
            transaction.addCallback(LaunchActivityItem.obtain(
                    new Intent(next.intent),
                    System.identityHashCode(next),
                    next.info,
                    mergedConfiguration.getGlobalConfiguration(),
                    mergedConfiguration.getOverrideConfiguration(),
                    next.compat,
                    task.voiceInteractor,
                    proc.getReportedProcState(),
                    next.getSavedState(),
                    next.getPersistentSavedState(),
                    results, newIntents, next.takeOptions(),
                    isTransitionForward,
                    proc.createProfilerInfoIfNeeded(),
                    next.assistToken,
                    activityClientController));

            final ActivityLifecycleItem lifecycleItem = 
                    andResume ? 
                    ResumeActivityItem.obtain(isTransitionForward) : 
                    PauseActivityItem.obtain();
            
            transaction.setLifecycleStateRequest(lifecycleItem);
            mService.getLifecycleManager().scheduleTransaction(transaction);
            
            return true;
        }

        // Need to start the activity (process may not exist yet)
        if (!next.hasBeenLaunched) {
            next.hasBeenLaunched = true;
        }
        mTaskSupervisor.startSpecificActivity(next, true, true);
        return true;
    }
}

Process Launch and Activity Binding

When the target process doesn't exist, ActivityTaskSupervisor.startSpecificActivity() intiiates the process:

public class ActivityTaskSupervisor {
    
    void startSpecificActivity(ActivityRecord r, 
                               boolean andResume, 
                               boolean checkConfig) {
        
        final WindowProcessController wpc =
                mService.getProcessController(r.processName, 
                                             r.info.applicationInfo.uid);

        if (wpc != null && wpc.hasThread()) {
            realStartActivityLocked(r, wpc, andResume, checkConfig);
            return;
        }

        r.notifyUnknownVisibilityLaunchedForKeyguardTransition();
        
        final boolean isTop = andResume && r.isTopRunningActivity();
        mService.startProcessAsync(r, false, isTop, 
                isTop ? "top-activity" : "activity");
    }
}

Once the process is established, realStartActivityLocked() sends the launch transaction to the client:

boolean realStartActivityLocked(ActivityRecord r, 
                                WindowProcessController proc,
                                boolean andResume, 
                                boolean checkConfig) 
        throws RemoteException {
    
    if (!mRootWindowContainer.allPausedActivitiesComplete()) {
        return false;
    }

    final Task task = r.getTask();
    beginDeferResume();
    proc.pauseConfigurationDispatch();

    try {
        r.startFreezingScreenLocked(proc, 0);
        r.setProcess(proc);

        // Create activity launch transaction
        final ClientTransaction clientTransaction = ClientTransaction.obtain(
                proc.getThread(), r.appToken);

        clientTransaction.addCallback(LaunchActivityItem.obtain(
                new Intent(r.intent),
                System.identityHashCode(r),
                r.info,
                mergedConfiguration.getGlobalConfiguration(),
                mergedConfiguration.getOverrideConfiguration(),
                r.compat,
                r.getFilteredReferrer(r.launchedFromPackage),
                task.voiceInteractor,
                proc.getReportedProcState(),
                r.getSavedState(),
                r.getPersistentSavedState(),
                results, newIntents, r.takeOptions(),
                isTransitionForward,
                proc.createProfilerInfoIfNeeded(),
                r.assistToken,
                activityClientController,
                r.createFixedRotationAdjustmentsIfNeeded(),
                r.shareableActivityToken,
                r.getLaunchedFromBubble()));

        final ActivityLifecycleItem lifecycleItem;
        if (andResume) {
            lifecycleItem = ResumeActivityItem.obtain(isTransitionForward);
        } else {
            lifecycleItem = PauseActivityItem.obtain();
        }
        clientTransaction.setLifecycleStateRequest(lifecycleItem);

        mService.getLifecycleManager().scheduleTransaction(clientTransaction);

    } finally {
        endDeferResume();
        proc.resumeConfigurationDispatch();
    }

    if (andResume && readyToResume()) {
        rootTask.minimalResumeActivityLocked(r);
    }

    return true;
}

Component Chain Overview

The activity launch mechanism involves these key components:

Component Responsibility
Activity Public API entry point
Instrumentation Intercepts and forwards calls to system
ActivityTaskManagerService Entry point in system server
ActivityStartController Factory for ActivityStarter
ActivityStarter Resolves intent and validates launch
RootWindowContainer Manages display-level activity scheduling
Task Manages activity stack and resume ordering
TaskFragment Coordinates pause/resume sequence
ActivityTaskSupervisor Oversees activity lifecycle and process management

Summary

The startActivity call traverses multiple layers: from the application through Instrumentation, across IPC to ActivityTaskManagerService, through ActivityStarter for resolution and validation, then into the window management subsystem with RootWindowContainer and Task, before finally reaching TaskFragment which coordinates the actual pause/resume lifecycle transition. The final launch instruction is packaged as a ClientTransaction and sent to the target application's ActivityThread for execution.

Tags: Android

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.