Now let’s take a look at the Android startup process. The Android startup process is quite important, because in this process, in addition to completing the initialization of the Linux system, we also need to complete the initialization of the Android basic services and startup interface.

We won’t delve too deeply into the source code in this article. Because every functional module in Android involves a lot of code calls at the Framework level. Digging too much into the source code just gets us lost in layers of call stacks. In contrast, I prefer to just put out some core code and tease out the stack process. When we need to dig deeper into this area, it’s enough to know where to look for answers.

1. Start the system

After pressing the power, first load the boot program BootLoader into RAM; Then, the boot program BootLoader is executed to pull up the system OS. Next, start the Linux kernel; The first user process started in the kernel is the init process, which starts the Zygote service by parsing init.rc. Zygote will further start SystemServer; In SystemServer, Android launches a series of system services for users to call.

Android system corresponds to the init program in the Android. Mk is located in the system/core/init/Android. Mk, is a Makefile, used to describe our source code to compile system. We can use the make tool to execute the file. So, mk files are like Shell scripts.

1.1 Executing the Init program

After the Linux kernel is loaded, start the init process. In the 8.0 source code, the first stage of system startup is to create the various directories required for startup. In the latest source code, this code is included in init_first_stage:

    // platform/system/core/init/init_first_stage.cpp
    int main(int argc, char** argv) {
        if (REBOOT_BOOTLOADER_ON_PANIC) {
            InstallRebootSignalHandlers(a); } boot_clock::time_point start_time = boot_clock::now(a); std::vector<std::pair<std::string,int>> errors;
    #define CHECKCALL(x) \
        if(x ! = 0) errors.emplace_back(#x" failed", errno);
        umask(0);
        CHECKCALL(clearenv());
        CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
        // Create directory
        CHECKCALL(mount("tmpfs"."/dev"."tmpfs", MS_NOSUID, "mode=0755"));
        CHECKCALL(mkdir("/dev/pts".0755));
        CHECKCALL(mkdir("/dev/socket".0755));
        // ...
    #undef CHECKCALL
        auto reboot_bootloader = [](const char*) { RebootSystem(ANDROID_RB_RESTART2, "bootloader"); };
        InitKernelLogging(argv, reboot_bootloader);
        // ...
        const char* path = "/system/bin/init";
        const char* args[] = {path, nullptr};
        execv(path, const_cast<char**>(args));
        return 1;
    }
Copy the code

Execv () is called several times during system startup, and the main() method is re-executed each time the method is called. The method is as follows:

int execv(const char *progname, char *const argv[]);   //#include <unistd.h>
Copy the code

Execv () will stop the execution of the current process and replace the stopped process with progname application process. The process ID will not change.

Next, the init. CPP process entry function main:

    // platform/system/core/init/init.cpp
    int main(int argc, char** argv) {
        if (!strcmp(basename(argv[0]), "ueventd")) {
            return ueventd_main(argc, argv);
        }
        if (argc > 1&&!strcmp(argv[1]."subcontext")) {
            android::base::InitLogging(argv, &android::base::KernelLogger);
            const BuiltinFunctionMap function_map;
            return SubcontextMain(argc, argv, &function_map);
        }
        if (REBOOT_BOOTLOADER_ON_PANIC) {
            // Initializes the processing signal to restart the system
            InstallRebootSignalHandlers(a); }// ...
        property_init(a);// Initialize the property service
        // ...
        Epoll epoll; // Create an epoll handle
        if (auto result = epoll.Open(a); ! result) {PLOG(FATAL) << result.error(a); }InstallSignalFdHandler(&epoll);
        // ...
        StartPropertyService(&epoll); // Start the properties service
        // ...

        ActionManager& am = ActionManager::GetInstance(a); ServiceList& sm = ServiceList::GetInstance(a);LoadBootScripts(am, sm); // Load the startup script
        // ...
        // Charging mode does not start the system, otherwise start the system
        std::string bootmode = GetProperty("ro.bootmode"."");
        if (bootmode == "charger") {
            am.QueueEventTrigger("charger");
        } else {
            am.QueueEventTrigger("late-init");
        }
        // ...
        return 0;
    }
Copy the code

The init.rc file is parsed here in the LoadBootScripts() method. Refer to the AOSP documentation for the meaning of this file directive: Android Init Language. The classes that complete the parsing are ActionManager, Parser, and XXParser, all located under the System /core/init directory. In addition, there are classes such as Action and Service. Their purpose is that the various Parsers are used to parse instructions in RC files. The parsed instructions are encapsulated into objects such as Actions and services.

When we open the file, we can see that it contains the following two lines of code, which use placeholders, that is, it loads the corresponding files in the current directory based on the current environment variables. In addition, we can see that init.zygote64.rc and init.zygote32.rc do exist under the system/core/rootdir directory.

import /init.${ro.hardware}.rc
import /init.${ro.zygote}.rc
Copy the code

Take the example of rinit.zygote64.rc, which tells the init process to create a process named zygote. The execution path is /system/bin/app_process64,

// platform/system/core/rootdir/init.zygote64.rc
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
    class main
    // ...
Copy the code

We can see that it uses the Service directive, so it will be resolved to service.

Note that at the end of the main() method of init.cpp, late init. Rc is configured to listen for late init events via ON. It also uses trigger to trigger other commands. These commands are also listened on. (Of course, rc is just a configuration file, and the actual logic is parsed and done in the program.)

Among the events triggered by the late-init event is the Zygote-start event. And zygote-start monitoring implementation and according to the monitoring conditions and a variety of. However, they both call the Start Zygote method. The start here is mapped to the builtins class’s do_start() method. This method calls the start() method of the Service. This method basically calls clone or fork to create the child process, then calls execve to execute the configuration binary, and then performs the configuration based on the previous configuration in the RC file. So the program will start app_process64.

// platform/system/core/init/service.cpp
Result<Success> Service::Start(a) {
    // ...
    pid_t pid = - 1;
    if (namespace_flags_) {
        pid = clone(nullptr.nullptr, namespace_flags_ | SIGCHLD, nullptr);
    } else {
        pid = fork();
    }

    if (pid == 0) {
        umask(077);
        // ...
        // Internal call execv() to execute
        if (!ExpandArgsAndExecv(args_, sigstop_)) {
            PLOG(ERROR) << "cannot execve('" << args_[0] < <"')";
        }
        _exit(127);
    }
    // ...
    return Success(a); }Copy the code

Mapping reference source: the system/core/init/builtins. CPP about rc file command parsing, you can refer to the Android 8.0 system startup process of the init. Rc parsing and service process (7)”

The mk file corresponding to the rc file /system/bin/app_process64 is located under the directory /base/ CMDS /app_process/Android.mk. From this file, we can see that the corresponding source file for either app_process, app_process32, or app_process64 is app_main.cpp. The program will then enter the main() method of app_main.cpp.

After entering the main() method, the parameters of the instruction are parsed.

// platform/frameworks/base/cmds/app_process/app_main.cpp
int main(int argc, char* const argv[])
{
    // ...
    bool zygote = false;
    bool startSystemServer = false;
    bool application = false;
    String8 niceName;
    String8 className;

    ++i;  // Skip unused "parent dir" argument.
    while (i < argc) {
        const char* arg = argv[i++];
        if (strcmp(arg, "--zygote") = =0) {
            zygote = true;
            niceName = ZYGOTE_NICE_NAME;
        } else if (strcmp(arg, "--start-system-server") = =0) {
            startSystemServer = true;
        } else if (strcmp(arg, "--application") = =0) {
            application = true;
        } else if (strncmp(arg, "--nice-name=".12) = =0) {
            niceName.setTo(arg + 12);
        } else if (strncmp(arg, "--".2) != 0) {
            className.setTo(arg);
            break;
        } else {
            --i;
            break; }}// ...
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (className) {
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    } else {
        app_usage();
    }
}
Copy the code

As we can see from the previous RC file, the argument is –zygote, so ZygoteInit’s main() method will be called to continue execution. The Runtime here is AndroidRuntime, and the start() method here is a JNI call. The static main() method in Java will be called to continue execution. This is important, because we usually call C++ methods in Java, and this is calling Java methods in C++. Its source code is at Base \ Core \jni\AndroidRuntime.cpp.

// platform/frameworks/base/core/jni/AndroidRuntime.cpp
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    // ...

    // Get the ANDROID_ROOT environment variable
    const char* rootDir = getenv("ANDROID_ROOT");
    if (rootDir == NULL) {
        rootDir = "/system";
        if (!hasDir("/system")) {
            return;
        }
        setenv("ANDROID_ROOT", rootDir, 1);
    }

    // Start the VM
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    if (startVm(&mJavaVM, &env, zygote) ! =0) {
        return;
    }
    onVmCreated(env);

    / /... Parse the main function to fire below

    // Start the thread, the current thread will become the main thread of the virtual machine, and end when the virtual machine exits.
    char* slashClassName = toSlashClassName(className ! =NULL ? className : "");
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
    } else {
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main"."([Ljava/lang/String;)V");
        if (startMeth == NULL) {
            ALOGE("JavaVM unable to find main() in '%s'\n", className);
        } else {
            env->CallStaticVoidMethod(startClass, startMeth, strArray); }}// ...
}
Copy the code

In the above method, we can see that we need to call the startVM() method to start the vm. ZygoteInit’s static main() method is executed using the handle function env after the virtual machine is started.

1.2 start the Zygote

Based on the preceding analysis, the system has started the VM. And after the virtual machine is started, the program enters the main() method in ZygoteInit,

    // platform/framework/base/core/java/com/android/internal/os/ZygoteInit.java
    public static void main(String argv[]) {
        // ...
        try {
            // ...
            boolean startSystemServer = false;
            String socketName = "zygote";
            String abiList = null;
            boolean enableLazyPreload = false;
            for (int i = 1; i < argv.length; i++) {
                if ("start-system-server".equals(argv[i])) {
                    startSystemServer = true;
                } else if ("--enable-lazy-preload".equals(argv[i])) {
                    enableLazyPreload = true;
                } else if (argv[i].startsWith(ABI_LIST_ARG)) {
                    abiList = argv[i].substring(ABI_LIST_ARG.length());
                } else if (argv[i].startsWith(SOCKET_NAME_ARG)) {
                    socketName = argv[i].substring(SOCKET_NAME_ARG.length());
                } else {
                    throw new RuntimeException("Unknown command line argument: "+ argv[i]); }}// Register the Socket named zygote
            zygoteServer.registerServerSocketFromEnv(socketName);
            // Decide whether to preload the resource
            if(! enableLazyPreload) {/ /... Recording Log Information
                preload(bootTimingsTraceLog);
                / /... Recording Log Information
            } else {
                Zygote.resetNicePriority();
            }

            gcAndFinalize(); // Perform GC to clear space

            // ...

            if (startSystemServer) {
                // Start SystemServer, parent if r is null, child otherwise
                Runnable r = forkSystemServer(abiList, socketName, zygoteServer);
                if(r ! =null) {
                    r.run();
                    return; }}// Wait for the AMS connection request
            caller = zygoteServer.runSelectLoop(abiList);
        } catch (Throwable ex) {
            throw ex;
        } finally {
            zygoteServer.closeServerSocket();
        }

        if(caller ! =null) { caller.run(); }}Copy the code

There are several main things done here:

First, create the Socket on the Server side. In this case, create the ZygoteServer object. It provides commands to wait for UNIX sockets and provides a way to fork the virtual machine.

Then, the resource is preloaded.

Next, start SystemServer. This is done by calling forkSystemServer(). A command argument is built and Zygote’s static method is called to Fork a child process. Internally, the JNI layer’s nativeForkSystemServer method is called to Fork.

    // platform/framework/base/core/java/com/android/internal/os/Zygote.java
    private static Runnable forkSystemServer(String abiList, String socketName, ZygoteServer zygoteServer) {
        // ...

        /* Hardcoded command line to start System Server */
        String args[] = {
            "--setuid=1000"."--setgid=1000"."- setgroups = 1001100 2100 3100 4100 5100 6100 7100 8100 9101 0101 8102 1102 3102 4103 2106 5300 1300 2300 3300 6300 7, 30 09301. ""."--capabilities=" + capabilities + "," + capabilities,
            "--nice-name=system_server"."--runtime-args"."--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,
            "com.android.server.SystemServer"}; ZygoteConnection.Arguments parsedArgs =null;
        int pid;
        try {
            parsedArgs = new ZygoteConnection.Arguments(args);
            ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
            ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);

            boolean profileSystemServer = SystemProperties.getBoolean(
                    "dalvik.vm.profilesystemserver".false);
            if (profileSystemServer) {
                parsedArgs.runtimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;
            }

            /* Request fork System Server process */
            pid = Zygote.forkSystemServer(
                    parsedArgs.uid, parsedArgs.gid,
                    parsedArgs.gids,
                    parsedArgs.runtimeFlags,
                    null,
                    parsedArgs.permittedCapabilities,
                    parsedArgs.effectiveCapabilities);
        } catch (IllegalArgumentException ex) {
            throw new RuntimeException(ex);
        }

        /* The child is processed */
        if (pid == 0) {
            if (hasSecondZygote(abiList)) {
                waitForSecondaryZygote(socketName);
            }

            zygoteServer.closeServerSocket();
            // Stop the rest of the work for the new system Server process
            return handleSystemServerProcess(parsedArgs);
        }

        return null;
    }
Copy the code

Finally, the SELECT loop starts and waits for new connections. The following is the definition of this method, which we won’t explain much more than the comments in the code are quite complete.

    // platform/framework/base/core/java/com/android/internal/os/ZygoteServer.java
    Runnable runSelectLoop(String abiList) {
        // ...
        while (true) { // Use an infinite loop to listen
            // ...
            for (int i = pollFds.length - 1; i >= 0; --i) {
                if ((pollFds[i].revents & POLLIN) == 0) {
                    continue;
                }
                if (i == 0) { // walk through to the last one
                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
                    peers.add(newPeer);
                    fds.add(newPeer.getFileDesciptor());
                } else { // Waiting for connection
                    try {
                        ZygoteConnection connection = peers.get(i);
                         // processOneCommand() reads a command from the socket command. If it succeeds, it forks the child process and returns its main method. If it is the parent process, null should always be returned
                        final Runnable command = connection.processOneCommand(this);
                        if (mIsForkChild) {
                            // The child process requires at least one command
                            if (command == null) {
                                throw new IllegalStateException("command == null");
                            }
                            return command;
                        } else {
                            // Server process, there should be no command to execute
                            if(command ! =null) {
                                throw new IllegalStateException("command ! = null");
                            }
                            if (connection.isClosedByPeer()) { // Close the requestconnection.closeSocket(); peers.remove(i); fds.remove(i); }}}catch (Exception e) {
                        if(! mIsForkChild) {// An error occurs, and the request is closed
                            ZygoteConnection conn = peers.remove(i);
                            conn.closeSocket();
                            fds.remove(i);
                        } else {
                            throwe; }}finally {
                        mIsForkChild = false;
                    }
                }
            }
        }
    }
Copy the code

When a command is read from the socket using acceptCommandPeer(), the child process is forked and a Runnable is returned to initiate the main() method of the child process. This part of the logic is in the acceptCommandPeer() method. It calls the static method forkAndSpecialize() of the Zygote class to create the child process. The handleChildProc() method is then called to return the main() method used to start the child process. It is defined as follows,

    // platform/framework/base/core/java/com/android/internal/os/ZygoteConnection.java
    private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors,
            FileDescriptor pipeFd, boolean isZygote) {
        // ...
        if(parsedArgs.invokeWith ! =null) {
            throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned");
        } else {
            if(! isZygote) {return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs,
                        null /* classLoader */);
            } else {
                return ZygoteInit.childZygoteInit(parsedArgs.targetSdkVersion,
                        parsedArgs.remainingArgs, null /* classLoader */); }}}Copy the code

IsZygote here means whether to start a process as a child of the current process, specified with the –start-child-zygote parameter. Because the process we are currently starting is the parent Zygote process, the zygoteinit.zygoteinit () method will be called to continue processing. The core code for this method is just two lines,

    // platform/framework/base/core/java/com/android/internal/os/ZygoteInit.java
    public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {
        // ...
        ZygoteInit.nativeZygoteInit();
        return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
    }
Copy the code

NativeZygoteInit () is a native method for starting Binder thread pools. The corresponding native method is defined in AndroidRuntime.cpp. The gCurRuntime here is AppRumtime, defined in app_main.cpp.

// platform/frameworks/base/cmds/app_process/app_main.cpp
static void com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env, jobject clazz)
{
    gCurRuntime->onZygoteInit(a); }Copy the code

The applicationInit() method is primarily used to trigger SystemServer’s main() method. In the latest code, the methods and arguments to be fired are wrapped in a Runnable and the reflection firing method is called in its run() method. So, we’ll go to SystemServer’s main() method. This class is located under Base \services\ Java \com\ Android \server. The method is defined as follows,

    // platform/frameworks/base/service/java/com/android/server/SystemServer.java
    public static void main(String[] args) {
        new SystemServer().run();
    }

    // platform/frameworks/base/service/java/com/android/server/SystemServer.java
    private void run(a) {
        try {
            // ...

            Looper.prepareMainLooper(); // Create the main thread message loop
            System.loadLibrary("android_servers"); // Load the so library
            performPendingShutdown();
            // Create the system context
            createSystemContext();
            // ServiceManager!!! It is used to manage the life cycle of creating and starting services in system services
            mSystemServiceManager = new SystemServiceManager(mSystemContext);
            mSystemServiceManager.setStartInfo(mRuntimeRestart,
                    mRuntimeStartElapsedTime, mRuntimeStartUptime);
            LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
            SystemServerInitThreadPool.get();
        } finally {
            traceEnd();  // InitBeforeStartServices
        }

        // Start the service
        try {
            traceBeginAndSlog("StartServices");
            startBootstrapServices(); // Start the boot service
            startCoreServices(); // Start the core service
            startOtherServices(); // Start other services
            SystemServerInitThreadPool.shutdown();
        } catch (Throwable ex) {
            throw ex;
        } finally {
            traceEnd();
        }
        // ...
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
Copy the code

As you can see from the above, the main logic in this approach is to manage the various services in the system. After the SystemServiceManager is created, it is used to manage the life cycle of creating and starting various services. For example, in startBootstrapServices(), the famous PMS and AMS are started. Starting the service is done by calling SystemServiceManager’s startService() method. This method has three overloaded methods. However, no matter which method is called, the following method will eventually be called.

    // platform/frameworks/base/services/core/com/android/server/SystemServiceManager.java
    public void startService(@NonNull final SystemService service) {
        mServices.add(service);
        long time = SystemClock.elapsedRealtime();
        try {
            service.onStart();
        } catch (RuntimeException ex) {
            throw new RuntimeException("Failed to start service " + service.getClass().getName()
                    + ": onStart threw an exception", ex); }}Copy the code

In this method, in addition to calling onStart() of the service, you register it with mServices, which is a variable of type ArrayList

used to store the started service.

In addition, we notice that a Looper loop is started in the run() method. This indicates that the system service main thread will run forever. For more on Looper, see my other post:

Android Messaging: Handler, MessageQueue, and Looper

1.3 start the Launcher

An essential part of the system startup process is to start the Launcher, which is called the Android desktop application. In the above methods, the system starts the various services it needs, and in the startOtherServices() method, the systemReady() method of the started service is called to do the logic when the system is ready to start. This includes the ams.startotherServices () method, which is a long one, so we won’t post the code. Let’s look directly at AMS’s systemReady() method. This method is also a long one, and we’re only going to take part of it,

    // platform/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
    public void systemReady(final Runnable goingCallback, TimingsTraceLog traceLog) {
        // ...
        synchronized (this) {
            // ...
            startHomeActivityLocked(currentUserId, "`");
            // ...}}Copy the code

The startHomeActivityLocked() method is called to continue to complete the desktop startup,

    // platform/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
    boolean startHomeActivityLocked(int userId, String reason) {
        // ...
        / / build a Intent used to launch the desktop program, this Intent contains a Category android. The Intent. The Category. HOME type Cateogry
        Intent intent = getHomeIntent();
        / / traverse the installation package to check whether there is any Cateogry for android. The intent. The category. The Activity of the HOME
        ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
        if(aInfo ! =null) {
            // Pass the above application information to the Intent
            intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
            aInfo = new ActivityInfo(aInfo);
            aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId);
            ProcessRecord app = getProcessRecordLocked(aInfo.processName,
                    aInfo.applicationInfo.uid, true);
            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;
                // Continue the process of starting the LaunchermActivityStartController.startHomeActivity(intent, aInfo, myReason); }}return true;
    }
Copy the code

The method will then continue into the startHomeActivity() method of ActivityStartController,

    // platform/frameworks/base/services/core/java/com/android/server/am/ActivityStartController.java
    void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason) {
        // Move the stack of the Launcher to the top
        mSupervisor.moveHomeStackTaskToTop(reason);
        // obtainStarter() will return an ActivityStarter and call its execute() to continue processing
        mLastHomeActivityStartResult = obtainStarter(intent, "startHomeActivity: " + reason)
                .setOutActivity(tmpOutRecord)
                .setCallingUid(0)
                .setActivityInfo(aInfo)
                .execute();
        mLastHomeActivityStartRecord = tmpOutRecord[0];
        if(mSupervisor.inResumeTopActivity) { mSupervisor.scheduleResumeTopActivities(); }}Copy the code

Here an ActivityStarter is returned via obtainStarter() and its execute() is called to continue processing, clearly using the Builder design pattern. The remaining flow is the start flow of the Activity. We won’t go into more detail, but we can continue this later when we introduce the Activity launch.

2, summarize

Above we have combed the main process of Android system startup, here we summarize.

Recommended information:

  1. Android 8.0 Zygote startup Process
  2. Android 8.0 system startup process init.rc parsing and Service flow (7)