1. Introduction

AMS(ActivityManagerService) is the core component of Activity management. It provides functions such as Activity startup, lifecycle management, and stack management. Being familiar with AMS will help us understand the working principle of Activity. The current more mature plug-in technology, but also through the Activity start process in the important components (such as Instrumentation or the main thread Handler) Hook to complete, master AMS to learn our plug-in technology also has a great help

There is a lot of content in AMS, and it is impossible to analyze it in all aspects. I expect to start from the aspects of Activity startup, Activity message return (onActivityResult), Activity stack management, and the cooperation between AMS and WMS and PMS. Hopefully, this series of articles will provide an updated understanding of AMS

2. Activity lifecycle

2.1. The cycle of an Activity from start to destruction

onCreate -> onStart -> onResume -> onPause -> onStop -> onDestory

This is the most basic knowledge of Android, not to repeat

2.2. The lifecycle of starting an Activity from another Activity

Now there are two activities, AActivity and BActivity. If you jump from AActivity to BActivity, their life cycle will look like this:

AActivity#onPause -> BActivity#onCreate -> BActivity#onStart -> BActivity#onResume -> AActivity#onStop

The diagram below:

When returned from BActivity, their lifecycle looks like this:

BActivity#onPause -> AActivity#onStart -> AActivity#onResume -> BActivity#onStop -> BActivity#onDestroy

Why is that? Let’s analyze it from the point of view of source code

3. Source code analysis

Before we do our source code analysis, we need to understand two concepts

  • Android Binder IPC mechanism
  • Hander idleHandler mechanism

3.1. Binder

Binder is at the heart of Android’s cross-process communication, but it is not the focus of this article. Binder series is the Binder series I wrote earlier

For the purposes of this article, we need only know that whenever we see a call like mremote.transact, it is the beginning of cross-process communication

3.2. IdleHandler

IdelHandler’s queueIdle() method is executed when the message queue is idle. This method returns a Boolean value that removes the message if it is false or holds it if it is true until it is idle again

That is, the IdleHandler is a special Handler that is executed when the MessageQueue is idle

3.3. Identification of Activity in AMS

Each Activity instance will correspond to a ActivityRecord in AMS, ActivityRecord object is created in the process of the Activity start, Within each ActivityRecord, a ProcessRecord marks the process in which the Activity is located

In the APP process, an ActivityClientRecord is also created when the Activity starts, which corresponds to the ActivityRecord in AMS.

The link between Activity, ActivityClientRecord, and ActivityRecord is a token object that inherits from iApplicationToke. Stub, a Binder object, There is a copy in Activity, ActivityClientRecord, and ActivityRecord.

The diagram for Activity, ActivityClientRecord, and ActivityRecord is as follows:

3.4. Sequence diagram of startup process

The yellow is APP process and the blue is AMS process

3.5. Life cycle analysis from AActivity to BActivity

AMS has too much logic, this article only for Activity declaration cycle related code, other related logic will continue to analyze in the next few articles

Starting from the Activity

    public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
                                       @Nullable Bundle options) {
        if (mParent == null) {
            options = transferSpringboardActivityOptions(options);
            Instrumentation.ActivityResult ar =
                    mInstrumentation.execStartActivity(
                            this, mMainThread.getApplicationThread(), mToken, this,
                            intent, requestCode, options);
           
        } else {
   
        }
    }
Copy the code

Instrumentation# execStartActivity method:

public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { ... try { intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(who); int result = ActivityManager.getService() .startActivity(whoThread, who.getBasePackageName(), 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; }Copy the code

Important Parameters and Meanings:

  • Who: starts the source Activity
  • ContextThread: the ApplicationThread of the host Activity
  • Token: The Activity id -> ActivityClientRecord#token -> ActivityRecord#appToken -> iapplicationtoke.stub
  • Target: The start method that is invoked by which activity, so the activity will receive any return result, possibly null if the start call was not initiated from the activity
  • Intent: An intent that starts an Activity
  • RequestCoode: Specifies the requestCode to start the Activity
  • Options: Options for starting the Activity

Note the token parameter, which this article will focus on tracking, and which is very important and will be used in many places

Continuing with the logic, an IPC call is made here, from the APP process to the AMS process, ActivityManager. GetService () call startActivity in AMS process is corresponding ActivityManagerService# startActivity, we take a look at

ActivityManagerService#startActivity after a series of calls leads to ActivityStarter#startActivityMayWait

final int startActivityMayWait(IApplicationThread caller, int callingUid, int requestRealCallingPid, int requestRealCallingUid, String callingPackage, Intent intent, String resolvedType, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, WaitResult outResult, Configuration globalConfig, Bundle bOptions, boolean ignoreTargetSecurity, int userId, TaskRecord inTask, String reason) { ... // Collect information about the target of the Intent. // Zhangyulong parse the Activity information in the Intent ActivityInfo aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, profilerInfo); . // Zhangyulong declare an ActivityRecord array Final ActivityRecord[] outRecord = new ActivityRecord[1]; int res = startActivityLocked(caller, intent, ephemeralIntent, resolvedType, aInfo, rInfo, voiceSession, voiceInteractor, resultTo, resultWho, requestCode, callingPid, callingUid, callingPackage, realCallingPid, realCallingUid, startFlags, options, ignoreTargetSecurity, componentSpecified, outRecord, inTask, reason); Binder.restoreCallingIdentity(origId); . return res; }}Copy the code

What you do here is parse the Intent and continue to call startActivityLocked. Note that the resultTo is the token passed in from the App process, and is still transparently passed to startActivityLocked

int startActivityLocked(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, String reason) { if (TextUtils.isEmpty(reason)) { throw new IllegalArgumentException("Need to specify a reason."); } mLastStartReason = reason; mLastStartActivityTimeMs = System.currentTimeMillis(); mLastStartActivityRecord[0] = null; mLastStartActivityResult = startActivity(caller, intent, ephemeralIntent, resolvedType, aInfo, rInfo, voiceSession, voiceInteractor, resultTo, resultWho, requestCode, callingPid, callingUid, callingPackage, realCallingPid, realCallingUid, startFlags, options, ignoreTargetSecurity, componentSpecified, mLastStartActivityRecord, inTask); if (outActivity ! = null) { // mLastStartActivityRecord[0] is set in the call to startActivity above. outActivity[0] = mLastStartActivityRecord[0]; } // Aborted results are treated as successes externally, but we must track them internally. return mLastStartActivityResult ! = START_ABORTED ? mLastStartActivityResult : START_SUCCESS; }Copy the code

This method also doesn’t do much, passing parameters transparently to startActivity to continue execution

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; // Pull the optional Ephemeral Installer-only bundle out of the options early. final Bundle verificationBundle = options ! = null ? options.popAppVerificationBundle() : null; ProcessRecord callerApp = null; if (caller ! = null) { callerApp = mService.getRecordForAppLocked(caller); if (callerApp ! = null) { callingPid = callerApp.pid; callingUid = callerApp.info.uid; } else { Slog.w(TAG, "Unable to find app for caller " + caller + " (pid=" + callingPid + ") when starting: " + intent.toString()); err = ActivityManager.START_PERMISSION_DENIED; } } final int userId = aInfo ! = null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0; if (err == ActivityManager.START_SUCCESS) { Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false) + "} from uid " + callingUid); } ActivityRecord sourceRecord = null; ActivityRecord resultRecord = null; if (resultTo ! = null) {resultTo find ActivityRecord in AMS, The corresponding sourceRecord ActivityRecord and start the Activity of the source Activity = mSupervisor. IsInAnyStackLocked (resultTo); if (DEBUG_RESULTS) Slog.v(TAG_RESULTS, "Will send result to " + resultTo + " " + sourceRecord); if (sourceRecord ! = null) { if (requestCode >= 0 && ! sourceRecord.finishing) { resultRecord = sourceRecord; } } } final int launchFlags = intent.getFlags(); // Create a new ActivityRecord for the target Activity, 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; } return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags, true, options, inTask, outActivity); }Copy the code

Remember the ActivityRecord, ActivityClientRecord, and Activity diagrams we drew before analyzing the source code? ResultTo is the token that is passed when the Activity is started. Therefore, in AMS, there must be an ActivityRecord corresponding to it. Through mSupervisor. IsInAnyStackLocked found and corresponding ActivityRecord AActivity (resultTo).

Another important task of this method is to create an ActivityRecord that corresponds to the Activity to be opened, that is, the BActivity. When this is done, continue with startActivity.

StartActivity continues to call startActivityUnchecked. The most important function of this method is to process the Acitivity stack and the Activity startup mode. It is very long and complicated logic, but it is not the focus of this article, so we will focus on the analysis in later articles. For now, focus only on the parts of the Activity launch process

private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask, ActivityRecord[] outActivity) { ... if (mDoResume) { final ActivityRecord topTaskActivity = mStartActivity.getTask().topRunningActivityLocked(); if (! mTargetStack.isFocusable() || (topTaskActivity ! = null && topTaskActivity.mTaskOverlay && mStartActivity ! = topTaskActivity)) { mTargetStack.ensureActivitiesVisibleLocked(null, 0, ! PRESERVE_WINDOWS); mWindowManager.executeAppTransition(); } else { if (mTargetStack.isFocusable() && ! mSupervisor.isFocusedStack(mTargetStack)) { mTargetStack.moveToFront("startActivityUnchecked"); } mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity, mOptions); } } else { mTargetStack.addRecentActivityLocked(mStartActivity); }... return START_SUCCESS; }Copy the code
3.5.1 track of. AActivity# onPause

Look at mSupervisor. ResumeFocusedStackTopActivityLocked (mTargetStack mStartActivity, mOptions), here, ActivityRecord mStartActivity is imminent, mTargetStack is calculated in this way it’s ActivityStack, continue to see resumeFocusedStackTopActivityLocked

Remember the life-cycle process of jumping from AActivity to BActivity from the beginning? Don’t remember it doesn’t matter, review first: AActivity#onPause -> BActivity#onCreate -> BActivity#onStart -> BActivity#onResume -> AActivity#onStop, In resumeFocusedStackTopActivityLocked this method we will come into contact with the first statement in the process of the cycle method, namely AActivity# onPause, and this method is called process itself is extremely complex, it is important to parse, first look at the code:

private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) { if (! mService.mBooting && ! mService.mBooted) { // Not ready yet! return false; } // Find the next top-most activity to resume in this stack that is not finishing and is // focusable. If it is not focusable, We will fall into the case below to resume the // Top activity in the next focusable task ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */); final boolean hasRunningActivity = next ! = null; // TODO: Maybe this entire condition can get removed? If (hasRunningActivity && getDisplay() == null) {// Zhangyulong returns false if (hasRunningActivity && getDisplay() == null) {// Zhangyulong returns false; } mStackSupervisor.cancelInitializingActivities(); // Remember how we'll process this pause/resume situation, and ensure // that the state is reset however we wind up proceeding. final boolean userLeaving = mStackSupervisor.mUserLeaving; mStackSupervisor.mUserLeaving = false; if (! HasRunningActivity) {// There are no activities left in the stack, let's look somewhere else. // Zhangyulong task returns clean, The restoration of other task return resumeTopActivityInNextFocusableStack (prev, options, "noMoreActivities"); } next.delayedResume = false; . / / the Activity in the stack has the need to Pause operation Activity Boolean pausing = mStackSupervisor. PauseBackStacks (userLeaving, next, false); if (mResumedActivity ! = null) {/ / pause the currently displayed Activity pausing | = startPausingLocked (userLeaving, false, the next, false); } if (pausing && ! resumeWhilePausing) { if (next.app ! = null && next.app.thread ! = null) { mService.updateLruProcessLocked(next.app, true, null); } if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); return true; }... ActivityStack lastStack = mStackSupervisor.getLastStack(); if (next.app ! = null && next.app.thread ! = null) { ... } else { // Whoops, need to restart this activity! if (! next.hasBeenLaunched) { next.hasBeenLaunched = true; } else { if (SHOW_APP_STARTING_PREVIEW) { next.showStartingWindow(null /* prev */, false /* newTask */, false /* taskSwich */); } if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next); } if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Restarting " + next); mStackSupervisor.startSpecificActivityLocked(next, true, true); } if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); return true; }Copy the code

This method is executed twice, the first time it is entered, final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */); We get the Activity at the top of the stack. Before we execute this method, we have pushed the target ActivityRecord onto the stack, so we get the target ActivityRecord to open, mStackSupervisor.pauseBackStacks(userLeaving, next, false); Check whether there are any activities in ActivityStack that need to be paused. If there are any activities in ActivityStack that need to be paused, perform pause. By calling startPausingLocked(userLeaving, false, next, false); Pause an Activity that has not been paused, and return after the Activity is paused. Take a look at startPausingLocked:

final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping, ActivityRecord resuming, Boolean pauseImmediately) {// Assign prev to mResumedActivity and set mResumedActivity null ActivityRecord prev = mResumedActivity; mResumedActivity = null; mPausingActivity = prev; if (prev.app ! = null && prev.app.thread ! = null) { if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueueing pending pause: " + prev); try { EventLog.writeEvent(EventLogTags.AM_PAUSE_ACTIVITY, prev.userId, System.identityHashCode(prev), prev.shortComponentName); mService.updateUsageStats(prev, false); / / for Pause operation prev. App. Thread. SchedulePauseActivity (prev. AppToken, prev. Finishing, userLeaving, prev.configChangeFlags, pauseImmediately); } catch (Exception e) { // Ignore exception, if process died other code will cleanup. Slog.w(TAG, "Exception thrown during pause", e); mPausingActivity = null; mLastPausedActivity = null; mLastNoHistoryActivity = null; } } else { mPausingActivity = null; mLastPausedActivity = null; mLastNoHistoryActivity = null; } if (mPausingActivity ! = null) { if (! uiSleeping) { prev.pauseKeyDispatchingLocked(); } else if (DEBUG_PAUSE) { Slog.v(TAG_PAUSE, "Key dispatch not paused for screen off"); } if (pauseImmediately) { completePauseLocked(false, resuming); return false; } else {500ms delay schedulePauseTimeout(prev); return true; } } else { if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Activity not running, resuming next."); if (resuming == null) { mStackSupervisor.resumeFocusedStackTopActivityLocked(); } return false; }}Copy the code

MResumedActivity is the Activity will be Pause, through prev. App. Thread. SchedulePauseActivity (prev. AppToken, prev. Finishing, userLeaving, prev.configChangeFlags, pauseImmediately); Therefore, at the end of the method, a 500ms delay message is added to prevent the Pause operation timeout. Of course, this delay message may not be executed, but will be cancelled if the Pause operation is completed within 500ms. If you’re familiar with the Activity startup process, We know that schedulePauseActivity must eventually execute to ApplicationThread#handlePauseActivity. This ApplicationThread is the Applicati of AActivity in this article’s scenario OnThread, take a look at the code:

private void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving, int configChanges, boolean dontReport, int seq) { ActivityClientRecord r = mActivities.get(token); if (DEBUG_ORDER) Slog.d(TAG, "handlePauseActivity " + r + ", seq: " + seq); if (! checkAndUpdateLifecycleSeq(seq, r, "pauseActivity")) { return; } if (r ! = null) { //Slog.v(TAG, "userLeaving=" + userLeaving + " handling pause of " + r); if (userLeaving) { performUserLeavingActivity(r); } r.activity.mConfigChangeFlags |= configChanges; // Actually call Activity#onPause performPauseActivity(Token, finished, R.isrehoneycomb (), "handlePauseActivity"); // Make sure any pending writes are now committed. if (r.isPreHoneycomb()) { QueuedWork.waitToFinish(); } // Tell the activity manager we have paused. if (! DontReport) {try {/ / notify the AMS Activity has been completed Pause ActivityManager. GetService () activityPaused (token); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } mSomeActivitiesChanged = true; }}Copy the code

Among them, PerformPauseActivity will eventually call AActivity onPause, will continue to execute down through the ActivityManager. GetService () activityPaused (token) notify the AMS process Activity has been completed Pause,

// ActivityManagerService public final void activityPaused(IBinder token) { final long origId = Binder.clearCallingIdentity(); synchronized(this) { ActivityStack stack = ActivityRecord.getStackLocked(token); if (stack ! = null) { stack.activityPausedLocked(token, false); } } Binder.restoreCallingIdentity(origId); }Copy the code

Continue to see ActivityStack# activityPausedLocked

final void activityPausedLocked(IBinder token, boolean timeout) { ... final ActivityRecord r = isInStackLocked(token); if (r ! Mhandler. removeMessages(PAUSE_TIMEOUT_MSG, r); if (mPausingActivity == r) { if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to PAUSED: " + r + (timeout ? " (due to timeout)" : " (pause complete)")); mService.mWindowManager.deferSurfaceLayout(); CompletePauseLocked (true /* resumeNext */, null /* resumingActivity */); } finally { mService.mWindowManager.continueSurfaceLayout(); } return; }... }Copy the code

Two more important operations:

  • Cancel Pause delay wait message message
  • Follow-up operations for Pause are complete

The follow-up to Pause is done in ActivityStack#completePauseLocked

private void completePauseLocked(boolean resumeNext, ActivityRecord resuming) { ActivityRecord prev = mPausingActivity; . if (prev ! = null) { ... / / will have been Pause Activity to join mStackSupervisor. AddToStopping mStoppingActivities list (prev scheduleIdle true / * * /, false /* idleDelayed */); . } if (resumeNext) { final ActivityStack topStack = mStackSupervisor.getFocusedStack(); if (! TopStack. ShouldSleepOrShutDownActivities ()) {/ / Resume stack Activity, Prev is currently in pause mStackSupervisor resumeFocusedStackTopActivityLocked (topStack, prev, null); } else { checkReadyForSleep(); ActivityRecord top = topStack.topRunningActivityLocked(); if (top == null || (prev ! = null && top ! = prev)) { mStackSupervisor.resumeFocusedStackTopActivityLocked(); }}}... }Copy the code

Focus on mStackSupervisor. ResumeFocusedStackTopActivityLocked (topStack, prev, null); For a walk, back to the origin, we are on the verge of a second call ActivityStack# resumeTopActivityInnerLocked, this time with the last time is different, because have finished active Activity pause, therefore, Determine whether need to Pause the branch statement won’t perform, continue to execute down to mStackSupervisor. StartSpecificActivityLocked (next, true, true); , this method still has an important thing is to have been Pause Activity to join mStackSupervisor. MStoppingActivities list, this is an ArrayList, as for when to use, the talk about

3.5.2. BActivity# onCreate

At this point, we just finished the Pause of AActivity, some friends must be worried, that has to analyze the time ah, fast fast, soon after, speed up the progress, Then see ActivityStackSupervisor# startSpecificActivityLocked

void startSpecificActivityLocked(ActivityRecord r, boolean andResume, Boolean checkConfig) {/ / target Activity process information ProcessRecord app. = mService getProcessRecordLocked (r.p rocessName, r.info.applicationInfo.uid, true); r.getStack().setLaunchTime(r); if (app ! = null && app.thread ! = null) { try { if ((r.info.flags&ActivityInfo.FLAG_MULTIPROCESS) == 0 || !" android".equals(r.info.packageName)) { app.addPackage(r.info.packageName, r.info.applicationInfo.versionCode, mService.mProcessStats); } // StartActivity realStartActivityLocked(r, app, andResume, checkConfig); return; } catch (RemoteException e) { Slog.w(TAG, "Exception when starting activity " + r.intent.getComponent().flattenToShortString(), e); } // If a dead object exception was thrown -- fall through to restart the application mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0, "activity", r.intent.getComponent(), false, false, true); }Copy the code

The main job of this method is to assign a process to ActivityRecord. If there is no process, a new process is started. Jumping from AActivity to BActivity is the same process. So realStartActivityLocked(r, app, andResume, checkConfig) will continue:

    final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
            boolean andResume, boolean checkConfig) throws RemoteException {

            ...
                app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
                        System.identityHashCode(r), r.info,
                       
            ...
        return true;
    }
Copy the code

Method comparison is long, break off both ends only see app. Thread. ScheduleLaunchActivity, start from here into the app process execution, The scheduleLaunchActivity method corresponds to ApplicationThread#scheduleLaunchActivity:

public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
                ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
                CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
                int procState, Bundle state, PersistableBundle persistentState,
                List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
                boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {

            updateProcessState(procState, false);

            ActivityClientRecord r = new ActivityClientRecord();

            r.token = token;
            r.ident = ident;
            r.intent = intent;
            r.referrer = referrer;
            r.voiceInteractor = voiceInteractor;
            r.activityInfo = info;
            r.compatInfo = compatInfo;
            r.state = state;
            r.persistentState = persistentState;

            r.pendingResults = pendingResults;
            r.pendingIntents = pendingNewIntents;

            r.startsNotResumed = notResumed;
            r.isForward = isForward;

            r.profilerInfo = profilerInfo;

            r.overrideConfig = overrideConfig;
            updatePendingConfiguration(curConfig);

            sendMessage(H.LAUNCH_ACTIVITY, r);
        }
Copy the code

The ActivityClientRecord is created here, so the ActivityClientRecord corresponds to the ActivityRecord in AMS. Send the h.launch_activity message to the main thread and execute the handleLaunchActivity after the message callback:

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. ... Activity a = performLaunchActivity(r, customIntent); if (a ! = null) { .... handleResumeActivity(r.token, false, r.isForward, ! r.activity.mFinished && ! r.startsNotResumed, r.lastProcessedSeq, reason); . }... }Copy the code

Call performLaunchActivity here:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {/ / create the Context ContextImpl appContext = createBaseContextForActivity (r); Activity activity = null; Try {/ / create the Activity through this example Java lang. This cl = appContext. GetClassLoader (); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl); r.intent.prepareToEnterProcess(); if (r.state ! = null) { r.state.setClassLoader(cl); } } catch (Exception e) { if (! mInstrumentation.onException(activity, e)) { throw new RuntimeException( "Unable to instantiate activity " + component + ": " + e.toString(), e); } } try { Application app = r.packageInfo.makeApplication(false, mInstrumentation); if (activity ! = null) {/ / read the Activity information CharSequence title = state Richard armitage ctivityInfo. LoadLabel (appContext. GetPackageManager ()); Configuration config = new Configuration(mCompatConfiguration); if (r.overrideConfig ! = null) { config.updateFrom(r.overrideConfig); } if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity " + r.activityInfo.name + " with config " + config); Window window = null; if (r.mPendingRemoveWindow ! = null && r.mPreserveWindow) { window = r.mPendingRemoveWindow; r.mPendingRemoveWindow = null; r.mPendingRemoveWindowManager = null; } appContext.setOuterContext(activity); Attach (appContext, this, getInstrumentation(), R.toy, R.I.dent, app, R.I.ntent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.configCallback); if (customIntent ! = null) { activity.mIntent = customIntent; } r.lastNonConfigurationInstances = null; checkAndBlockForNetworkAccess(); activity.mStartedActivity = false; int theme = r.activityInfo.getThemeResource(); if (theme ! = 0) { activity.setTheme(theme); } activity.mCalled = false; / / the Activity onCreate perform the if (r.i sPersistable ()) {mInstrumentation. CallActivityOnCreate (Activity, r.s Tate, 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()"); } r.activity = activity; r.stopped = true; if (! R.activity. MFinished) {// Activity onStart executes activity.performStart(); r.stopped = false; } if (! r.activity.mFinished) { if (r.isPersistable()) { if (r.state ! = null || r.persistentState ! = null) { mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state, r.persistentState); } } else if (r.state ! = null) { mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state); } } } r.paused = true; mActivities.put(r.token, r); } catch (SuperNotCalledException e) { throw e; } catch (Exception e) { } return activity; }Copy the code

This method is where the Activity actually creates the instance and the onCreate executes. By calling the Activity# Attach method, the ActivityClientRecord token is associated with the Activity

3.5.3. BActivity# onStart

After onCreate is executed, activity.performStart() is called directly; Therefore, we can conclude that there is not much difference between the processing logic in the Activity’s onCreate and onStart methods

3.5.4. BActivity# onResume

The performLaunchActivity call has a return value, that is, the created Activity. If the return value is not empty, the Activity was successfully created and handleResumeActivity continues

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ActivityClientRecord r = mActivities.get(token); if (! checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) { return; }... // Execute onResume r = performResumeActivity(token, clearHide, Reason); . if (r ! = null) { final Activity a = r.activity; // Send a Idle message to messageQueue if (! r.onlyLocalRequest) { r.nextIdle = mNewActivities; mNewActivities = r; if (localLOGV) Slog.v( TAG, "Scheduling idle handler for " + r); Looper.myQueue().addIdleHandler(new Idler()); } r.onlyLocalRequest = false; / / notify the AMS completed Resume operation the if (reallyResume) {try {ActivityManager. GetService () activityResumed (token); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); }}}... }Copy the code

PerformResumeActivity is where the onResume of the Activity is executed

2.6.2. AActivity# onStop

Idler (Idler) {Idler (Idler) {Idler (Idler) {Idler (Idler) {Idler (Idler) {Idler (Idler);

private class Idler implements MessageQueue.IdleHandler { @Override public final boolean queueIdle() { ActivityClientRecord a = mNewActivities; boolean stopProfiling = false; if (mBoundApplication ! = null && mProfiler.profileFd ! = null && mProfiler.autoStopProfiler) { stopProfiling = true; } if (a ! = null) { mNewActivities = null; IActivityManager am = ActivityManager.getService(); ActivityClientRecord prev; do { if (localLOGV) Slog.v( TAG, "Reporting idle of " + a + " finished=" + (a.activity ! = null && a.activity.mFinished)); if (a.activity ! = null && ! A.activity. MFinished) {try {// call ActivityManagerService#activityIdle am. ActivityIdle (a.token, a.createdconfig, stopProfiling); a.createdConfig = null; } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } prev = a; a = a.nextIdle; prev.nextIdle = null; } while (a ! = null); } if (stopProfiling) { mProfiler.stopProfiling(); } ensureJitEnabled(); return false; }}Copy the code

Looking at am.activityIdle(A.token, A.createdConfig, stopProfiling) here, this call has been made many times in this article and we can easily conclude that: This is an IPC call, and the corresponding method is in ActivityManagerService#activityIdle:

@Override public final void activityIdle(IBinder token, Configuration config, boolean stopProfiling) { final long origId = Binder.clearCallingIdentity(); synchronized (this) { ActivityStack stack = ActivityRecord.getStackLocked(token); if (stack ! = null) { ActivityRecord r = mStackSupervisor.activityIdleInternalLocked(token, false /* fromTimeout */, false /* processPausingActivities */, config); if (stopProfiling) { if ((mProfileProc == r.app) && mProfilerInfo ! = null) { clearProfilerLocked(); } } } } Binder.restoreCallingIdentity(origId); }Copy the code

Work is not much, call the mStackSupervisor. ActivityIdleInternalLocked:

final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout, boolean processPausingActivities, Configuration config) { if (DEBUG_ALL) Slog.v(TAG, "Activity idle: " + token); . // Atomically retrieve all of the other things to do. final ArrayList<ActivityRecord> stops = processStoppingActivitiesLocked(r, true /* remove */, processPausingActivities); NS = stops ! = null ? stops.size() : 0; if ((NF = mFinishingActivities.size()) > 0) { finishes = new ArrayList<>(mFinishingActivities); mFinishingActivities.clear(); } if (mStartingUsers.size() > 0) { startingUsers = new ArrayList<>(mStartingUsers); mStartingUsers.clear(); }... // Stop any activities that are scheduled to do so but have been // waiting for the next one to start. for (int i = 0; i < NS; i++) { r = stops.get(i); final ActivityStack stack = r.getStack(); if (stack ! = null) { if (r.finishing) { stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false); } else { stack.stopActivityLocked(r); }}}... return r; }Copy the code

Is processStoppingActivitiesLocked do need to be Stop Activity and look into:

final ArrayList<ActivityRecord> processStoppingActivitiesLocked(ActivityRecord idleActivity, boolean remove, boolean processPausingActivities) { ArrayList<ActivityRecord> stops = null; final boolean nowVisible = allResumedActivitiesVisible(); For (int activityNdx = mstoppingActivities.size () -1; activityNdx >= 0; --activityNdx) { ActivityRecord s = mStoppingActivities.get(activityNdx); boolean waitingVisible = mActivitiesWaitingForVisibleActivity.contains(s); if (DEBUG_STATES) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + nowVisible + " waitingVisible=" + waitingVisible + " finishing=" + s.finishing); if (waitingVisible && nowVisible) { mActivitiesWaitingForVisibleActivity.remove(s); waitingVisible = false; if (s.finishing) { s.setVisibility(false); } } if (remove) { final ActivityStack stack = s.getStack(); final boolean shouldSleepOrShutDown = stack ! = null ? stack.shouldSleepOrShutDownActivities() : mService.isSleepingOrShuttingDownLocked(); if (! waitingVisible || shouldSleepOrShutDown) { if (! processPausingActivities && s.state == PAUSING) { // Defer processing pausing activities in this iteration and reschedule // a delayed idle to reprocess it again removeTimeoutsForActivityLocked(idleActivity); scheduleIdleTimeoutLocked(idleActivity); continue; } if (DEBUG_STATES) Slog.v(TAG, "Ready to stop: " + s); if (stops == null) { stops = new ArrayList<>(); } stops.add(s); mStoppingActivities.remove(activityNdx); } } } return stops; }Copy the code

After the Pause operation on the AActivity, we added the AActivity to a list of mStoppingActivities in the ActivityStackSupervisor class. This is where the list comes in handy, and we’re going to iterate over the list, Retrieve the eligible activities from it and assemble them into a new list.

ActivityIdleInternalLocked after completion of the operation began to traverse the returned list into the Stop operation.

Look at stack.stopActivityLocked(R), and with my 20 years of Android development experience, I feel like this is where AActivity#onStop is triggered:

// ActivityStack final void stopActivityLocked(ActivityRecord r) { if (r.app ! = null && r.app.thread ! = null) { adjustFocusedActivityStackLocked(r, "stopActivity"); r.resumeKeyDispatchingLocked(); try { r.app.thread.scheduleStopActivity(r.appToken, r.visible, r.configChangeFlags); if (shouldSleepOrShutDownActivities()) { r.setSleeping(true); } Message msg = mHandler.obtainMessage(STOP_TIMEOUT_MSG, r); mHandler.sendMessageDelayed(msg, STOP_TIMEOUT); } catch (Exception e) { } } }Copy the code

Sure enough, in this method the internal implementation of state Richard armitage pp. Thread. ScheduleStopActivity, this method is not went there to have a look. We just need to know it ultimately calls the r onStop

At this point, the life cycle of going from AActivity to BActivity is complete.

Why IdleHandler?

I guess Google’s engineers think that since BActivity is already started, the main Handler’s primary task is to handle the internal tasks of process B. As for AActivity, it has already been paused, so it can tell AMS to Stop it at some idle time.

3.6. Return from BActivity to AActivity

Let’s review the lifecycle of returning AActivity from BActivity

BActivity#onPause -> AActivity#onStart -> AActivity#onResume -> BActivity#onStop -> BActivity#onDestroy

Start with the BActivity’s Finish method

BActivity’s finish method eventually calls AMS’s finishActivity method,

// ActivityManagerService public final boolean finishActivity(IBinder token, int resultCode, Intent resultData, int finishTask) { // Refuse possible leaked file descriptors if (resultData ! = null && resultData.hasFileDescriptors() == true) { throw new IllegalArgumentException("File descriptors passed in Intent"); } synchronized(this) { ActivityRecord r = ActivityRecord.isInStackLocked(token); if (r == null) { return true; } // Keep track of the root activity of the task before we finish it TaskRecord tr = r.getTask(); ActivityRecord rootR = tr.getrootActivity (); // ActivityTask ActivityRecord rootR = tr.getrootActivity (); if (rootR == null) { Slog.w(TAG, "Finishing task with all activities already finished"); } final long origId = Binder.clearCallingIdentity(); try { boolean res; final boolean finishWithRootActivity = finishTask == Activity.FINISH_TASK_WITH_ROOT_ACTIVITY; if (finishTask == Activity.FINISH_TASK_WITH_ACTIVITY || (finishWithRootActivity && r == rootR)) { ... } else { res = tr.getStack().requestFinishActivityLocked(token, resultCode, resultData, "app-request", true); } return res; } finally { Binder.restoreCallingIdentity(origId); }}}Copy the code

Meanings of input parameters:

  • Token: Binder token reference for the Activity that prepares Finish
  • ResultCode: resultCode literal, received in onActivityResult
  • ResultData: resultData literal, received in onActivityResult
  • FinishTask: Whether to finish ActivityStack together

Here we meet the token, which we analyzed earlier in the process of starting BActivity from AActivity. This token is the BActivity identifier in AMS.

ActivityManagerService#finishActivity is called through several layers to ActivityStack#finishActivityLocked:

final boolean finishActivityLocked(ActivityRecord r, int resultCode, Intent resultData, String reason, boolean oomAdj, boolean pauseImmediately) { if (r.finishing) { Slog.w(TAG, "Duplicate finish request for " + r); return false; } mWindowManager.deferSurfaceLayout(); Try {// mark r as Finishing R.akefinishinglocked (); . / / set the resultCode and resultData give resultTo finishActivityResultsLocked (r, the resultCode, resultData); final boolean endTask = index <= 0 && ! task.isClearingToReuseTask(); final int transit = endTask ? TRANSIT_TASK_CLOSE : TRANSIT_ACTIVITY_CLOSE; if (mResumedActivity == r) { if (DEBUG_VISIBILITY || DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare close transition: finishing " + r); if (endTask) { mService.mTaskChangeNotificationController.notifyTaskRemovalStarted( task.taskId); } mWindowManager.prepareAppTransition(transit, false); // Tell window manager to prepare for this one to be removed. r.setVisibility(false); if (mPausingActivity == null) { if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish needs to pause: " + r); if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING, "finish() => pause with userLeaving=false"); // Pause startPausingLocked(false, false, null, pauseImmediately); } if (endTask) { mStackSupervisor.removeLockedTaskLocked(task); } } else if (r.state ! = ActivityState.PAUSING) { ... } else { if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish waiting for pause of: " + r); } return false; } finally { mWindowManager.continueSurfaceLayout(); }}Copy the code

Key points of this method

  1. Change the finishing tag position of R (the Activity to finish) to true
  2. The resultCode and resultData will be set to the resultTo, which is passed in when we create R. As we introduced in the process of jumping from AActivity to BActivity, in this scenario, resultTo refers to the token of AActivity
  3. Pause an Activity that is currently active. In this scenario, the active Activity is AActivity
3.6.1. BActivity# onPause

StartPausingLocked, which we have analyzed previously, will not be described here. For detailed analysis, see section 3.5.1. As we know from our analysis, startPausingLocked will execute the BActvity onPause callback and call ActivityManagerService#activityPaused. I’ll show you that. Will call to ActivityStack# resumeTopActivityInnerLocked, this method calls and BActivity startup process is slightly different

3.6.2 AActivity# onStart, AActivity# onResume

In the call ActivityStack# resumeTopActivityInnerLocked, because we have finished the BActivity Pause, therefore, get the top Activity namely next, get is AActivity, If (next. App! = null && next.app.thread ! = null) was established, it will go ActivityStack# resumeTopActivityInnerLocked branch as follows:

if (next.app ! = null && next.app.thread ! = null) { if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resume running: " + next + " stopped=" + next.stopped + " visible=" + next.visible); try { // Deliver all pending results. ArrayList<ResultInfo> a = next.results; if (a ! = null) { final int N = a.size(); if (! next.finishing && N > 0) { if (DEBUG_RESULTS) Slog.v(TAG_RESULTS, "Delivering results to " + next + ": " + a); next.app.thread.scheduleSendResult(next.appToken, a); } } if (next.newIntents ! = null) { next.app.thread.scheduleNewIntent( next.newIntents, next.appToken, false /* andPause */); } // Well the app will no longer be stopped. // Clear app token stopped state in window manager if needed. next.notifyAppResumed(next.stopped); EventLog.writeEvent(EventLogTags.AM_RESUME_ACTIVITY, next.userId, System.identityHashCode(next), next.getTask().taskId, next.shortComponentName); next.sleeping = false; mService.showUnsupportedZoomDialogIfNeededLocked(next); mService.showAskCompatModeDialogLocked(next); next.app.pendingUiClean = true; next.app.forceProcessStateUpTo(mService.mTopProcessState); next.clearOptionsLocked(); // zhangyulong ResumeNextActivity next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState, mService.isNextTransitionForward(), resumeAnimOptions); if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed " + next); } catch (Exception e) { ... return true; }}}Copy the code

Look at next. App. Thread. ScheduleResumeActivity here, this method will invoke AActivity onRestart, onStart and onResume, this part of the call everyone running around here like a pro, certainly will not went there to have a look

3.6.3. BActivity# onStop, BActivity# onDestroy

In the BActivity startup process analysis, we know that onResume sends an IdleHandler to the main thread MessageQueue, This will eventually call ActivityStackSupervisor# IdleHandler activityIdleInternalLocked, but this time the call with different before, because we have in the previous operation will BActivity for finishing, So if (R.fishing) is true, the method call is executed

final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout, boolean processPausingActivities, Configuration config) { if (DEBUG_ALL) Slog.v(TAG, "Activity idle: " + token); // Atomically retrieve all of the other things to do. final ArrayList<ActivityRecord> stops = processStoppingActivitiesLocked(r, true /* remove */, processPausingActivities); NS = stops ! = null ? stops.size() : 0; if ((NF = mFinishingActivities.size()) > 0) { finishes = new ArrayList<>(mFinishingActivities); mFinishingActivities.clear(); } if (mStartingUsers.size() > 0) { startingUsers = new ArrayList<>(mStartingUsers); mStartingUsers.clear(); } // Stop any activities that are scheduled to do so but have been // waiting for the next one to start. for (int i = 0;  i < NS; i++) { r = stops.get(i); final ActivityStack stack = r.getStack(); if (stack ! = null) {if (r.f inishing) {/ / stop and pause stack. FinishCurrentActivityLocked (r, ActivityStack FINISH_IMMEDIATELY, false); } else {// stop stack.stopActivitylocked (r); } } } return r; }Copy the code

After method execution stack. FinishCurrentActivityLocked APP process corresponding to perform BActivity onStop and onDestroy

At this point, the BActivity lifecycle back to AActivity is complete

4. Afterword.

Many blogs say that the Activity startup process will go through two cross-processes, from the App process to AMS, from AMS to App process, but the actual situation is much more complicated than this, the specific number of interested friends can count. The onActiivtyResult execution will be analyzed in future articles