Start the Activity process

Keywords: Android Java socket supervisor

Preface

This is Android 9.0 AOSP series Let's review the general contents of the previous articles.

Pangu and Nuwa in Java World -- Zygote

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

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

The eldest son of the Zygote family -- system server

This paper mainly introduces the system server, the first process of the Zygote process fork, which hosts the creation and startup of various system services.

  • Language, time zone, region and other settings

  • Virtual machine memory settings

  • Fingerprint information, Binder call settings

  • Looper.prepareMainLooper(), create the main thread looper

  • Initialize the native service and load libandroid_servers.so

  • createSystemContext(), initializing the system context

  • Create system service manager

  • startBootstrapServices, start system boot service

  • Start core services

  • Startother services, start other services

  • Looper.loop() to open the message loop

At the end of startOtherServices, the onSystemReady() method of AMS will be called to start the desktop Activity.

In Android world, who wakes up Zygote?

This paper mainly introduces the process of AMS requesting Zygote to create application process, that is, socket communication to Zygote process, which corresponds to the first part.

  • Call Process.start() to create application process

  • ZygoteProcess is responsible for establishing socket connection with Zygote process, and sending the parameters required for creating process to Zygote's socket server

  • After the Zygote server receives the parameters, it calls ZygoteConnection.processOneCommand() to process the parameters, and the fork process.

  • Finally, find and execute the main() method of ActivityThread class through findStaticMain(), and the subprocess will start

"Ubiquitous" system core service -- Analysis of activity manager service startup process

This paper mainly introduces the starting process of activity manager service (AMS), which is closely related to the starting, switching, scheduling and application process management of the four components.

  • AMS initialization, via the constructor of ActivityManagerService.Lifecycle

  • setSystemProcess(), register various services, create ProcessRecord, and update the value of oom_adj

  • Install system Provider

  • systemReady() will eventually start the desktop Home Activity

Today, I want to introduce the startup process of Activity. The launch of Activity is a big project with a lot of details. This article will simply sort out the whole startup process, and will not go too deep into the source code details. The key issues, such as the handling of launchMode and life cycle, will be further analyzed in a separate article.

Start process analysis

First come to a flow chart, which is more convenient to understand.

Then, in the previous analysis, the systemReady() method of ActivityManagerService will finally start the desktop Hme Activity, and the method called is 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 desktop Activity
            mActivityStartController.startHomeActivity(intent, aInfo, myReason);
        }
    } else {
        Slog.wtf(TAG, "No home screen found for " + intent, new Throwable());
    }
    return true;
}

Call 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();
        }
    }

The obtainStarter() method returns the ActivityStarter object, which is responsible for the startup of Activity. A series of setXXX() methods pass in various parameters required for startup. The final execute() is the real startup logic.

Before you continue to look at the source code, think about which process you are in now? AMS is initialized in the system server process, so the above work is all in the system server process. The startActivity() method we usually use in the development process is obviously called in the application process. So, what kind of call chain is the normal startActivity() method? Follow up the Activity.startActivity() method to see.

> 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 the 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 {
          // Finally, it calls the Instrumentation.execStartActivity() method
            if (options != null) {
                mParent.startActivityFromChild(this, intent, requestCode, options);
            } else {
                mParent.startActivityFromChild(this, intent, requestCode);
            }
        }
    }

Finally, the execStartActivity() method of instrumentation will be called. Instrumentation is a very important class, which is indispensable for the start of Activity and the callback of life cycle. This class will be encountered 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 Activity
            int result = ActivityManager.getService()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
            // Test startup results
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }

Here, the startActivity() method of AMS is called through Binder. ActivityManager.getService() doesn't need to think about getting AMS proxy object.

> ActivityManager.java

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

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

Then go to AMS's 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 ActivityStarter object
                .setCaller(caller)
                .setCallingPackage(callingPackage)
                .setResolvedType(resolvedType)
                .setResultTo(resultTo)
                .setResultWho(resultWho)
                .setRequestCode(requestCode)
                .setStartFlags(startFlags)
                .setProfilerInfo(profilerInfo)
                .setActivityOptions(bOptions)
                .setMayWait(userId)
                .execute();

    }

Next, it is similar to the previous start of Home Activity. Get the ActivityStarter object, provide parameters, and finally execute().

obtainStarter() gets the ActivityStarter object through factory mode.

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

The default implementation of mFactory is 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 starter s can be activated at the same time.
         */
        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() {
            // Get from synchronized pool
            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);
        }
    }

A synchronous object cache pool with a capacity of 3 is provided to cache ActivityStarter objects. The setXXX() method is parameter configuration. Note that the setMayWait method will set the mayWait parameter to true. Let's look directly at its actual execution, the execute() function.

> ActivityStarter.java       

int execute() {
        try {
            if (mRequest.mayWait) { // Set mayWait() method 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 {
                return startActivity(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 {
            // Recycle the current ActivityStarter object
            onExecutionComplete();
        }
    }

Then call startActivityMayWait().

>  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!
        // Recreate without modifying the client's original intent
        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 ActivityInfo of target Intent
        ActivityInfo aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, profilerInfo);

        synchronized (mService) {
            final ActivityStack stack = mSupervisor.mFocusedStack;
            stack.mConfigWillChange = globalConfig != null
                    && mService.getGlobalConfiguration().diff(globalConfig) != 0;
            ......

            final ActivityRecord[] outRecord = new ActivityRecord[1];
            // Call startActivity() method
            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, originatingPendingIntent);

            Binder.restoreCallingIdentity(origId);

            ......

            if (outResult != null) {
                // Set startup results
                outResult.result = res;

                final ActivityRecord r = outRecord[0];

                switch(res) {
                    case START_SUCCESS: {
                        mSupervisor.mWaitingActivityLaunched.add(outResult);
                        do {
                            try {
                                // Wait for start 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;
                    }
                }
            }

            return res;
        }
    }

Call the startActivity() method to start the Activity, which has two overloaded methods called in turn. Wait for the startup result here.

>  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) {
            // When the caller is not empty, use AMS to find ProcessRecord
            callerApp = mService.getRecordForAppLocked(caller);
            if (callerApp != null) {
                callingPid = callerApp.pid;
                callingUid = callerApp.info.uid;
            } else {
                err = ActivityManager.START_PERMISSION_DENIED;
            }
        }

        // sourceRecord is used to describe the Activity that initiates this request
        // resultRecord user description Activity receiving startup result
        // Generally, the two activities should be the same
        ActivityRecord sourceRecord = null;
        ActivityRecord resultRecord = null;
        ......
        // Get start flag
        final int launchFlags = intent.getFlags();

        ......

        if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) {
            // No class could be found to handle the intent
            err = ActivityManager.START_INTENT_NOT_RESOLVED;
        }

        if (err == ActivityManager.START_SUCCESS && aInfo == null) {
            // The Activity class specified in intent was not found
            err = ActivityManager.START_CLASS_NOT_FOUND;
        }

        ......

        // Permission check
        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);

        ......

        // Build ActivityRecord
        ActivityRecord r = new ActivityRecord(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 focus
        final ActivityStack stack = mSupervisor.mFocusedStack;

        // If you start a new activity with a uid different from the activity currently in the resume state, check whether 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);
                // Switch not allowed, return directly
                return ActivityManager.START_SWITCHES_CANCELED;
            }
        }

        ......

        // Call overloaded method
        return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags,
                true /* doResume */, checkedOptions, inTask, outActivity);
    }
>  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() method
            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 layout
            mService.mWindowManager.continueSurfaceLayout();
        }

        postStartActivityProcessing(r, result, mTargetStack);

        return result;
    }

Then call startActivityUnchecked().

>  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 of starting Activity, including flag
        setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
                voiceInteractor);

        // Calculate mLaunchFlags, start flag bit
        computeLaunchingTaskFlags();

        // Calculate mSourceStack
        computeSourceStack();

        // Set start flag bit
        mIntent.setFlags(mLaunchFlags);

        // Find reusable activities
        ActivityRecord reusedActivity = getReusableIntentActivity();

       ......

        // Not equal to 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 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 a new task
        boolean newTask = false;
        final TaskRecord taskToAffiliate = (mLaunchTaskBehind && mSourceRecord != null)
                ? mSourceRecord.getTask() : null;

        ......

        // Top the Activity to be started in 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() method
                mSupervisor.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;
    }

The startActivityUnchecked() method mainly deals with the start flag, the task stack to be started, etc. The source code of this block is very long, which has been greatly reduced, and only the basic call chain is retained. Interested students can view the source files by themselves. Next, the resumeFocusedStackTopActivityLocked() method of the ActivityStackSupervisor is called.

> ActivityStackSupervisor.java

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

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

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

        // Get the ActivityRecord at the top of the mFocusedStack stack
        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;
    }

Get the ActivityStack of the Activity to be started 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;
            // Execute resumeTopActivityInnerLocked() method)
            result = resumeTopActivityInnerLocked(prev, options);

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

        return result;
    }
> 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 one. May launch Home app
        return resumeTopActivityInNextFocusableStack(prev, options, "noMoreActivities");
    }

    // next is the target Activity. Remove it from the following queues
    mStackSupervisor.mStoppingActivities.remove(next);
    mStackSupervisor.mGoingToSleepActivities.remove(next);
    next.sleeping = false;
    mStackSupervisor.mActivitiesWaitingForVisibleActivity.remove(next);

    ......

    // mResumedActivity refers to the current Activity
    if (mResumedActivity != null) {
        // When another Activity is in onResume(), pause it first
        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;
}

Most of the code in the resumeTopActivityInnerLocked() method is omitted above. The original code is about 400 lines. Note the startPausingLocked() and startSpecificActivityLocked() methods.

Before you start an Activity, if the current Activity is in the onResume state, you need to pause it first, that is, call its onPause. This is the responsibility of the startPausingLocked() method. We will not analyze it in detail here, and we will write a separate article to explain the declaration cycle call of Activity. In addition, we need to execute the onPause of the current Activity before starting the target Activity, so we can't execute the time-consuming task in onPause, which will cause the Activity to be stuck when switching.

Another method, startSpecificActivityLocked(), is to start the specified Activity. Let's continue.

> ActivityStackSupervisor.java

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

        // 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 process if application process does not exist
        mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
                "activity", r.intent.getComponent(), false, false, true);
    }

First, use AMS to find out whether the application process already exists. If it already exists and attach es, call realStartActivityLocked() to start the target Activity directly. If the application process does not exist, create the application process first.

stay In Android world, who wakes up Zygote? The creation process of the application process has been described. Let's briefly say that when the Zygote process is started, the LocalSocket server is enabled, waiting for the client's request. AMS sends a request to Zygote as the socket client. After Zygote receives the request, it fork s out the subprocess.

Today I see a very interesting question. Most IPC communication in Android is realized through Binder mechanism. Why does Zygote communicate across processes through socket? To be honest, I don't know. Welcome to leave your opinion.

Then there is realStartActivityLocked(), as the name implies, to start the Activity.

> ActivityStackSupervisor.java

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

        if (!allPausedActivitiesComplete()) {
            // The new activity will not be started until all onPause() execution ends
            return false;
        }

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

        beginDeferResume();

        try {
            ......

            // Update process oom adj value
            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 lifecycle state
                final ActivityLifecycleItem lifecycleItem;
                if (andResume) {
                    lifecycleItem = ResumeActivityItem.obtain(mService.isNextTransitionForward());
                } else {
                    lifecycleItem = PauseActivityItem.obtain();
                }
                clientTransaction.setLifecycleStateRequest(lifecycleItem);

                //  A key
                //  //Call clientlife cyclemanager. Scheduletransaction()
                mService.getLifecycleManager().scheduleTransaction(clientTransaction);

                ......

            } catch (RemoteException e) {
                if (r.launchFailed) {
                    // Second start failed, finish activity
                    mService.appDiedLocked(app);
                    stack.requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null,
                            "2nd-crash", false);
                    return false;
                }
                // First failure, restart process and try again
                r.launchFailed = true;
                app.activities.remove(r);
                throw e;
            }
        } finally {
            endDeferResume();
        }

        r.launchFailed = false;
        ......
        return true;
    }

The key point above is this code, mservice. Getlifecycle manager(). Scheduletransaction (clienttransaction).

Here we use ClientTransaction again. Remember the pause Activity mentioned above, which is also implemented through this class. I was going to write a separate article on the life cycle for further analysis. It seems that I can't escape. Here's a little bit of ClientTransaction.

First, mService.getLifecycleManager() returns the clientlifcyclemanager object, which is a new class in Android 9.0. Let's take a look at its scheduleTransaction() method.

> ClientLifecycleManager.java

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

Follow up the schedule() method.

> ClientTransaction.java

public void schedule() throws RemoteException {
    mClient.scheduleTransaction(this);
}

Here, mClient is of iaapplicationthread type, which is the Binder proxy object of ApplicationThread, so it will call the ApplicationThread.scheduleTransaction() method across processes. ApplicationThread is the internal class of ActivityThread, but neither ApplicationThread nor ActivityThread has 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 class
        sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
    }

  }

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

The execute ﹣ transaction message is sent to mH, and the transaction is carried. mH is a Handler class called H. It is responsible for the main thread message processing and defines about 50 kinds of events. Find out how it handles the execute? Transaction message.

> ActivityThread.java

case EXECUTE_TRANSACTION:
            final ClientTransaction transaction = (ClientTransaction) msg.obj;
           // Execute TransactionExecutor.execute()
           mTransactionExecutor.execute(transaction);
           if (isSystem()) {
                transaction.recycle();
           }

The TransactionExecutor's execute() method was called.

> TransactionExecutor.java`

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

    // Execute callBack
    executeCallbacks(transaction);

    // Execution lifecycle state
    executeLifecycleState(transaction);
    mPendingActions.clear();
    log("End resolving transaction");
}

Let's first look at the executeCallbacks() method.

> TransactionExecutor.java

@VisibleForTesting
public void executeCallbacks(ClientTransaction transaction) {

    ......

    final int size = callbacks.size();
    for (int i = 0; i < size; ++i) {
        final ClientTransactionItem item = callbacks.get(i);

        ......

        item.execute(mTransactionHandler, token, mPendingActions);
        item.postExecute(mTransactionHandler, token, mPendingActions);

        ....
}

That's the core code. Execute the execute() and postExecute() methods of the incoming callback. Do you remember the parameters in the previous realStartActivityLocked() method calling addCallback()?

clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent), ......);

That is to say, the execute() method of LaunchActivityItem will be 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);
}

Go around, go back to ActivityThread again, and execute its handleLaunchActivity() method.

> ActivityThread.java

@Override
public Activity handleLaunchActivity(ActivityClientRecord r,
        PendingTransactionActions pendingActions, Intent customIntent) {

    ......

    final Activity a = performLaunchActivity(r, customIntent);

    ......

    return a;
}
> 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 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 Context
     ContextImpl appContext = createBaseContextForActivity(r);
     Activity activity = null;
     try {
         java.lang.ClassLoader cl = appContext.getClassLoader();
         // Reflection create 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 {
         // Get 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 up themes
                 activity.setTheme(theme);
             }

             activity.mCalled = false;
             // Execute 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;
 }

Here comes the figure of Instrumentation, which calls the newActivity() method and callActivityOnCreate() method respectively.

The newActivity() method reflects the creation of an 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;
}

The callActivityOnCreate() method calls the Activity.performCreate() method and finally calls the onCreate() method.

> Instrumentation.java

public void callActivityOnCreate(Activity activity, Bundle icicle) {
    prePerformCreate(activity);
    activity.performCreate(icicle);
    postPerformCreate(activity);
}
> 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());
}

See here, there is a sense of relief, and finally to the onCreate() method. In fact, each life cycle callback of Activity is a similar call chain.

Do you remember which method traced all the way to onCreate? Is the execute() method of the TransactionExecutor.

> TransactionExecutor.java`

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

    // Execute callBack
    executeCallbacks(transaction);

    // Execution lifecycle state
    executeLifecycleState(transaction);
    mPendingActions.clear();
    log("End resolving transaction");
}

In the previous analysis, executeCallBack() traces all the way to onCreate(). Next, we will analyze the executelifcyclestate() 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);
 }

I'm familiar with it, and I see lifecycleItem.execute(). The lifecycleItem here is still assigned in the realStartActivityLocked() method.

  lifecycleItem = ResumeActivityItem.obtain(mService.isNextTransitionForward());

But before analyzing the ResumeActivityItem, notice the cycleToPath() method before the execute() method. The specific source code will not be analyzed. Its function is to synchronize with the upcoming lifecycle state according to the last executed lifecycle state. It's not so easy to understand. For example, the onCreate() method was called back last time. This time, the ResumeActivityItem is to be executed, and there is an onStart() state in the middle. Then the cycleToPath() method will call back onStart(), that is, the ActivityThread.handleStartActivity(). A call chain similar to handleLaunchActivity().

Then go back to ResumeActivityItem.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);
}

Still calling ActivityThread.handleResumeActivity(). But there is something special here. I have to carry it out.

The article begins with the WeChat public number: Bing Xin said, focus on Java, Android original knowledge sharing, LeetCode solution.

More new original articles, scan and follow me!

> ActivityThread.java

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
         String reason) {
        ......
    
         r.activity.mVisibleFromServer = true;
         mNumVisibleActivities++;
         if (r.activity.mVisibleFromClient) {
            // Visible page
             r.activity.makeVisible();
         }
     }

     // Idler will be executed when the main thread is idle
     Looper.myQueue().addIdleHandler(new Idler());
 }

The makeVisible() method makes DecorView visible.

> Activity.java

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

The last thing to notice is Looper.myQueue().addIdleHandler(new Idler()). Because of the length, I won't introduce it here. I will analyze it later when I write the Activity life cycle separately. You can first go to the source code to find the answer.

summary

All the way through the analysis, the Activity finally shows to the user.

In fact, the article is smelly and long. Many people may have questions. Is it really useful to read these articles? In my opinion, the two most important things for a programmer are basic skills and internal skills. Good basic skills can make us easily master a technology, and deep internal skills can make us solve problems. Source code can bring you, it is these.

Recently I read the source code of some components in Jetpack. The next article should be about Jetpack. Coming soon!

The article begins with the WeChat public number: Bing Xin said, focus on Java, Android original knowledge sharing, LeetCode solution.

More new original articles, scan and follow me!

Posted by Nymphetamine on Tue, 03 Dec 2019 15:47:28 -0800