Android Source Reading Analysis: Activity Manager Service Analysis (2) - Activity Management

Keywords: Java Android Windows

Activity Manager Service Analysis (2) - Activity Management

(Note: The source code is android-8.1)

0. Preface

In the article Android Source Reading Analysis: Activity Manager Service Analysis (1) - Start Process In this paper, we analyzed how the Activity Manager Service was created and started, and the initialization operation after starting. As mentioned earlier, Activity Manager Service is the core of managing the four components. So, this article begins with the logic of Activity Manager Service to manage Activity.
  

1. Start Activity

I'm writing about this process. Android Source Reading Analysis: Activity Manager Service Analysis (1) - Start Process Some brief code tracking analysis has been done. Here's a more detailed analysis of what the Activity Manager Service did when it started the activity operation. Because the method invocation process has been tracked in the previous article, then this article focuses on the core method analysis.
First, the ActivityStarter.startActivity method is analyzed.

(frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java)

private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
            ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
            ActivityRecord[] outActivity, TaskRecord inTask) {
    int err = ActivityManager.START_SUCCESS;
    ...
    ProcessRecord callerApp = null;
    if (caller != null) {
        callerApp = mService.getRecordForAppLocked(caller);
        // Inadequate memory may result in the killing of the initiator's process.
        if (callerApp != null) {
            callingPid = callerApp.pid;
            callingUid = callerApp.info.uid;
        } else {
            // If the process is killed, the new Activity cannot be started and the return privilege rejected
            err = ActivityManager.START_PERMISSION_DENIED;
        }
    }
    ...
    // Activation to start the current Activity
    ActivityRecord sourceRecord = null;
    // Activity that receives the returned result
    ActivityRecord resultRecord = null;
    if (resultTo != null) {
        sourceRecord = mSupervisor.isInAnyStackLocked(resultTo);
        if (sourceRecord != null) {
            if (requestCode >= 0 && !sourceRecord.finishing) {
                // If the result needs to be returned, the resultRecord is set to sourceRecord
                resultRecord = sourceRecord;
            }
        }
    }

    final int launchFlags = intent.getFlags();

    if ((launchFlags & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) {
        if (requestCode >= 0) {
            // FLAG_ACTIVITY_FORWARD_RESULT and requestCode>= 0 cannot be set simultaneously
            // Because when FLAG_ACTIVITY_FORWARD_RESULT is set up, the newly started Activity passes the result back to the source Activity of Activity initiated by the operation.
            ActivityOptions.abort(options);
            return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT;
        }
        // The result returns to the originator's source Activity
        resultRecord = sourceRecord.resultTo;
        if (resultRecord != null && !resultRecord.isInStackLocked()) {
            resultRecord = null;
        }
        resultWho = sourceRecord.resultWho;
        requestCode = sourceRecord.requestCode;
        sourceRecord.resultTo = null;
        if (resultRecord != null) {
            // After setting FLAG_ACTIVITY_FORWARD_RESULT, the Activity receiving the result can no longer hold the Activity receiving the result.
            resultRecord.removeResultsLocked(sourceRecord, resultWho, requestCode);
        }
        if (sourceRecord.launchedFromUid == callingUid) {
            callingPackage = sourceRecord.launchedFromPackage;
        }
    }
    if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) {
        // Active that needs to be started does not exist and returning intent cannot be resolved
        err = ActivityManager.START_INTENT_NOT_RESOLVED;
    }
    if (err == ActivityManager.START_SUCCESS && aInfo == null) {
        // Class not found
        err = ActivityManager.START_CLASS_NOT_FOUND;
    }
    ...
    final ActivityStack resultStack = resultRecord == null ? null : resultRecord.getStack();
    if (err != START_SUCCESS) {
        // If it fails to start successfully, the execution is abandoned.
        if (resultRecord != null) {
            resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, RESULT_CANCELED, null);
        }
        ActivityOptions.abort(options);
        return err;
    }
    // Check whether there is activation Activity privilege
    boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
            requestCode, callingPid, callingUid, callingPackage, ignoreTargetSecurity, callerApp,
            resultRecord, resultStack, options);
    abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid, callingPid, resolvedType, aInfo.applicationInfo);
    ...
    if (abort) {
        ...
        return START_ABORTED;
    }
    ...
    // Create the corresponding ActivityRecord for Activity that needs to be created
    ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid,
            callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(),
            resultRecord, resultWho, requestCode, componentSpecified, voiceSession != null,
            mSupervisor, options, sourceRecord);
    if (outActivity != null) {
        outActivity[0] = r;
    }
    ...
    // Get ActiveyStack that currently receives input or is starting Activity
    final ActivityStack stack = mSupervisor.mFocusedStack;
    if (voiceSession == null && (stack.mResumedActivity == null 
            || stack.mResumedActivity.info.applicationInfo.uid != callingUid)) {
        // If the Activity in the resumed state of the current ActivityStack is empty, the userId of the Activity in the resumed state is different from the userId that initiates the startActivity.
        // If the process cannot be switched by checking at this time, the execution of Create Activity is abandoned and returned.
        if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, realCallingPid, realCallingUid, "Activity start")) {
            PendingActivityLaunch pal =  new PendingActivityLaunch(r, sourceRecord, startFlags, stack, callerApp);
            mPendingActivityLaunches.add(pal);
            ActivityOptions.abort(options);
            return ActivityManager.START_SWITCHES_CANCELED;
        }
    }
    ...
    // Start pending Activeness, but the doResume parameter is false
    doPendingActivityLaunchesLocked(false);

    return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags, true, options, inTask, outActivity);
}

This method mainly does some permission checking.
The following is a detailed analysis of the ActivityStarter.startActivityUnchecked method.

(frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java)

private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
        IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
        int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
        ActivityRecord[] outActivity) {
    ...
    // Determine whether a new Activity needs to be put into an existing Task
    // If null is returned, the new Acitivity needs to be in the new Task of the method, otherwise Activity in the Task to be added is returned.
    ActivityRecord reusedActivity = getReusableIntentActivity();
    ...
    if (reusedActivity != null) {
        ...
        if (mStartActivity.getTask() == null) {
            mStartActivity.setTask(reusedActivity.getTask());
        }
        if (reusedActivity.getTask().intent == null) {
            reusedActivity.getTask().setIntent(mStartActivity);
        }
        if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
                || isDocumentLaunchesIntoExisting(mLaunchFlags)
                || mLaunchSingleInstance || mLaunchSingleTask) {
            final TaskRecord task = reusedActivity.getTask();
            // In this case, you need to remove the Activity above Activation that is being started in the stack.
            // In most cases, this means resetting Task to its initial state.
            final ActivityRecord top = task.performClearTaskForReuseLocked(mStartActivity, mLaunchFlags);
            // The previous operation may remove reusedActivity from Task, resulting in it
            if (reusedActivity.getTask() == null) {
                reusedActivity.setTask(task);
            }
            if (top != null) {
                if (top.frontOfTask) {
                    // Make sure Task has a new intent now
                    top.getTask().setIntent(mStartActivity);
                }
                deliverNewIntent(top);
            }
        }
        ...
        reusedActivity = setTargetStackAndMoveToFrontIfNeeded(reusedActivity);
        final ActivityRecord outResult = outActivity != null && outActivity.length > 0 ? outActivity[0] : null;
        if (outResult != null && (outResult.finishing || outResult.noDisplay)) {
            outActivity[0] = reusedActivity;
        }
        if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
            // There is no need to start a new Activity, and the client (that is, the initiator) specifies that nothing needs to be done in this case.
            // In this case, resume the target stack only if necessary
            resumeTargetStackIfNeeded();
            return START_RETURN_INTENT_TO_CALLER;
        }
        setTaskFromIntentActivity(reusedActivity);
        if (!mAddingToTask && mReuseTask == null) {
            // resume target stack if necessary
            resumeTargetStackIfNeeded();
            if (outActivity != null && outActivity.length > 0) {
                outActivity[0] = reusedActivity;
            }
            return START_TASK_TO_FRONT;
        }
    }
    if (mStartActivity.packageName == null) {
        final ActivityStack sourceStack = mStartActivity.resultTo != null ? mStartActivity.resultTo.getStack() : null;
        if (sourceStack != null) {
            sourceStack.sendActivityResultLocked(-1 /* callingUid */, mStartActivity.resultTo,
                    mStartActivity.resultWho, mStartActivity.requestCode, RESULT_CANCELED,
                    null /* data */);
        }
        ActivityOptions.abort(mOptions);
        return START_CLASS_NOT_FOUND;
    }

    // If the Activation being started is the same as the Activation on the top of the stack now, you need to check if the Activity can only be started once.
    final ActivityStack topStack = mSupervisor.mFocusedStack;
    final ActivityRecord topFocused = topStack.topActivity();
    final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);
    final boolean dontStart = top != null && mStartActivity.resultTo == null
            && top.realActivity.equals(mStartActivity.realActivity)
            && top.userId == mStartActivity.userId
            && top.app != null && top.app.thread != null
            && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
            || mLaunchSingleTop || mLaunchSingleTask);
    if (dontStart) {
        // Ensure Activeness on the top of the correct resume stack
        topStack.mLastPausedActivity = null;
        if (mDoResume) {
            mSupervisor.resumeFocusedStackTopActivityLocked();
        }
        ...
        return ...
    }

    boolean newTask = false;
    final TaskRecord taskToAffiliate = (mLaunchTaskBehind && mSourceRecord != null) ? mSourceRecord.getTask() : null;

    // Determine whether a new Task is needed
    int result = START_SUCCESS;
    if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
        newTask = true;
        result = setTaskFromReuseOrCreateNewTask(taskToAffiliate, preferredLaunchStackId, topStack);
    } else if (mSourceRecord != null) {
        result = setTaskFromSourceRecord();
    } else if (mInTask != null) {
        result = setTaskFromInTask();
    } else {
        setTaskToCurrentTopOrCreateNewTask();
    }
    if (result != START_SUCCESS) {
        return result;
    }
    ...
    // Start a new Activity
    mTargetStack.startActivityLocked(mStartActivity, topFocused, newTask, mKeepCurTransition, mOptions);
    if (mDoResume) {
        final ActivityRecord topTaskActivity = mStartActivity.getTask().topRunningActivityLocked();
        if (!mTargetStack.isFocusable()
                || (topTaskActivity != null && topTaskActivity.mTaskOverlay
                && mStartActivity != topTaskActivity)) {
            // 1. If the Activity has no focus, then it can't resume, but still confirm its visibility.
            // 2. If the current Task has coverage, the Activity startup needs to be in a visible pause state until the coverage is removed.
            mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
            mWindowManager.executeAppTransition();
        } else {
            // If the target Task is not focused before, then the Task is focused.
            if (mTargetStack.isFocusable() && !mSupervisor.isFocusedStack(mTargetStack)) {
                mTargetStack.moveToFront("startActivityUnchecked");
            }
            mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity, mOptions);
        }
    } else {
        mTargetStack.addRecentActivityLocked(mStartActivity);
    }
    // Update the latest used stack id for non-current users
    // The latest usage stack for current users is the focus stack.
    mSupervisor.updateUserStackLocked(mStartActivity.userId, mTargetStack);
    mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTask(), preferredLaunchStackId, preferredLaunchDisplayId, mTargetStack.mStackId);
    return START_SUCCESS;
}

The main function of this method is to manage the activity stack and tasks.

Look again at the ActivityStack.startActivityLocked method.

(frameworks/base/services/core/java/com/android/server/am/ActivityStack.java)

final void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
        boolean newTask, boolean keepCurTransition, ActivityOptions options) {
    TaskRecord rTask = r.getTask();
    final int taskId = rTask.taskId;

    if (!r.mLaunchTaskBehind && (taskForIdLocked(taskId) == null || newTask)) {
        // The latest Active has been removed, or Activity ManangerService is reusing Task, so insert or replace it
        insertTaskAtTop(rTask, r);
    }
    TaskRecord task = null;
    if (!newTask) {
        // Start in an existing Task, first find which Task is
        boolean startIt = true;
        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
            task = mTaskHistory.get(taskNdx);
            if (task.getTopActivity() == null) {
                // All activities of the Task are in the finishing state
                continue;
            }
            if (task == rTask) {
                // This stack is the stack that needs to start Activity
                // If it is not currently visible, add the Activity only to the stack, but not start
                // When the user returns to the Activity, it starts
                if (!startIt) {
                    r.createWindowContainer();
                    ActivityOptions.abort(options);
                    return;
                }
                break;
            } else if (task.numFullscreen > 0) {
                startIt = false;
            }
        }
    }

    // Place a new Activity on the top of the stack, and then interact with the user

    // If we don't put this new Activity at the front, then don't call the onUser Leaving callback that is actually the front Active
    final TaskRecord activityTask = r.getTask();
    if (task == activityTask && mTaskHistory.indexOf(task) != (mTaskHistory.size() - 1)) {
        mStackSupervisor.mUserLeaving = false;
    }
    ...
    if (r.getWindowContainerController() == null) {
        r.createWindowContainer();
    }
    // Make sure frontOfTask is set correctly
    task.setFrontOfTask();
    if (!isHomeOrRecentsStack() || numActivities() > 0) {
        ...
        // Ready to start transition animation
        if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
            mWindowManager.prepareAppTransition(TRANSIT_NONE, keepCurTransition);
            mNoAnimActivities.add(r);
        } else {
            int transit = TRANSIT_ACTIVITY_OPEN;
            if (newTask) {
                if (r.mLaunchTaskBehind) {
                    transit = TRANSIT_TASK_OPEN_BEHIND;
                } else {
                    if (canEnterPipOnTaskSwitch(focusedTopActivity, null /* toFrontTask */, r, options)) {
                        focusedTopActivity.supportsEnterPipOnTaskSwitch = true;
                    }
                    transit = TRANSIT_TASK_OPEN;
                }
            }
            mWindowManager.prepareAppTransition(transit, keepCurTransition);
            mNoAnimActivities.remove(r);
        }
        boolean doShow = true;
        if (newTask) {
            if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
                resetTaskIfNeededLocked(r, r);
                doShow = topRunningNonDelayedActivityLocked(null) == r;
            }
        } else if (options != null && options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
            doShow = false;
        }
        if (r.mLaunchTaskBehind) {
            // If Activity's mLauchTaskBehind is started, then window is not started
            // Make sure Activeness that notifies Windows Manager to start is visible even if it is not on the top of the stack
            r.setVisibility(true);
            ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
        } else if (SHOW_APP_STARTING_PREVIEW && doShow) {
            TaskRecord prevTask = r.getTask();
            ActivityRecord prev = prevTask.topRunningActivityWithStartingWindowLocked();
            if (prev != null) {
                if (prev.getTask() != prevTask) {
                    prev = null;
                }
                else if (prev.nowVisible) {
                    prev = null;
                }
            }
            r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity));
        }
    } else {
        // If this is the first Activity, don't animate anything.
        ActivityOptions.abort(options);
    }
}

The main functions of this method are stacking operation and the determination of transition animation category.

2. Destroy Activity

When an Activity completes its mission, we usually call the finish method to destroy the Activity.

(frameworks/base/core/java/android/app/Activity.java)

public void finish() {
    finish(DONT_FINISH_TASK_WITH_ACTIVITY);
}

private void finish(int finishTask) {
    ...
    if (ActivityManager.getService().finishActivity(mToken, resultCode, resultData, finishTask)) {
        mFinished = true;
    }
    ...
}

Calling finishDestruction Activity calls the finishActivity method of ActivityManagerService.

(frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java)

@Override
public final boolean finishActivity(IBinder token, int resultCode, Intent resultData, int finishTask) {
    ...
    synchronized(this) {
        ActivityRecord r = ActivityRecord.isInStackLocked(token);
        ...
        TaskRecord tr = r.getTask();
        ActivityRecord rootR = tr.getRootActivity();
        ...
        try {
            boolean res;
            final boolean finishWithRootActivity = finishTask == Activity.FINISH_TASK_WITH_ROOT_ACTIVITY;
            if (finishTask == Activity.FINISH_TASK_WITH_ACTIVITY || (finishWithRootActivity && r == rootR)) {
                // The Task will only be removed if the Activity to be destroyed is the root activity of the corresponding Task.
                // Ignore the execution result because it does not support passing results across Task s
                res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, finishWithRootActivity);
                ...
            } else {
                res = tr.getStack().requestFinishActivityLocked(token, resultCode, resultData, "app-request", true);
                ...
            }
            return res;
        }
        ...
    }
}

This method first determines whether the Task needs to be terminated. Eventually, the ActivityStack.finishActivityLocked method is called.

(frameworks/base/services/core/java/com/android/server/am/ActivityStack.java)

final boolean finishActivityLocked(ActivityRecord r, int resultCode, Intent resultData, String reason, boolean oomAdj, boolean pauseImmediately) {
    ...
    final TaskRecord task = r.getTask();
    ...
    if (mResumedActivity == r) {
        ...

    } else if (r.state != ActivityState.PAUSING) {
        ...
        // Notify window manager to remove the Activity
        r.setVisibility(false);
        if (mPausingActivity == null) {
            ...
            // First pause the Activity
            startPausingLocked(false, false, null, pauseImmediately);
        }
        ...
    } else if (r.state != ActivityState.PAUSING) {
        // If Activity is in PAUSING state, you need to finish after pause.
        ...
        final int finishMode = (r.visible || r.nowVisible) ? FINISH_AFTER_VISIBLE : FINISH_AFTER_PAUSE;
        final boolean removedActivity = finishCurrentActivityLocked(r, finishMode, oomAdj) == null;
        ...
    }
    ...
}

final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, int mode, boolean oomAdj) {
    // If the Activity is currently visible and resumed Activity is not yet visible,
    // So defer finish ing until resumed Activity becomes visible
    final ActivityRecord next = mStackSupervisor.topRunningActivityLocked();
    if (mode == FINISH_AFTER_VISIBLE && (r.visible || r.nowVisible) && next != null && !next.nowVisible) {
        if (!mStackSupervisor.mStoppingActivities.contains(r)) {
            addToStopping(r, false /* scheduleIdle */, false /* idleDelayed */);
        }
        ...
        r.state = STOPPING;
        if (oomAdj) {
            mService.updateOomAdjLocked();
        }
        return r;
    }

    // Ensure that the activity is cleared elsewhere
    mStackSupervisor.mStoppingActivities.remove(r);
    mStackSupervisor.mGoingToSleepActivities.remove(r);
    mStackSupervisor.mActivitiesWaitingForVisibleActivity.remove(r);
    if (mResumedActivity == r) {
        mResumedActivity = null;
    }
    final ActivityState prevState = r.state;
    final boolean finishingActivityInNonFocusedStack
            = r.getStack() != mStackSupervisor.getFocusedStack()
            && prevState == ActivityState.PAUSED && mode == FINISH_AFTER_VISIBLE;
    if (mode == FINISH_IMMEDIATELY
            || (prevState == ActivityState.PAUSED
                && (mode == FINISH_AFTER_PAUSE || mStackId == PINNED_STACK_ID))
            || finishingActivityInNonFocusedStack
            || prevState == STOPPING
            || prevState == STOPPED
            || prevState == ActivityState.INITIALIZING) {
        r.makeFinishingLocked();
        // Destroy Activity
        boolean activityRemoved = destroyActivityLocked(r, true, "finish-imm");
        ...
        if (activityRemoved) {
            mStackSupervisor.resumeFocusedStackTopActivityLocked();
        }
        ...
        return activityRemoved ? null : r;
    }
    ...
    mStackSupervisor.mFinishingActivities.add(r);
    r.resumeKeyDispatchingLocked();
    mStackSupervisor.resumeFocusedStackTopActivityLocked();
    return r;
}

final boolean destroyActivityLocked(ActivityRecord r, boolean removeFromApp, String reason) {
    ...
    final boolean hadApp = r.app != null;
    if (hadApp) {
        if (removeFromApp) {
            // Remove the Activity from the process
            ...
        }
        boolean skipDestroy = false;
        try {
            // Dealing with destruction operations in the application process, this method will call Activity's onPause, onStop, and onDestroy methods in succession.
            r.app.thread.scheduleDestroyActivity(r.appToken, r.finishing, r.configChangeFlags);
        }
        ...
        // If Activity is finishing, you need to defer removing it from the list for cleanup.
        // Otherwise, it is directly labeled as DESTROYING status.
        if (r.finishing && !skipDestroy) {
            r.state = ActivityState.DESTROYING;
            Message msg = mHandler.obtainMessage(DESTROY_TIMEOUT_MSG, r);
            mHandler.sendMessageDelayed(msg, DESTROY_TIMEOUT);
        } else {
            r.state = ActivityState.DESTROYED;
            r.app = null;
        }
    }
    ...
}

First pause the current Activity, then destroy the Activity after the next Activity enters the visible state.

3. summary

Activity Manager Service basically manages the activity through Activity Stack. The general process can be summarized in the following two figures.
  
Start Acitivity

Destroy Activity

Posted by test on Wed, 15 May 2019 13:39:01 -0700