preface

This is the fifth installment in the Android 9.0 AOSP series, so let’s review the previous installments.

The Pangu and Nuwa of the Java world — Zygote

This paper mainly introduces the startup process of Zygote, the first Java process in the Android world.

  • Registers the server socket to respond to client requests
  • Various preloading operations, classes, resources, shared libraries, etc
  • Force GC once
  • The fork SystemServer process
  • Loop waiting for socket requests from clients (request socket connection and request fork application process)

The oldest son of the Zygote family, SystemServer

This paper mainly introduces the Zygote process fork first process SystemServer, it carries all kinds of system services to create and start.

  • Language, time zone, and region Settings

  • Vm memory Settings

  • Fingerprint information, Binder call Settings

  • PrepareMainLooper (), create the main thread Looper

  • Initialize the native service and load libandroid_servers.so

  • CreateSystemContext (), initializes the system context

  • Create the SystemServiceManager SystemServiceManager

  • StartBootstrapServices: Starts the system boot service

  • StartCoreServices: Starts the system core service

  • StartOtherServices, to startOtherServices

  • Looper.loop(), which starts the message loop

At the end of startOtherServices, AMS’s onSystemReady() method is called to start the desktop Activity.

Who woke up Zygote in the Android world?

This paper mainly introduces the process of AMS request to Zygote to create application process, that is, socket communication to Zygote process, echoing with the first article.

  • Call process.start () to create the application Process

  • ZygoteProcess is responsible for establishing a socket connection with the Zygote process and sending the parameters required to create the process to the Socket server of Zygote

  • Zygote server receives a parameter called after ZygoteConnection. ProcessOneCommand () processing parameters, and the process of the fork

  • Finally, findStaticMain() finds the Main() method of the ActivityThread class and executes, and the child process starts

ActivityManagerService, the ubiquitous system core service, starts process parsing

This paper introduces the startup process of ActivityManagerService (AMS), which is closely related to the startup, switching, scheduling and application process management of the four components.

  • AMS initialization, through ActivityManagerService. Lifecycle in the constructor of initialization

  • SetSystemProcess (), registers various services, creates ProcessRecord, updates the oOM_adj value

  • Installing the System Provider

  • SystemReady (), which will eventually start the desktop Home Activity

Today we are going to introduce the process of starting an Activity. Starting an Activity is a big project with lots of details. This article will briefly review the startup process without diving too deeply into source code details. Key issues, such as launchMode handling and lifecycle handling, will be dissected in a separate article.

Startup Process Analysis

Let’s start with a flow chart to make it easier to understand.

Following the previous analysis, ActivityManagerService’s systemReady() method finally starts the desktop Hme Activity, calling startHomeActivityLocked.

> ActivityManagerService.java

boolean startHomeActivityLocked(int userId, String reason) {... Intent intent = getHomeIntent(); ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);if(aInfo ! =null) {...if (app == null || app.instr == null) {
            intent.setFlags(intent.getFlags() | FLAG_ACTIVITY_NEW_TASK);
            final int resolvedUserId = UserHandle.getUserId(aInfo.applicationInfo.uid);
            final String myReason = reason + ":" + userId + ":" + resolvedUserId;
            // Start the desktop ActivitymActivityStartController.startHomeActivity(intent, aInfo, myReason); }}else {
        Slog.wtf(TAG, "No home screen found for " + intent, new Throwable());
    }
    return true;
}
Copy the code

Call the startHomeActivity() method of ActivityStartController:

> ActivityStartController.java

void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason) {
        mSupervisor.moveHomeStackTaskToTop(reason);

        mLastHomeActivityStartResult = obtainStarter(intent, "startHomeActivity: " + reason)
                .setOutActivity(tmpOutRecord)
                .setCallingUid(0)
                .setActivityInfo(aInfo)
                .execute();
        mLastHomeActivityStartRecord = tmpOutRecord[0];
        if(mSupervisor.inResumeTopActivity) { mSupervisor.scheduleResumeTopActivities(); }}Copy the code

The obtainStarter() method returns the ActivityStarter object, which is responsible for starting the Activity, a series of setXXX() methods that pass in the various parameters needed to start the Activity, and finally execute(), which is the actual startup logic.

Before moving on to the source code, what process are you in? AMS is initialized in the system_server process, so all the work above takes place in the system_server process. The startActivity() method we typically use during development is obviously called in the application process. What about the call chain for the normal startActivity() method? Check out the activity.startActivity () method.

> Activity.java    

@Override
    public void startActivity(Intent intent, @Nullable Bundle options) {
        if(options ! =null) {
            startActivityForResult(intent, -1, options);
        } else {
            startActivityForResult(intent, -1); }}public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) {
        if (mParent == null) {
            options = transferSpringboardActivityOptions(options);
            / / call Instrumentation. ExecStartActivity () method
            Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
            if(ar ! =null) {
              / / callback ActivityResult
                mMainThread.sendActivityResult(
                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                    ar.getResultData());
            }
            if (requestCode >= 0) {
                mStartedActivity = true;
            }

            cancelInputsAndStartExitTransition(options);
        } else {
          / / in the end is called Instrumentation. ExecStartActivity () method
            if(options ! =null) {
                mParent.startActivityFromChild(this, intent, requestCode, options);
            } else {
                mParent.startActivityFromChild(this, intent, requestCode); }}}Copy the code

The execStartActivity() method of the Instrumentation is eventually called. Instrumentation is a very important class, Activity start, lifecycle callbacks are inseparable from it. You’ll encounter this class many times later.

> Instrumentation.java

public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) { IApplicationThread whoThread = (IApplicationThread) contextThread; .try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess(who);
            // Binder calls AMS to start the Activity
            intresult = ActivityManager.getService() .startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target ! =null ? target.mEmbeddedID : null,
                        requestCode, 0.null, options);
            // Check startup results
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }
Copy the code

Here, the AMS startActivity() method is called with Binder. ActivityManager. GetService () don’t think it must be for AMS proxy objects.

> ActivityManager.java

public static IActivityManager getService(a) {
    return IActivityManagerSingleton.get();
}

private static final Singleton<IActivityManager> IActivityManagerSingleton =
        new Singleton<IActivityManager>() {
            @Override
            protected IActivityManager create(a) {
                final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                final IActivityManager am = IActivityManager.Stub.asInterface(b);
                returnam; }};Copy the code

We then go to the AMS startActivity() method.

> ActivityManagerService.java    

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

    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");

        // TODO: Switch to user app stacks here.
        return mActivityStartController.obtainStarter(intent, "startActivityAsUser") // Get the ActivityStarter object
                .setCaller(caller)
                .setCallingPackage(callingPackage)
                .setResolvedType(resolvedType)
                .setResultTo(resultTo)
                .setResultWho(resultWho)
                .setRequestCode(requestCode)
                .setStartFlags(startFlags)
                .setProfilerInfo(profilerInfo)
                .setActivityOptions(bOptions)
                .setMayWait(userId)
                .execute();

    }
Copy the code

The next step is similar to starting the Home Activity before. Get the ActivityStarter object, provide the parameters, and finally execute().

ObtainStarter () gets the ActivityStarter object in factory mode.

    ActivityStarter obtainStarter(Intent intent, String reason) {
        return mFactory.obtain().setIntent(intent).setReason(reason);
    }
Copy the code

The default implementation is mFactory ActivityStarter DefaultFactory.

> ActivityStarter.java   

static class DefaultFactory implements Factory {
        /** * The maximum count of starters that should be active at one time: * 1. last ran starter (for logging and post activity processing) * 2. current running starter * 3. starter from re-entry In (2) * * A maximum of three starters can be activated simultaneously. * /
        private final int MAX_STARTER_COUNT = 3;

        private ActivityStartController mController;
        private ActivityManagerService mService;
        private ActivityStackSupervisor mSupervisor;
        private ActivityStartInterceptor mInterceptor;

        private SynchronizedPool<ActivityStarter> mStarterPool =
                new SynchronizedPool<>(MAX_STARTER_COUNT);

        DefaultFactory(ActivityManagerService service,
                ActivityStackSupervisor supervisor, ActivityStartInterceptor interceptor) {
            mService = service;
            mSupervisor = supervisor;
            mInterceptor = interceptor;
        }

        @Override
        public void setController(ActivityStartController controller) {
            mController = controller;
        }

        @Override
        public ActivityStarter obtain(a) {
            // From SynchronizedPool
            ActivityStarter starter = mStarterPool.acquire();

            if (starter == null) {
                starter = new ActivityStarter(mController, mService, mSupervisor, mInterceptor);
            }

            return starter;
        }

        @Override
        public void recycle(ActivityStarter starter) {
            starter.reset(true /* clearRequest*/); mStarterPool.release(starter); }}Copy the code

A synchronous object cache pool of capacity 3 is provided to cache ActivityStarter objects. The setXXX() methods are parameter configurations. Note that the setMayWait method sets the mayWait parameter to true. Let’s go straight to the actual execution, the execute() function.

> ActivityStarter.java       

int execute(a) {
        try {
            if (mRequest.mayWait) { The setMayWait() method sets mayWait to true
                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,
                        mRequest.originatingPendingIntent);
            } else {
                returnstartActivity(mRequest.caller, mRequest.intent, mRequest.ephemeralIntent, mRequest.resolvedType, mRequest.activityInfo, mRequest.resolveInfo, mRequest.voiceSession, mRequest.voiceInteractor, mRequest.resultTo, mRequest.resultWho, mRequest.requestCode, mRequest.callingPid, mRequest.callingUid, mRequest.callingPackage, mRequest.realCallingPid, mRequest.realCallingUid, mRequest.startFlags, mRequest.activityOptions, mRequest.ignoreTargetSecurity, mRequest.componentSpecified, mRequest.outActivity, mRequest.inTask, mRequest.reason, mRequest.allowPendingRemoteAnimationRegistryLookup, mRequest.originatingPendingIntent); }}finally {
            Reclaim the current ActivityStarter objectonExecutionComplete(); }}Copy the code

StartActivityMayWait () is then called.

>  ActivityStarter.java

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,
            PendingIntentRecord originatingPendingIntent) {...// Save a copy in case ephemeral needs it
        final Intent ephemeralIntent = new Intent(intent);
        // Don't modify the client's object!
        // Create a new intent without modifying the original intent on the client
        intent = new Intent(intent);
        if(componentSpecified && ! (Intent.ACTION_VIEW.equals(intent.getAction()) && intent.getData() ==null) &&! Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE.equals(intent.getAction()) && ! Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE.equals(intent.getAction()) && mService.getPackageManagerInternalLocked() .isInstantAppInstallerComponent(intent.getComponent())) { intent.setComponent(null /*component*/);
            componentSpecified = false;
        }

        / / get ResolveInfo
        ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId,
                0 /* matchFlags */, computeResolveFilterUid( callingUid, realCallingUid, mRequest.filterCallingUid)); .// Collect information about the target of the Intent.
        // Get the ActivityInfo of the target Intent
        ActivityInfo aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, profilerInfo);

        synchronized (mService) {
            finalActivityStack stack = mSupervisor.mFocusedStack; stack.mConfigWillChange = globalConfig ! =null&& mService.getGlobalConfiguration().diff(globalConfig) ! =0; .final ActivityRecord[] outRecord = new ActivityRecord[1];
            // Call startActivity()
            intres = 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, originatingPendingIntent); Binder.restoreCallingIdentity(origId); .if(outResult ! =null) {
                // Set the startup result
                outResult.result = res;

                final ActivityRecord r = outRecord[0];

                switch(res) {
                    case START_SUCCESS: {
                        mSupervisor.mWaitingActivityLaunched.add(outResult);
                        do {
                            try {
                                // Wait for the startup result
                                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; }...break; }}}returnres; }}Copy the code

To start the Activity, invoke the startActivity() method, which has two overloaded methods called in turn. It waits for the startup result.

>  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,
            SafeActivityOptions options,
            boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,
            TaskRecord inTask, boolean allowPendingRemoteAnimationRegistryLookup,
            PendingIntentRecord originatingPendingIntent) {
        int err = ActivityManager.START_SUCCESS;

        ProcessRecord callerApp = null;
        if(caller ! =null) {
            // If caller is not empty, find ProcessRecord through AMS
            callerApp = mService.getRecordForAppLocked(caller);
            if(callerApp ! =null) {
                callingPid = callerApp.pid;
                callingUid = callerApp.info.uid;
            } else{ err = ActivityManager.START_PERMISSION_DENIED; }}// sourceRecord describes the Activity that initiated the request
        // resultRecord User describes the Activity that receives startup results
        // In general, the two activities should be the same
        ActivityRecord sourceRecord = null;
        ActivityRecord resultRecord = null; .// Get the start flag
        final intlaunchFlags = intent.getFlags(); .if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) {
            // No class was found to handle the intent
            err = ActivityManager.START_INTENT_NOT_RESOLVED;
        }

        if (err == ActivityManager.START_SUCCESS && aInfo == null) {
            // The Activity class specified in the intent was not founderr = ActivityManager.START_CLASS_NOT_FOUND; }...// Check permissions
        booleanabort = ! 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); ./ / build ActivityRecord
        ActivityRecord r = newActivityRecord(mService, callerApp, callingPid, callingUid, callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(), resultRecord, resultWho, requestCode, componentSpecified, voiceSession ! =null, mSupervisor, checkedOptions, sourceRecord); .// Get the ActivityStack of the current capture focus
        final ActivityStack stack = mSupervisor.mFocusedStack;

        // If you start a new activity with a different UID than the activity currently in resume state, check to see if app switching is allowed
        if (voiceSession == null && (stack.getResumedActivity() == null|| stack.getResumedActivity().info.applicationInfo.uid ! = realCallingUid)) {if(! mService.checkAppSwitchAllowedLocked(callingPid, callingUid, realCallingPid, realCallingUid,"Activity start")) {
                mController.addPendingActivityLaunch(new PendingActivityLaunch(r,
                        sourceRecord, startFlags, stack, callerApp));
                ActivityOptions.abort(checkedOptions);
                // Do not allow switching, return directly
                returnActivityManager.START_SWITCHES_CANCELED; }}...// Call overloaded methods
        return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags,
                true /* doResume */, checkedOptions, inTask, outActivity);
    }
Copy the code
>  ActivityStarter.java

        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 {
            // Delay layout
            mService.mWindowManager.deferSurfaceLayout();
            // Call startActivityUnchecked()
            result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
                    startFlags, doResume, options, inTask, outActivity);
        } finally {
            final ActivityStack stack = mStartActivity.getStack();
            if(! ActivityManager.isStartResultSuccessful(result) && stack ! =null) {
                stack.finishActivityLocked(mStartActivity, RESULT_CANCELED,
                        null /* intentResultData */."startActivity".true /* oomAdj */);
            }
            // Restore the layout
            mService.mWindowManager.continueSurfaceLayout();
        }

        postStartActivityProcessing(r, result, mTargetStack);

        return result;
    }
Copy the code

StartActivityUnchecked () is then called.

>  ActivityStarter.java

    private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
            ActivityRecord[] outActivity) {

        // Set the initial state for starting the Activity, including flag
        setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
                voiceInteractor);

        // Calculate mLaunchFlags
        computeLaunchingTaskFlags();

        / / mSourceStack calculation
        computeSourceStack();

        // Set the boot flag bit
        mIntent.setFlags(mLaunchFlags);

        // Find reusable activitiesActivityRecord reusedActivity = getReusableIntentActivity(); .// Not null indicates that the new activity should be inserted into the existing task stack
        if(reusedActivity ! =null) {
            if (mService.getLockTaskController().isLockTaskModeViolation(reusedActivity.getTask(),
                    (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
                            == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))) {
                Slog.e(TAG, "startActivityUnchecked: Attempt to violate Lock Task Mode");
                return START_RETURN_LOCK_TASK_MODE_VIOLATION;
            }

            final boolean clearTopAndResetStandardLaunchMode =
                    (mLaunchFlags & (FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED))
                            == (FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
                    && mLaunchMode == LAUNCH_MULTIPLE;

            if (mStartActivity.getTask() == null && !clearTopAndResetStandardLaunchMode) {
                mStartActivity.setTask(reusedActivity.getTask());
            }

            if (reusedActivity.getTask().intent == null) {
                reusedActivity.getTask().setIntent(mStartActivity);
            }

            if((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) ! =0
                    || isDocumentLaunchesIntoExisting(mLaunchFlags)
                    || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
                final TaskRecord task = reusedActivity.getTask();

                // Clear the task stack
                final ActivityRecord top = task.performClearTaskForReuseLocked(mStartActivity,
                        mLaunchFlags);

                if (reusedActivity.getTask() == null) {
                    reusedActivity.setTask(task);
                }

                if(top ! =null) {
                    if (top.frontOfTask) {
                        top.getTask().setIntent(mStartActivity);
                    }
                    / / trigger onNewIntent ()deliverNewIntent(top); }}...// Whether to create new tasks
        boolean newTask = false;
        finalTaskRecord taskToAffiliate = (mLaunchTaskBehind && mSourceRecord ! =null)? mSourceRecord.getTask() :null; .// The Activity to be started is placed at the top of the Task
        mTargetStack.startActivityLocked(mStartActivity, topFocused, newTask, mKeepCurTransition,
                mOptions);
        if (mDoResume) {
            final ActivityRecord topTaskActivity =
                    mStartActivity.getTask().topRunningActivityLocked();
            if(! mTargetStack.isFocusable() || (topTaskActivity ! =null&& topTaskActivity.mTaskOverlay && mStartActivity ! = topTaskActivity)) { mTargetStack.ensureActivitiesVisibleLocked(null.0, !PRESERVE_WINDOWS);
                mService.mWindowManager.executeAppTransition();
            } else {
                if(mTargetStack.isFocusable() && ! mSupervisor.isFocusedStack(mTargetStack)) { mTargetStack.moveToFront("startActivityUnchecked");
                }
                / / call ActivityStackSupervisor. ResumeFocusedStackTopActivityLocked () methodmSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity, mOptions); }}else if(mStartActivity ! =null) {
            mSupervisor.mRecentTasks.add(mStartActivity.getTask());
        }
        mSupervisor.updateUserStackLocked(mStartActivity.userId, mTargetStack);

        mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTask(), preferredWindowingMode,
                preferredLaunchDisplayId, mTargetStack);

        return START_SUCCESS;
    }
Copy the code

The startActivityUnchecked() method mainly handles startup flags, stacks of tasks to launch, etc. This piece of source code is very long, it has been heavily cut, only the basic call chain is retained. Interested students can check the source file for themselves. Then call the ActivityStackSupervisor resumeFocusedStackTopActivityLocked () method.

> ActivityStackSupervisor.java

        boolean resumeFocusedStackTopActivityLocked( ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) {

        if(! readyToResume()) {return false;
        }

        // The target Stack is mFocusedStack
        if(targetStack ! =null && isFocusedStack(targetStack)) {
            return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
        }

        // Get the ActivityRecord at the top of the mFocusedStack
        final ActivityRecord r = mFocusedStack.topRunningActivityLocked();
        if (r == null| |! r.isState(RESUMED)) { mFocusedStack.resumeTopActivityUncheckedLocked(null.null);
        } else if (r.isState(RESUMED)) {
            // Kick off any lingering app transitions form the MoveTaskToFront operation.
            mFocusedStack.executeAppTransition(targetOptions);
        }

        return false;
    }
Copy the code

Take after launch Activity ActivityStack and call its resumeTopActivityUncheckedLocked () method.

> ActivityStack.java

boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
        if (mStackSupervisor.inResumeTopActivity) {
            // Prevent recursive startup
            return false;
        }

        boolean result = false;
        try {
            mStackSupervisor.inResumeTopActivity = true;
            / / execution resumeTopActivityInnerLocked () method)
            result = resumeTopActivityInnerLocked(prev, options);

            final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
            if (next == null || !next.canTurnScreenOn()) {
                checkReadyForSleep();
            }
        } finally {
            mStackSupervisor.inResumeTopActivity = false;
        }

        return result;
    }

Copy the code
> ActivityStack.java

private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
    if(! mService.mBooting && ! mService.mBooted) {// AMS has not been started yet
        return false; }...if(! hasRunningActivity) {// If there is no activity in the current Stack, go to the next Stack. The Home app might start
        return resumeTopActivityInNextFocusableStack(prev, options, "noMoreActivities");
    }

    // Next is the target Activity and removes it from the following queues
    mStackSupervisor.mStoppingActivities.remove(next);
    mStackSupervisor.mGoingToSleepActivities.remove(next);
    next.sleeping = false; mStackSupervisor.mActivitiesWaitingForVisibleActivity.remove(next); .// mResumedActivity indicates the current Activity
    if(mResumedActivity ! =null) {
        // When another Activity is in onResume(), suspend it
        pausing |= startPausingLocked(userLeaving, false, next, false); }... ActivityStack lastStack = mStackSupervisor.getLastStack();if(next.app ! =null&& next.app.thread ! =null) {...synchronized(mWindowManager.getWindowManagerLock()) {
            // This activity is now becoming visible.
            if(! next.visible || next.stopped || lastActivityTranslucent) { next.setVisibility(true); }...try {
                final ClientTransaction transaction = ClientTransaction.obtain(next.app.thread,
                        next.appToken);
                // 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); transaction.addCallback(ActivityResultItem.obtain(a)); }}if(next.newIntents ! =null) {
                    transaction.addCallback(NewIntentItem.obtain(next.newIntents,
                            false /* andPause */));
                }

                next.sleeping = false;
                mService.getAppWarningsLocked().onResumeActivity(next);
                mService.showAskCompatModeDialogLocked(next);
                next.app.pendingUiClean = true;
                next.app.forceProcessStateUpTo(mService.mTopProcessState);
                next.clearOptionsLocked();
                transaction.setLifecycleStateRequest(
                        ResumeActivityItem.obtain(next.app.repProcState,
                                mService.isNextTransitionForward()));
                mService.getLifecycleManager().scheduleTransaction(transaction);

            } catch (Exception e) {
                next.setState(lastState, "resumeTopActivityInnerLocked");

                // lastResumedActivity being non-null implies there is a lastStack present.
                if(lastResumedActivity ! =null) {
                    lastResumedActivity.setState(RESUMED, "resumeTopActivityInnerLocked");
                }

                Slog.i(TAG, "Restarting because process died: " + next);
                if(! next.hasBeenLaunched) { next.hasBeenLaunched =true;
                } else  if(SHOW_APP_STARTING_PREVIEW && lastStack ! =null
                        && lastStack.isTopStackOnDisplay()) {
                    next.showStartingWindow(null /* prev */.false /* newTask */.false /* taskSwitch */);
                }
                / / call startSpecificActivityLocked ()
                mStackSupervisor.startSpecificActivityLocked(next, true.false);
                if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
                return true; }}// From this point on, if something goes wrong there is no way
        // to recover the activity.
        try {
            next.completeResumeLocked();
        } catch(Exception e) { ...... }}else{.../ / call startSpecificActivityLocked ()
        mStackSupervisor.startSpecificActivityLocked(next, true.true);
    }

    if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
    return true;
}

Copy the code

Above omitted resumeTopActivityInnerLocked () method in most of the code, there are about more than four hundred lines of code. Which need to be aware of is startPausingLocked () and startSpecificActivityLocked () method.

Before starting an Activity, if the Activity is currently in the onResume state, you need to pause it by calling its onPause. This is what startPausingLocked() does. I won’t go into details here, but I’ll write a separate article explaining the Activity declaration cycle. In addition, the onPause of the current Activity is performed before the target Activity is started. Therefore, we cannot perform time-consuming tasks in the onPause, which will cause delays in switching activities.

Another method startSpecificActivityLocked () is to start the specified Activity, we continue to go down.

> ActivityStackSupervisor.java

   void startSpecificActivityLocked(ActivityRecord r,
            boolean andResume, boolean checkConfig) {
        // Use AMS to find if the process already exists
        ProcessRecord app = mService.getProcessRecordLocked(r.processName,
                r.info.applicationInfo.uid, true);

        // The application process already exists and is bound
        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.longVersionCode,
                            mService.mProcessStats);
                }
                // Call realStartActivityLocked() when the application process already exists
                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.
        }

        // Create a process if the application process does not exist
        mService.startProcessLocked(r.processName, r.info.applicationInfo, true.0."activity", r.intent.getComponent(), false.false.true);
    }
Copy the code

AMS first checks if the application process already exists, and if it exists and attaches, the target Activity is directly started by calling realStartActivityLocked(). If the application process does not exist, create it first.

Who woke up Zygote in the Android world? This section describes how to create an application process. The Zygote process starts with the LocalSocket server on, waiting for client requests. AMS sends a request to Zygote as a socket client, and Zygote forks out a child process after receiving the request.

Android IPC communication is mostly implemented with Binder mechanisms. Why Zygote uses sockets to communicate across processes? To be honest, I don’t know. I welcome your comments.

Next, realStartActivityLocked(), as its name suggests, actually starts the Activity.

> ActivityStackSupervisor.java

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

        if(! allPausedActivitiesComplete()) {// A new activity is not started until all onPause() has been executed
            return false;
        }

        final TaskRecord task = r.getTask();
        final ActivityStack stack = task.getStack();

        beginDeferResume();

        try{...// Update the oom-adj value of the process
            mService.updateLruProcessLocked(app, true.null);
            mService.updateOomAdjLocked();

            try{.../ / add LaunchActivityItem
                final ClientTransaction clientTransaction = ClientTransaction.obtain(app.thread,
                        r.appToken);
                clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
                        System.identityHashCode(r), r.info,
                        mergedConfiguration.getGlobalConfiguration(),
                        mergedConfiguration.getOverrideConfiguration(), r.compat,
                        r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
                        r.persistentState, results, newIntents, mService.isNextTransitionForward(),
                        profilerInfo));

                // Set the life cycle state
                final ActivityLifecycleItem lifecycleItem;
                if (andResume) {
                    lifecycleItem = ResumeActivityItem.obtain(mService.isNextTransitionForward());
                } else {
                    lifecycleItem = PauseActivityItem.obtain();
                }
                clientTransaction.setLifecycleStateRequest(lifecycleItem);

                / / the key
                / / / / call ClientLifecycleManager. ScheduleTransaction ()mService.getLifecycleManager().scheduleTransaction(clientTransaction); . }catch (RemoteException e) {
                if (r.launchFailed) {
                    // If the second startup fails, finish activity
                    mService.appDiedLocked(app);
                    stack.requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null."2nd-crash".false);
                    return false;
                }
                // If the first attempt fails, restart the process and try again
                r.launchFailed = true;
                app.activities.remove(r);
                throwe; }}finally {
            endDeferResume();
        }

        r.launchFailed = false; .return true;
    }

Copy the code

This is the focus of the code above, mService getLifecycleManager () scheduleTransaction (clientTransaction); .

ClientTransaction is used again. Remember that suspending an Activity is also done through this class. Originally prepared to write the life cycle of the separate article to re-analysis, it seems that still can not escape. Interspersed here is ClientTransaction.

First mService. GetLifecycleManager () returns the ClientLifecycleManager object, this is new in Android 9.0 class. Let’s look at its scheduleTransaction() method.

> ClientLifecycleManager.java

void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
    final IApplicationThread client = transaction.getClient(); // -> ApplicationThread
    transaction.schedule(); // ClientTransaction
    if(! (clientinstanceofBinder)) { transaction.recycle(); }}Copy the code

Follow up schedule() method.

> ClientTransaction.java

public void schedule(a) throws RemoteException {
    mClient.scheduleTransaction(this);
}
Copy the code

The mClient is of type IApplicationThread, which is the Binder proxy object for ApplicationThread. So there will be cross process calls to ApplicationThread. ScheduleTransaction () method. ApplicationThread is an inner class of ActivityThread, but neither ApplicationThread nor ActivityThread has a scheduleTransaction() method. So the method of its parent class ClientTransactionHandler is called.

> ClientTransactionHandler.java

public abstract class ClientTransactionHandler {

    /** Prepare and schedule transaction for execution. */
    void scheduleTransaction(ClientTransaction transaction) {
        transaction.preExecute(this);
        The sendMessage() method is implemented in the ActivityThread classsendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction); }}Copy the code

Take a look at the sendMessage() method back in the ActivityThread class.

> ActivityThread.java

private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
    Message msg = Message.obtain();
    msg.what = what;
    msg.obj = obj;
    msg.arg1 = arg1;
    msg.arg2 = arg2;
    if (async) {
        msg.setAsynchronous(true);
    }
    mH.sendMessage(msg);
}
Copy the code

The EXECUTE_TRANSACTION message is sent to the mH and the transaction is carried with it. MH is a Handler class called H. It is responsible for main thread message processing and defines about fifty events. Find out how it handles the EXECUTE_TRANSACTION message.

> ActivityThread.java

case EXECUTE_TRANSACTION:
            final ClientTransaction transaction = (ClientTransaction) msg.obj;
           / / execution TransactionExecutor. The execute ()
           mTransactionExecutor.execute(transaction);
           if (isSystem()) {
                transaction.recycle();
           }
Copy the code

The Execute () method of TransactionExecutor is called.

> TransactionExecutor.java`

public void execute(ClientTransaction transaction) {
    final IBinder token = transaction.getActivityToken();
    log("Start resolving transaction for client: " + mTransactionHandler + ", token: " + token);

    / / callBack execution
    executeCallbacks(transaction);

    // Execute the lifecycle state
    executeLifecycleState(transaction);
    mPendingActions.clear();
    log("End resolving transaction");
}
Copy the code

Let’s start with the executeCallbacks() method.

> TransactionExecutor.java

@VisibleForTesting
public void executeCallbacks(ClientTransaction transaction) {...final int size = callbacks.size();
    for (int i = 0; i < size; ++i) {
        finalClientTransactionItem item = callbacks.get(i); . item.execute(mTransactionHandler, token, mPendingActions); item.postExecute(mTransactionHandler, token, mPendingActions); . }Copy the code

That’s the core code. Execute the execute() and postExecute() methods of the callback passed in. Remember the argument passed in from the call to addCallback() in realStartActivityLocked() earlier?

clientTransaction.addCallback(LaunchActivityItem.obtain(newIntent(r.intent), ......) ;Copy the code

That is, the Execute () method of the LaunchActivityItem is executed.

> LaunchActivityItem.java

@Override
public void execute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) {
    Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
    ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
            mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
            mPendingResults, mPendingNewIntents, mIsForward,
            mProfilerInfo, client);
    / / call ActivityThread. HandleLaunchActivity ()
    client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
    Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
Copy the code

Go back to the ActivityThread and execute its handleLaunchActivity() method.

> ActivityThread.java

@Override
public Activity handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent) {...finalActivity a = performLaunchActivity(r, customIntent); .return a;
}
Copy the code
> ActivityThread.java

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
     ActivityInfo aInfo = r.activityInfo;
     if (r.packageInfo == null) {
         r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                 Context.CONTEXT_INCLUDE_CODE);
     }

     / / get the ComponentName
     ComponentName component = r.intent.getComponent();
     if (component == null) {
         component = r.intent.resolveActivity(
             mInitialApplication.getPackageManager());
         r.intent.setComponent(component);
     }

     if(r.activityInfo.targetActivity ! =null) {
         component = new ComponentName(r.activityInfo.packageName,
                 r.activityInfo.targetActivity);
     }

     / / get the Context
     ContextImpl appContext = createBaseContextForActivity(r);
     Activity activity = null;
     try {
         java.lang.ClassLoader cl = appContext.getClassLoader();
         // Reflection creates Activity
         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) {
          ......
     }

     try {
         / / for Application
         Application app = r.packageInfo.makeApplication(false, mInstrumentation);

         if(activity ! =null) {
             CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
             Configuration config = new Configuration(mCompatConfiguration);
             if(r.overrideConfig ! =null) {
                 config.updateFrom(r.overrideConfig);
             }
             Window window = null;
             if(r.mPendingRemoveWindow ! =null && r.mPreserveWindow) {
                 window = r.mPendingRemoveWindow;
                 r.mPendingRemoveWindow = null;
                 r.mPendingRemoveWindowManager = null;
             }
             appContext.setOuterContext(activity);
             activity.attach(appContext, this, getInstrumentation(), r.token,
                     r.ident, app, r.intent, 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) {
                 // Set the theme
                 activity.setTheme(theme);
             }

             activity.mCalled = false;
             / / execution onCreate ()
             if (r.isPersistable()) {
                 mInstrumentation.callActivityOnCreate(activity, r.state, 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.setState(ON_CREATE);

         mActivities.put(r.token, r);

     } catch (SuperNotCalledException e) {
         throw e;

     } catch (Exception e) {
        ......
     }

     return activity;
 }
Copy the code

The new Instrumentation method and callActivityOnCreate() method are called respectively.

The newActivity() method reflects the creation of the Activity and calls its Attach () method.

> Instrumentation.java

public Activity newActivity(Class
        clazz, Context context, IBinder token, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, Object lastNonConfigurationInstance) throws InstantiationException,
        IllegalAccessException {
    Activity activity = (Activity)clazz.newInstance();
    ActivityThread aThread = null;
    // Activity.attach expects a non-null Application Object.
    if (application == null) {
        application = new Application();
    }
    activity.attach(context, aThread, this, token, 0 /* ident */, application, intent,
            info, title, parent, id,
            (Activity.NonConfigurationInstances)lastNonConfigurationInstance,
            new Configuration(), null /* referrer */.null /* voiceInteractor */.null /* window */.null /* activityConfigCallback */);
    return activity;
}
Copy the code

The callActivityOnCreate() method calls the activity.performCreate () method, eventually calling back to the onCreate() method.

> Instrumentation.java

public void callActivityOnCreate(Activity activity, Bundle icicle) {
    prePerformCreate(activity);
    activity.performCreate(icicle);
    postPerformCreate(activity);
}
Copy the code
> Activity.java

final void performCreate(Bundle icicle) {
    performCreate(icicle, null);
}

final void performCreate(Bundle icicle, PersistableBundle persistentState) {
    mCanEnterPictureInPicture = true;
    restoreHasCurrentPermissionRequest(icicle);
    / / callback onCreate ()
    if(persistentState ! =null) {
        onCreate(icicle, persistentState);
    } else {
        onCreate(icicle);
    }
    writeEventLog(LOG_AM_ON_CREATE_CALLED, "performCreate"); mActivityTransitionState.readState(icicle); mVisibleFromClient = ! mWindow.getWindowStyle().getBoolean( com.android.internal.R.styleable.Window_windowNoDisplay,false);
    mFragments.dispatchActivityCreated();
    mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
}
Copy the code

There is a sense of relief that the onCreate() method has finally been executed. In fact, each lifecycle callback of an Activity is a similar invocation chain.

Remember how we traced it all the way to onCreate? Is the Execute () method of TransactionExecutor.

> TransactionExecutor.java`

public void execute(ClientTransaction transaction) {
    final IBinder token = transaction.getActivityToken();
    log("Start resolving transaction for client: " + mTransactionHandler + ", token: " + token);

    / / callBack execution
    executeCallbacks(transaction);

    // Execute the lifecycle state
    executeLifecycleState(transaction);
    mPendingActions.clear();
    log("End resolving transaction");
}
Copy the code

From analyzing executeCallBack() all the way to onCreate(), we’ll analyze the executeLifecycleState() method.

> TransactionExecutor.java

private void executeLifecycleState(ClientTransaction transaction) {
     final ActivityLifecycleItem lifecycleItem = transaction.getLifecycleStateRequest();
     if (lifecycleItem == null) {
         // No lifecycle request, return early.
         return;
     }

     final IBinder token = transaction.getActivityToken();
     final ActivityClientRecord r = mTransactionHandler.getActivityClient(token);

     if (r == null) {
         // Ignore requests for non-existent client records for now.
         return;
     }

     // Cycle to the state right before the final requested state.
     cycleToPath(r, lifecycleItem.getTargetState(), true /* excludeLastState */);

     // Execute the final transition with proper parameters.
     lifecycleItem.execute(mTransactionHandler, token, mPendingActions);
     lifecycleItem.postExecute(mTransactionHandler, token, mPendingActions);
 }

Copy the code

Lifecycleitem.execute (). LifecycleItem here is still assigned in realStartActivityLocked().

  lifecycleItem = ResumeActivityItem.obtain(mService.isNextTransitionForward());
Copy the code

But before we look at the ResumeActivityItem, take a look at the cycleToPath() method before the execute() method. Specific source code will not go to the analysis, its role according to the last implementation of the life cycle state, and the upcoming implementation of the life cycle state synchronization. For example, last time we called back onCreate(), this time we’re going to do ResumeActivityItem with an onStart() state in the middle, So cycleToPath () method to callback onStart (), which is call ActivityThread. HandleStartActivity (). Call chain similar to handleLaunchActivity().

Then, back to ResumeActivityItem. The execute ().

> ResumeActivityItem.java

@Override
public void execute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) {
    Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
    client.handleResumeActivity(token, true /* finalStateRequest */, mIsForward,
            "RESUME_ACTIVITY");
    Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
Copy the code

Is still call ActivityThread. HandleResumeActivity (). But there is one particular point here that I should mention.

Article first published wechat public account: Bingxin said, focus on Java, Android original knowledge sharing, LeetCode problem solving.

More latest original articles, scan code to pay attention to me!

> ActivityThread.java

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
         String reason) {... r.activity.mVisibleFromServer =true;
         mNumVisibleActivities++;
         if (r.activity.mVisibleFromClient) {
            // The page is visibler.activity.makeVisible(); }}// Execute Idler when main thread is idle
     Looper.myQueue().addIdleHandler(new Idler());
 }
Copy the code

The makeVisible() method makes the DecorView visible.

> Activity.java

void makeVisible(a) {
    if(! mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded =true;
    }
    mDecor.setVisibility(View.VISIBLE);
}
Copy the code

The last thing to notice is looper.myQueue ().addidleHandler (new Idler()). For space reasons, I won’t cover it here and will analyze it later when I write the Activity life cycle separately. You can go to the source code to find the answer.

conclusion

After all the analysis, the Activity is finally presented to the user.

The article is smelly and long, and many of you may wonder, is this really useful? In my opinion, the two most important things for a programmer are basic skills and internal skills. Good basic skills can make us easily grasp a skill, while deep internal skills can make us easily solve problems. This is what source code can bring you.

I’ve been looking at the source code for some of the components in Jetpack, so I think the next article will be about Jetpack. Stay tuned!

This article first published wechat official account: BingxinshuaiTM, focusing on Java, Android original knowledge sharing, LeetCode problem solving.

More latest original articles, scan code to pay attention to me!