Android 9.0 Acitvity startup process

Note: The red line represents a process across the Binder

No matter how Android’s startup architecture evolves, the underlying flow remains the same. Android uses Binder to access AMS, and then goes through AMS’s stack processing to return ActivityRecord to AppThread(App process).

In Android 9.0, AMS no longer controls the Activity life cycle on the App side by simply calling IPC. Instead, a state design pattern abstracts each Activity life cycle into a state, and then manages the entire life cycle through a state machine.

Tip: As you can see from the previous articles, AMS actually belongs to the SystemServer process. Not in the same place as the App process.

Start the various roles within AMS in the process

AMS plays the most important role in launching an Activity, and the classes listed below perform each of the major functions in AMS

  • 1.ActivityStack represents the Activity stack (imprecise, more on that later)
  • 2.ActivityStarter represents the control class that officially starts the Activity
  • 3.ActivityManagerService represents the control center for all activities in the system
  • 4. ActivityStackSupervisor represents ActivityStack monitoring center

There are actually four core classes that we need to focus on during the entire Activity launch. This usually involves changing the Activity stack. The core classes involved in this process are:

  • 1.ActivityRecord
  • 2.TaskRecord
  • 3.mRecentTasks
  • 4.mTaskHistory
  • 5.ProcessRecord

AMS cross-process communication creates an Activity, the first step

public final int startActivityAsUser(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) { enforceNotIsolatedCaller("startActivity"); userId = mActivityStartController.checkTargetUser(userId, validateIncomingUser, Binder.getCallingPid(), Binder.getCallingUid(), "startActivityAsUser"); return mActivityStartController.obtainStarter(intent, SetCaller (caller)// Caller's AppThread IBinder. SetCallingPackage (callingPackage)// Caller's package name Call type. SetResolvedType (resolvedType) / / .setresultto (resultTo)// Binder of the caller's ActivityClientRecord (which is actually the AMS ActivityRecord object on the App side) .setresultwho (resultWho)// The identifier of the caller.setreQuestCode (requestCode)// The requestcode.setStartFlags (startFlags) that needs to be returned // The start flag bit .setProfilerInfo(profilerInfo)// The permission file object that is carried on startup .setActivityOptions(bOptions)//ActivityOptions starts the Activity, which is null in normal apps. .setmayWait (userId)// If the activity is open, the default is true. Execute (); // Execute method.Copy the code

From this it is clear what parameters are required during startup. Although it looks like a builder design pattern, it’s actually factory design pattern + share design + chain call. The DefaultFactory gets an ActivityStarter from the mStarterPool via obtainStarter (up to three in the pool) and passes in the startup parameters via a chain call.

Once the setup is complete, let’s go straight to the execute method.

ActivityStarter officially starts the Activity

int execute() { try { // TODO(b/64750076): Look into passing request directly to these methods to allow // for transactional diffs and preprocessing. if (mRequest.mayWait) { return startActivityMayWait(mRequest.caller, mRequest.callingUid, mRequest.callingPackage, mRequest.intent, mRequest.resolvedType, mRequest.voiceSession, mRequest.voiceInteractor, mRequest.resultTo, mRequest.resultWho, mRequest.requestCode, mRequest.startFlags, mRequest.profilerInfo, mRequest.waitResult, mRequest.globalConfig, mRequest.activityOptions, mRequest.ignoreTargetSecurity, mRequest.userId, mRequest.inTask, mRequest.reason, mRequest.allowPendingRemoteAnimationRegistryLookup); } else { ... } } finally { onExecutionComplete(); }}Copy the code

As you can see from execute, Google engineers used the try-final mechanism flexibly in this process, using onExecutionComplete to clear data in ActivityStartController and put it back into startPool.

At this point we are a synchronous operation, so look at the startActivityMayWait method.

startActivityMayWait

This paragraph is divided into three paragraphs:

1. Prepare data for the activity from PackageManagerService

private int startActivityMayWait(IApplicationThread caller, int callingUid, String callingPackage, Intent intent, String resolvedType, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, WaitResult outResult, Configuration globalConfig, SafeActivityOptions options, boolean ignoreTargetSecurity, int userId, TaskRecord inTask, String reason, boolean allowPendingRemoteAnimationRegistryLookup) { .... ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId, 0 /* matchFlags */, computeResolveFilterUid( callingUid, realCallingUid, mRequest.filterCallingUid)); if (rInfo == null) { UserInfo userInfo = mSupervisor.getUserInfo(userId); if (userInfo ! = null && userInfo.isManagedProfile()) { // Special case for managed profiles, if attempting to launch non-cryto aware // app in a locked managed profile from an unlocked parent allow it to resolve // as user will be sent via confirm credentials to unlock the profile. UserManager userManager = UserManager.get(mService.mContext); boolean profileLockedAndParentUnlockingOrUnlocked = false; long token = Binder.clearCallingIdentity(); try { UserInfo parent = userManager.getProfileParent(userId); profileLockedAndParentUnlockingOrUnlocked = (parent ! = null) && userManager.isUserUnlockingOrUnlocked(parent.id) && ! userManager.isUserUnlockingOrUnlocked(userId); } finally { Binder.restoreCallingIdentity(token); } if (profileLockedAndParentUnlockingOrUnlocked) { rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId, PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, computeResolveFilterUid( callingUid, realCallingUid, mRequest.filterCallingUid)); } } } // Collect information about the target of the Intent. ActivityInfo aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, profilerInfo);Copy the code

The steps are as follows: 1. Call the PMS from ActivityStackSupervisor to obtain the ResolveInfo.

private ResolveInfo resolveIntentInternal(Intent intent, String resolvedType,
            int flags, int userId, boolean resolveForStart, int filterCallingUid) {
        try {

            if (!sUserManager.exists(userId)) return null;
            final int callingUid = Binder.getCallingUid();
            flags = updateFlagsForResolve(flags, userId, intent, filterCallingUid, resolveForStart);
            mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                    false /*requireFullPermission*/, false /*checkShell*/, "resolve intent");

            final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType,
                    flags, filterCallingUid, userId, resolveForStart, true /*allowDynamicSplits*/);

            final ResolveInfo bestChoice =
                    chooseBestActivity(intent, resolvedType, flags, query, userId);
            return bestChoice;
        } finally {
          ....
        }
    }
Copy the code

From the above code, we can see that this method uses the intent to find the most appropriate option. ResolveInfo is the androidmanifest. XML data loaded into the PackageManagerService(PMS) system after the App is installed.

QueryIntentActivitiesInternal step is:

  • 1. Check whether the current Intent is an explicit Intent. If yes, retrieve the class object and AndroidManifest to match, matching success return.
  • 2. If no package name is specified, the system-wide lookup matches the intent
  • 3. If the package name is specified, the current package name is used to find the Activity that matches the intent

Therefore, it is possible to match multiple intEnts that are appropriate and filter activities further with chooseBestActivity.

Why do I add this paragraph, and actually one of the key logic of this paragraph is AppLink. In AndroidManifest, set Intent parameters such as SCHme and let external app wake up our app. When only one awakened destination is returned, if there are more than one, the class in the intent is replaced by the system ResolveActivity. Used to select our destination App, as shown below.

  • 2. If the ResolveInfo file cannot be queried, obtain the ResolveInfo file from the direct startup file

Since Android 5.0. Android will begin to support multi-user systems, where the configuration of these users is controlled by the UserManager, with the AccountManager controlling each user’s account.

In Android7.0, a new Boot mode for apps is Direct Boot. This mode refers to a new mode entered after the device is started until the end of the user’s unlock phase. In this mode, a Device protected storage private space is created for the application.

This mode is special, we need to set Android :directBootAware=”true” in the AndroidManifest.

Therefore, in this mode, you need to wake up a special Activity to confirm that it has been unlocked, and need to find the corresponding ResolveInfo from the special private space.

  • 3. Use getActivityInfo of the PMS to read ActivityInfo

When we determine the ResolveInfo, AMS reads the Activity information from the ResolveInfo PMS via the resolveActivity.

ActivityInfo resolveActivity(Intent intent, ResolveInfo rInfo, int startFlags, ProfilerInfo profilerInfo) { final ActivityInfo aInfo = rInfo ! = null ? rInfo.activityInfo : null; if (aInfo ! = null) { intent.setComponent(new ComponentName( aInfo.applicationInfo.packageName, aInfo.name)); // Don't debug things in the system process if (! aInfo.processName.equals("system")) { if ((startFlags & ActivityManager.START_FLAG_DEBUG) ! = 0) { mService.setDebugApp(aInfo.processName, true, false); }... return aInfo; }Copy the code

When found, it shows the Settings ComponentName, package name and class name.

2. Tackle heavyweight processes

synchronized (mService) { final ActivityStack stack = mSupervisor.mFocusedStack; stack.mConfigWillChange = globalConfig ! = null && mService.getGlobalConfiguration().diff(globalConfig) ! = 0; . final long origId = Binder.clearCallingIdentity(); if (aInfo ! = null && (aInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) ! = 0 && mService.mHasHeavyWeightFeature) { // This may be a heavy-weight process! Check to see if we already // have another, different heavy-weight process running. if (aInfo.processName.equals(aInfo.applicationInfo.packageName)) { final ProcessRecord heavy = mService.mHeavyWeightProcess; if (heavy ! = null && (heavy.info.uid ! = aInfo.applicationInfo.uid || ! heavy.processName.equals(aInfo.processName))) { int appCallingUid = callingUid; if (caller ! = null) { ProcessRecord callerApp = mService.getRecordForAppLocked(caller); if (callerApp ! = null) { appCallingUid = callerApp.info.uid; } else { Slog.w(TAG, "Unable to find app for caller " + caller + " (pid=" + callingPid + ") when starting: " + intent.toString()); SafeActivityOptions.abort(options); return ActivityManager.START_PERMISSION_DENIED; } } IIntentSender target = mService.getIntentSenderLocked( ActivityManager.INTENT_SENDER_ACTIVITY, "android", appCallingUid, userId, null, null, 0, new Intent[] { intent }, new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT, null); Intent newIntent = new Intent(); if (requestCode >= 0) { // Caller is requesting a result. newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT,  true); } newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT, new IntentSender(target)); if (heavy.activities.size() > 0) { ActivityRecord hist = heavy.activities.get(0); newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_APP, hist.packageName); newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_TASK, hist.getTask().taskId); } newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP, aInfo.packageName); newIntent.setFlags(intent.getFlags()); newIntent.setClassName("android", HeavyWeightSwitcherActivity.class.getName()); intent = newIntent; resolvedType = null; caller = null; callingUid = Binder.getCallingUid(); callingPid = Binder.getCallingPid(); componentSpecified = true; rInfo = mSupervisor.resolveIntent(intent, null /*resolvedType*/, userId, 0 /* matchFlags */, computeResolveFilterUid( callingUid, realCallingUid, mRequest.filterCallingUid)); aInfo = rInfo ! = null ? rInfo.activityInfo : null; if (aInfo ! = null) { aInfo = mService.getActivityInfoForUser(aInfo, userId); }}}}Copy the code

This heavyweight process actually existed for a long time, but allowed us to set it up to be available to us after SDK 28(Android 9.0).

A heavyweight process is usually the only one in the entire system. It does not run in the background and is not killed by the background. Therefore, users need to exit the process.

This code is when the background has started a heavy process, the user wants to start another heavy process, will pop up an interface for the user to choose.

3. Perform the next startup

 final ActivityRecord[] outRecord = new ActivityRecord[1];
            int res = startActivity(caller, intent, ephemeralIntent, resolvedType, aInfo, rInfo,
                    voiceSession, voiceInteractor, resultTo, resultWho, requestCode, callingPid,
                    callingUid, callingPackage, realCallingPid, realCallingUid, startFlags, options,
                    ignoreTargetSecurity, componentSpecified, outRecord, inTask, reason,
                    allowPendingRemoteAnimationRegistryLookup);

...
 if (outResult != null) {
                outResult.result = res;

                final ActivityRecord r = outRecord[0];

                switch(res) {
                    case START_SUCCESS: {
                        mSupervisor.mWaitingActivityLaunched.add(outResult);
                        do {
                            try {
                                mService.wait();
                            } catch (InterruptedException e) {
                            }
                        } while (outResult.result != START_TASK_TO_FRONT
                                && !outResult.timeout && outResult.who == null);
                        if (outResult.result == START_TASK_TO_FRONT) {
                            res = START_TASK_TO_FRONT;
                        }
                        break;
                    }
                    case START_DELIVERED_TO_TOP: {
                        outResult.timeout = false;
                        outResult.who = r.realActivity;
                        outResult.totalTime = 0;
                        outResult.thisTime = 0;
                        break;
                    }
                    case START_TASK_TO_FRONT: {
                        // ActivityRecord may represent a different activity, but it should not be
                        // in the resumed state.
                        if (r.nowVisible && r.isState(RESUMED)) {
                            outResult.timeout = false;
                            outResult.who = r.realActivity;
                            outResult.totalTime = 0;
                            outResult.thisTime = 0;
                        } else {
                            outResult.thisTime = SystemClock.uptimeMillis();
                            mSupervisor.waitActivityVisible(r.realActivity, outResult);
                            // Note: the timeout variable is not currently not ever set.
                            do {
                                try {
                                    mService.wait();
                                } catch (InterruptedException e) {
                                }
                            } while (!outResult.timeout && outResult.who == null);
                        }
                        break;
                    }
                }
            }
Copy the code

We can see that after the next startup, if the status code START_SUCCESS is returned, AMS will block, waiting to wake up.

Typically, if startActivity completes the process normally, the status code is returned as START_SUCCESS. The first block is entered, and the block keeps checking whether the current state is START_TASK_TO_FRONT before exiting. However, if the Activity is being created for the first time, By ActivityStarter startActivity START_SUCCESS before, through postStartActivityProcessing START_TASK_TO_FRONT results translate into. Therefore, you can exit the current state immediately.

    private int startActivity(final ActivityRecord r, ActivityRecord sourceRecord,
                IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
                int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
                ActivityRecord[] outActivity) {
        int result = START_CANCELED;
        try {
            mService.mWindowManager.deferSurfaceLayout();
            result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
                    startFlags, doResume, options, inTask, outActivity);
        } finally {
     ...
        }

        postStartActivityProcessing(r, result, mTargetStack);

        return result;
    }
Copy the code

StartActivity handles the conversion of ActivityInfo to ActivityRecord

Here are three steps:

1. Prepare the basic data of ActivtyRecord

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, SafeActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity, TaskRecord inTask, boolean allowPendingRemoteAnimationRegistryLookup) { 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 { ... } } final int userId = aInfo ! = null && aInfo.applicationInfo ! = null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0; . ActivityRecord sourceRecord = null; ActivityRecord resultRecord = null; if (resultTo ! = null) { sourceRecord = mSupervisor.isInAnyStackLocked(resultTo); . if (sourceRecord ! = null) { if (requestCode >= 0 && ! sourceRecord.finishing) { resultRecord = sourceRecord; }}}... }Copy the code

To instantiate ActivityRecord, the Android system retrieves data from the process in which the Activity is currently running through the IApplicationThread.

That is called mService. GetRecordForAppLocked (caller); Obtain ProcessRecord.

AMS search process

Let’s track how AMS is obtained with the IApplicationThread remote Binder object.

private final int getLRURecordIndexForAppLocked(IApplicationThread thread) { final IBinder threadBinder = thread.asBinder(); // Find the application record. for (int i=mLruProcesses.size()-1; i>=0; i--) { final ProcessRecord rec = mLruProcesses.get(i); if (rec.thread ! = null && rec.thread.asBinder() == threadBinder) { return i; } } return -1; } ProcessRecord getRecordForAppLocked(IApplicationThread thread) { if (thread == null) { return null; } int appIndex = getLRURecordIndexForAppLocked(thread); if (appIndex >= 0) { return mLruProcesses.get(appIndex); } // Validation: if it isn't in the LRU list, it shouldn't exist, but let's // double-check that. final IBinder threadBinder = thread.asBinder(); final ArrayMap<String, SparseArray<ProcessRecord>> pmap = mProcessNames.getMap(); for (int i = pmap.size()-1; i >= 0; i--) { final SparseArray<ProcessRecord> procs = pmap.valueAt(i); for (int j = procs.size()-1; j >= 0; j--) { final ProcessRecord proc = procs.valueAt(j); if (proc.thread ! = null && proc.thread.asBinder() == threadBinder) { Slog.wtf(TAG, "getRecordForApp: exists in name list but not in LRU list: " + proc); return proc; } } } return null; }Copy the code

This gives us a glimpse of Google’s obsession with performance. Throughout AMS, there are two data structures that store process objects:

An LRU ArrayList stores data. Although this data structure is not our usual LRUMap(LinkHashMap automatically handles THE LRU algorithm, which will be discussed in the algorithms column), it is the least recently used algorithm. But Google engineers chose to take matters into their own hands.
2. MProcessNames stores the data of all processes. You can find the data of all processes by using the reference name of Binde.

So, to use common language, AMS uses a secondary cache for process lookups.

See how the process updates the LRU algorithm:

final void updateLruProcessLocked(ProcessRecord app, boolean activityChange, ProcessRecord client) { final boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities || app.treatLikeActivity || app.recentTasks.size() > 0; final boolean hasService = false; // not impl yet. app.services.size() > 0; if (! activityChange && hasActivity) { // The process has activities, so we are only allowing activity-based adjustments // to move it. It should be kept in the front of the list with other // processes that have activities, and we don't want those to change their // order except due to activity operations. return; } mLruSeq++; final long now = SystemClock.uptimeMillis(); app.lastActivityTime = now; // First a quick reject: if the app is already at the position we will // put it, Then there is nothing to do. // Step 1 if (hasActivity) {final int N = mlruprocesses.size (); if (N > 0 && mLruProcesses.get(N-1) == app) { ... return; } } else { if (mLruProcessServiceStart > 0 && mLruProcesses.get(mLruProcessServiceStart-1) == app) { ... return; Using the same ProcessRecord}} / / get the recent index int lrui = mLruProcesses. LastIndexOf (app); . if (lrui >= 0) { if (lrui < mLruProcessActivityStart) { mLruProcessActivityStart--; } if (lrui < mLruProcessServiceStart) { mLruProcessServiceStart--; }... mLruProcesses.remove(lrui); }... int nextIndex; } else if (hasService) {// Process has services, put it at the top of the service list. ... mLruProcesses.add(mLruProcessActivityStart, app); nextIndex = mLruProcessServiceStart; mLruProcessActivityStart++; } else {// If the app is currently using a content provider or Service, // bump those processes as well. for (int j=app.connections.size()-1; j>=0; j--) { ConnectionRecord cr = app.connections.valueAt(j); if (cr.binding ! = null && ! cr.serviceDead && cr.binding.service ! = null && cr.binding.service.app ! = null && cr.binding.service.app.lruSeq ! = mLruSeq && ! cr.binding.service.app.persistent) { nextIndex = updateLruProcessInternalLocked(cr.binding.service.app, now, nextIndex, "service connection", cr, app); } } for (int j=app.conProviders.size()-1; j>=0; j--) { ContentProviderRecord cpr = app.conProviders.get(j).provider; if (cpr.proc ! = null && cpr.proc.lruSeq ! = mLruSeq && ! cpr.proc.persistent) { nextIndex = updateLruProcessInternalLocked(cpr.proc, now, nextIndex, "provider reference", cpr, app); }}}Copy the code
The addition of additional knowledge

In order to understand this logic, we must cover a basic knowledge of Android’s UID and userId.

Although Android uses the Linux kernel, its UID is deceptive. There is a difference between an Android UID and a Linux UID. In Linux, uid and task_struct are bound one to one and represent the user ID of the current process user. And Android similar but different, Android at the framework layer userid is in PMS in accordance with the time by PackageParser Package. ScanPackageDirtyLI () good.

Each time the Uid is obtained, the following algorithm is used:

public static int getUid(@UserIdInt int userId, @appidInt int appId) {if (MU_ENABLED) {// Whether multiple users are supported //PER_USER_RANGE is 100000 return userId * PER_USER_RANGE + (appId % PER_USER_RANGE); } else { return appId; }}Copy the code

Obtain the UID from the userId

public static final int getUserId(int uid) { if (MU_ENABLED) { return uid / PER_USER_RANGE; } else { return 0; }}Copy the code

UserId * 100000 + (appId % 100000) this converts uid and userId to each other.

So this design results in uids that can be shared, albeit infrequently, but actually exist. When the package name is the same, but the component has the Android: Process tag set in the AndroidManifest, it can share a UID between different processes. You can also set Android :shareUserid, which can be shared with different package names (the same package name, the same signature will be overwritten).

Return to the PROCESS’s LRU algorithm

We first need to understand that the general LRU algorithm is to put the most recently used object at the end of the queue, and the most recently used object at the head of the queue, in order to reduce the search time in some common objects, so as to achieve the purpose of performance optimization.

The LRU algorithm for this process is slightly different. Most recently used at the end of the team, least recently used at the head of the team.

As a result, AMS searches the ProcessRecord from the end of the queue during the loop. Understand the design prototype, then take a look at the design of Google engineers.

Each time you update a process to the LRU algorithm, it first checks whether the current process contains an Activity or Service.

Returns immediately if you find that the ProcessRecord you are currently looking for is at the end of the queue, regardless of what it contains. The index that last used the same process is searched again and deleted.

Also here you can see that there are two location labels in this LRU

    1. mLruProcessActivityStart
    1. mLruProcessServiceStart

These two position tags split the entire LRU list into three parts, from mLruProcessActivityStart to the end of the queue, which is the collection of processes with activities. MLruProcessServiceStart to mLruProcessActivityStart is the collection with service, and from mLruProcessServiceStart to the head of the queue with neither of the above.

So when we adjust, the process with the Activity only needs to adjust mLruProcessActivityStart to the end of the queue. With the Service just adjust mLruProcessServiceStart to mLruProcessActivityStart.

So when we delete ProcessRecord, both indexes must be moved backwards.

Next, discuss in different cases:

  • 1. Process with Activity:
final int N = mLruProcesses.size(); if ((app.activities.size() == 0 || app.recentTasks.size() > 0) && mLruProcessActivityStart < (N - 1)) { ... mLruProcesses.add(N - 1, app); // To keep it from spamming the LRU list (by making a bunch of clients), // we will push down any other entries owned by the app. final int uid = app.info.uid; for (int i = N - 2; i > mLruProcessActivityStart; i--) { ProcessRecord subProc = mLruProcesses.get(i); if (subProc.info.uid == uid) { if (mLruProcesses.get(i - 1).info.uid ! = uid) { ... ProcessRecord tmp = mLruProcesses.get(i); mLruProcesses.set(i, mLruProcesses.get(i - 1)); mLruProcesses.set(i - 1, tmp); i--; } } else { // A gap, we can stop here. break; } } } else { ... mLruProcesses.add(app); } nextIndex = mLruProcessServiceStart;Copy the code
  • 2. Insert a process that has a service. Insert it directly into the mLruProcessActivityStart location and increment mLruProcessActivityStart to move the collection of activities backward. NextIndex = mLruProcessServiceStart;

  • 3. Insert a process that has no Activity or Service.

// Process not otherwise of interest, it goes to the top of the non-service area. int index = mLruProcessServiceStart; if (client ! = null) { // If there is a client, don't allow the process to be moved up higher // in the list than that client. int clientIndex = mLruProcesses.lastIndexOf(client); . if (clientIndex <= lrui) { // Don't allow the client index restriction to push it down farther in the // list than it already is. clientIndex = lrui; } if (clientIndex >= 0 && index > clientIndex) { index = clientIndex; }}... mLruProcesses.add(index, app); nextIndex = index-1; mLruProcessActivityStart++; mLruProcessServiceStart++;Copy the code

A service with a client is bound to a remote process with a Binder and restarts the service. The client is the ProcessRecord on the remote side. So there are two cases where there is an Activity/Service on the remote end, or there is none at all.

  • 1. The current process will be inserted in mLruProcessServiceStart when there is no client, then this position and both mLruProcessServiceStart and mLruProcessActivityStart will be moved back one bit. Nextindex is now mLruProcessServiceStart – 1 before moving.
  • 2. When adding a client If the client itself exists and is farther behind the LRU than the current process or does not exist, set clientIndex to the LRU position of the client before the current process adjustment.

If the client does not exist, the insertion location is mLruProcessServiceStart.

Then determine if the client exists, if the client position is earlier than the original position of the current process, and if the current position mLruProcessServiceStart is smaller, then the insertion position is the original position.

If the client exists and is later than the current process (app), and the position of the client is smaller than mLruProcessServiceStart, the insertion position is mLruProcessServiceStart.

If the client exists and is later than the current process (app), and the client position is larger than mLruProcessServiceStart, the insertion position is the CLIENT’s LRU position.

NextIndex is set to mLruProcessServiceStart -1, or the client is at LRU position -1.

LRU subsequent algorithm for the process

After the Activity and Service are processed, subsequent processing continues. The Service is bound to the remote end of the process, the ContentProvider.

for (int j=app.connections.size()-1; j>=0; j--) { ConnectionRecord cr = app.connections.valueAt(j); if (cr.binding ! = null && ! cr.serviceDead && cr.binding.service ! = null && cr.binding.service.app ! = null && cr.binding.service.app.lruSeq ! = mLruSeq && ! cr.binding.service.app.persistent) { nextIndex = updateLruProcessInternalLocked(cr.binding.service.app, now, nextIndex, "service connection", cr, app); } } for (int j=app.conProviders.size()-1; j>=0; j--) { ContentProviderRecord cpr = app.conProviders.get(j).provider; if (cpr.proc ! = null && cpr.proc.lruSeq ! = mLruSeq && ! cpr.proc.persistent) { nextIndex = updateLruProcessInternalLocked(cpr.proc, now, nextIndex, "provider reference", cpr, app); }}Copy the code
private int updateLruProcessInternalLocked(ProcessRecord app, long now, int index,
            String what, Object obj, ProcessRecord srcApp) {
        app.lastActivityTime = now;

        if (app.activities.size() > 0 || app.recentTasks.size() > 0) {
      
            return index;
        }

        int lrui = mLruProcesses.lastIndexOf(app);
        if (lrui < 0) {
            return index;
        }

        if (lrui >= index) {
    
            return index;
        }

        if (lrui >= mLruProcessActivityStart) {
            return index;
        }

        mLruProcesses.remove(lrui);
        if (index > 0) {
            index--;
        }

        mLruProcesses.add(index, app);
        return index;
Copy the code

Here’s the logical linkage above the function:

  • 1. When the remote process of a service has an Activity, it does not move.
  • 2. The remote process of the Service or ContentProvider does not exist in the LRU.
  • 3. Do not adjust when the remote process of the Service or ContentProvider is larger than nextIndex.
  • 4. The remote process of the Service or ContentProvider does not adjust after mLruProcessActivityStart.
  • 5. Otherwise, add mLruProcessServiceStart one by one. Processes without Activity/Service are inserted before or after mLruProcessServiceStart.

The LRU calculation for the entire process is shown in a diagram as follows

That’s all the process has to do with the LRU. This is why Google engineers chose to use ArrayList instead of LinkHashMap for LRU processing. Because Google engineering buoys the LRU, it divides the adjustment area, which further compresses the search and adjustment time.

isInAnyStackLocked

The above tirade is just to find the process cached in AMS. Next we will check the corresponding Activity stack.

This method passes in a key resultTo Binder proxy object. The difference is that this does not refer to any Activity, but to the WindowManager Binder agent that is bound when the Activity is started.

ActivityRecord isInAnyStackLocked(IBinder token) { int numDisplays = mActivityDisplays.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); final ActivityRecord r = stack.isInStackLocked(token); if (r ! = null) { return r; } } } return null; }Copy the code

You can see if the Binder proxy object with the caller WindowManager is launched. The Activity is probably in the same process as the current Activity. Android will retrieve the ActivityDisplay object from the mActivityDisplays and find that we need the ActivityStack, which, as the name suggests, is our Activity stack.

The mActivityDisplays is a data structure that connects the Activity’s display logic to the Activity. In other words, a data structure that WindowManager associates with an Activity. Save the ActivityStack in ActivityDisplay so that Windows Manager can change with the ActivityStack.

The scenario here is to get the current ActivityStack and get the initiator’s ActivityRecord from it (the Activity holds information in AMS).

StartActivity makes the first adjustment based on the current startup flag

final int launchFlags = intent.getFlags(); if ((launchFlags & Intent.FLAG_ACTIVITY_FORWARD_RESULT) ! = 0 && sourceRecord ! = null) { // Transfer the result target from the source activity to the new // one being started, including any failures. if (requestCode >= 0) { SafeActivityOptions.abort(options); return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; } resultRecord = sourceRecord.resultTo; // This resultTo refers to an attribute in the Activity, which is different from the above. if (resultRecord ! = null && ! resultRecord.isInStackLocked()) { resultRecord = null; } resultWho = sourceRecord.resultWho; requestCode = sourceRecord.requestCode; sourceRecord.resultTo = null; if (resultRecord ! = null) { resultRecord.removeResultsLocked(sourceRecord, resultWho, requestCode); } if (sourceRecord.launchedFromUid == callingUid) { callingPackage = sourceRecord.launchedFromPackage; }}... final ActivityStack resultStack = resultRecord == null ? null : resultRecord.getStack(); .Copy the code

This code is the first time to get the start flag in the intent. The first flag processed is FORWARD_RESULT.

The intent flag is limited, meaning that requestCode is passed through. This means that when the flag is set, the initiated Activity will not accept the requestCode. Instead, it passes through to the next Activity that is started. However, as a transparent passer, you cannot set any requestCode. If a requestCode is set, an error message is reported. FORWARD_RESULT_FLAG used while also requesting a result.

Knowing the usage, we can see directly from here that when we set requestCode greater than 0, an error will be returned immediately. Otherwise, the requestcode is not sent. The requestCode that started the sourceRecord is retrieved, resultWho is set to the next Activity, and the package name invoked is changed to sourceRecord, thus completing the passthrough action.

At the same time, the sourceRecord that has been started clears the resultTo to ensure that the passthrough target is the newly created one.

StartActivity verifies permissions and generates ActivityRecord

boolean abort = ! mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho, requestCode, callingPid, callingUid, callingPackage, ignoreTargetSecurity, inTask ! = null, callerApp, resultRecord, resultStack); abort |= ! mService.mIntentFirewall.checkStartActivity(intent, callingUid, callingPid, resolvedType, aInfo.applicationInfo); // Merge the two options bundles, while realCallerOptions takes precedence. ActivityOptions checkedOptions = options ! = null ? options.getOptions(intent, aInfo, callerApp, mSupervisor) : null; if (allowPendingRemoteAnimationRegistryLookup) { checkedOptions = mService.getActivityStartController() .getPendingRemoteAnimationRegistry() .overrideOptionsIfNeeded(callingPackage, checkedOptions); } if (mService.mController ! = null) { try { // The Intent we give to the watcher has the extra data // stripped off, since it can contain private information. Intent watchIntent = intent.cloneFilter(); abort |= ! mService.mController.activityStarting(watchIntent, aInfo.applicationInfo.packageName); } catch (RemoteException e) { mService.mController = null; } } mInterceptor.setStates(userId, realCallingPid, realCallingUid, startFlags, callingPackage); if (mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, callingPid, callingUid, checkedOptions)) { // activity start was intercepted, e.g. because the target user is currently in quiet // mode (turn off work) or the target application is suspended intent  = mInterceptor.mIntent; rInfo = mInterceptor.mRInfo; aInfo = mInterceptor.mAInfo; resolvedType = mInterceptor.mResolvedType; inTask = mInterceptor.mInTask; callingPid = mInterceptor.mCallingPid; callingUid = mInterceptor.mCallingUid; checkedOptions = mInterceptor.mActivityOptions; } if (abort) { if (resultRecord ! = null) { resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, RESULT_CANCELED, null); } ActivityOptions.abort(checkedOptions); return START_ABORTED; } // If permissions need a review before any of the app components can run, we // launch the review activity and pass a pending intent to start the activity // we are to launching now after the review is completed. if (mService.mPermissionReviewRequired && aInfo ! = null) { if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired( aInfo.packageName, userId)) { IIntentSender target = mService.getIntentSenderLocked( ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, callingUid, userId, null, null, 0, new Intent[]{intent}, new String[]{resolvedType}, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT, null); final int flags = intent.getFlags(); Intent newIntent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); newIntent.setFlags(flags | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); newIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName); newIntent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); if (resultRecord ! = null) { newIntent.putExtra(Intent.EXTRA_RESULT_NEEDED, true); } intent = newIntent; resolvedType = null; callingUid = realCallingUid; callingPid = realCallingPid; rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId, 0, computeResolveFilterUid( callingUid, realCallingUid, mRequest.filterCallingUid)); aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/); } } if (rInfo ! = null && rInfo.auxiliaryInfo ! = null) { intent = createLaunchIntent(rInfo.auxiliaryInfo, ephemeralIntent, callingPackage, verificationBundle, resolvedType, userId); resolvedType = null; callingUid = realCallingUid; callingPid = realCallingPid; aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/); } ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid, callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(), resultRecord, resultWho, requestCode, componentSpecified, voiceSession ! = null, mSupervisor, checkedOptions, sourceRecord); if (outActivity ! = null) { outActivity[0] = r; }Copy the code

In this code snippet, there are two key functions that do permission judgment.

  • 1. CheckStartAnyActivityPermission this function calls to the PermissionManagerService finally, for the current uid carefully inspection are legal.
  • The mInterceptor function is an interceptor that carefully intercepts the current parameter. There are three main interceptor criteria:
boolean intercept(Intent intent, ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType,
            TaskRecord inTask, int callingPid, int callingUid, ActivityOptions activityOptions) {
        mUserManager = UserManager.get(mServiceContext);

        mIntent = intent;
        mCallingPid = callingPid;
        mCallingUid = callingUid;
        mRInfo = rInfo;
        mAInfo = aInfo;
        mResolvedType = resolvedType;
        mInTask = inTask;
        mActivityOptions = activityOptions;

        if (interceptSuspendedPackageIfNeeded()) {
            return true;
        }
        if (interceptQuietProfileIfNeeded()) {
            return true;
        }
        if (interceptHarmfulAppIfNeeded()) {
            return true;
        }
        return interceptWorkProfileChallengeIfNeeded();
    }
Copy the code
  • 1. If the package to be started is suspended by the administrator, the operation cannot be performed
  • 2. When the user is in quiet mode, the quietmode is not the volume, but the requestQuietModeEnabled set in UserManager. In this mode, the application will not actually run. There’s a popover when you turn off quiet mode.

-3. The current application is judged to be harmful

In all three cases, whenever you want to start an Activity, a new ActivityInfo notifies the user.

There is also a common case where our permission judgment popover does not block the Activity directly, but instead blocks it as a popover after the Activity starts.

if (mService.mPermissionReviewRequired && aInfo ! = null) { if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired( aInfo.packageName, userId)) { IIntentSender target = mService.getIntentSenderLocked( ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, callingUid, userId, null, null, 0, new Intent[]{intent}, new String[]{resolvedType}, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT, null); final int flags = intent.getFlags(); Intent newIntent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); newIntent.setFlags(flags | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); newIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName); newIntent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); if (resultRecord ! = null) { newIntent.putExtra(Intent.EXTRA_RESULT_NEEDED, true); } intent = newIntent; resolvedType = null; callingUid = realCallingUid; callingPid = realCallingPid; rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId, 0, computeResolveFilterUid( callingUid, realCallingUid, mRequest.filterCallingUid)); aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/); }}Copy the code

At this point, you can see that you’re familiar with the IIntentSender class. And if you’ve read pendingIntent, you know that pendingItent is essentially IIntentSender that’s this class that’s postpones the action. I won’t go into detail here, but I’ll look at pendingIntent later. So you can attach an intent and wait until the Activity starts and then pop up a popover.

Finally, a new ActivityRecord is generated from the data (this ActivityRecord is the ActivityRecord of the target object) and the initiator’s sourceRecord and the current one are passed in as parameters. The ActivityStack operation is started. Therefore, we know that an Activity will correspond to an ActivityRecord in AMS.

summary

In this article, we will focus on ActvityStack changes under various StartFlags of intEnts.

This paper summarizes the process cache LRU algorithm, which is actually divided into three sections for management, including activities, services, neither of them. At the remote end of the ContentProvider and Service bindings, the process to which both may be linked is managed and cached. Therefore, it is clear that only Boardcast of the four components does not affect the priority of the process LRU.

Note, however, that the four components of the Android system will affect the process ADJ scheduling, the two are different.

. At the same time through Activitstarter startActivity method for target Activity ActivityRecord ready, what is the target object. The next step is to insert the ActivityRecord into the stack.