Android Application Startup Process - Process Creation

Keywords: socket Android Java Linux

app process creation

As you can see in this sketch, Launcher's click on the icon to start the process involves three roles. Launcher is the Initiate Request Process (caller), AMS receives Launcher's request and processes it. If the Activity process to be started does not exist, connect to Zygote through a local socket and initiate a request to create a process to Zygote.Zygote receives the request and fork s out the app process.

The whole process involves a lot of detail, so we're just focusing on the core part and dividing it into several subsections.

1. Zygote

Zygote is an important Daemon in the android system. Its main functions are:
1. Create VM instances, preloaded classes, resources, etc.
(2) Start systemserver.
(3) Create a local socket and wait for the fork request from AMS to complete the creation of the app process.

Here's a quick look at its source code:

// frameworks/base/cmds/app_process/app_main.cpp
int main(int argc, const char* const argv[])
{
    AppRuntime runtime;
    ...
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", 
                     startSystemServer ? "start-system-server" : "");
    }
    ...
}

AppRuntime's start() function inherits from the base class AndroidRuntime, which means the code above jumps to

// frameworks/base/core/jni/AndroidRuntime.cpp
void AndroidRuntime::start(const char* className, const char* options) {
    ...
    JNIEnv* env;
    // Start Virtual Machine
    if (startVm(&mJavaVM, &env) != 0) {
        return;
    }
    // jni functions required to register android
    if (startReg(env) < 0) {
        LOGE("Unable to register all android natives\n");
        return;
    }
    ...
    // className is com.android.internal.os.ZygoteInit
    char* slashClassName = toSlashClassName(className);
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        LOGE("JavaVM unable to locate class '%s'\n", slashClassName);
    } else {
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
        if (startMeth == NULL) {
            LOGE("JavaVM unable to find main() in '%s'\n", className);
        } else {
            // Call the main(String[]) method of ZygoteInit
            env->CallStaticVoidMethod(startClass, startMeth, strArray);
        }
    }
    free(slashClassName);
    ...
}

Okay, at this point, the virtual machine is up, loading and running the main() function of the main class (ZygoteInit).

// frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
public static void main(String argv[]) {
    try {
        // Create Local Socket
        registerZygoteSocket();
        // Preloaded classes and resources, etc.
        preload();
        ...
        if (argv[1].equals("start-system-server")) {
            // Start the system server process
            startSystemServer();
        } else if (!argv[1].equals("")) {
            throw new RuntimeException(argv[0] + USAGE_STRING);
        }
        ...
        // Create a local socket and wait for client requests
        runSelectLoopMode();
        closeServerSocket();
    } catch (MethodAndArgsCaller caller) {
        // The subprocess fork() comes out and ends up here
        // In this case, the main() method of the corresponding main class (ActivityThread) is invoked through reflection
        caller.run();
    } catch (RuntimeException ex) {
        closeServerSocket();
        throw ex;
    }
}
private static void runSelectLoopMode() throws MethodAndArgsCaller {
    // fds--peers Each item is a one-to-one correspondence, that is, <File Descriptor, Connection Established > Forms a tuple
    ArrayList<FileDescriptor> fds = new ArrayList();
    ArrayList<ZygoteConnection> peers = new ArrayList();
    FileDescriptor[] fdArray = new FileDescriptor[4];

    // The first tuple is <socket listen descriptor, null>
    fds.add(sServerSocket.getFileDescriptor());
    peers.add(null);

    while (true) {
        ...
        try {
            fdArray = fds.toArray(fdArray);
            // native method, using select io multiplexing mechanism
            index = selectReadable(fdArray);
        } catch (IOException ex) {
            throw new RuntimeException("Error in select()", ex);
        }

        if (index < 0) {
            throw new RuntimeException("Error in select()");
        } else if (index == 0) { 
            // socket listen descriptor with index 0 and selectReadable() returns 0 to indicate a new connection
            ZygoteConnection newPeer = acceptCommandPeer();
            // Add the newly connected tuple to the collection
            peers.add(newPeer);
            fds.add(newPeer.getFileDesciptor());
        } else {
            // Index>0 indicates that the connected descriptor has received data
            boolean done;
            // Let the corresponding connection process the data
            done = peers.get(index).runOnce();

            if (done) {
                peers.remove(index);
                fds.remove(index);
            }
        }
    }
}

At this point, we have finished analyzing the general start-up process of Zygote.Let's move to the AMS section later on how Zygote creates the app process.

2. AMS

Activity Manager Service, short for AMS.Knowing by name, it is a system service used to manage activities.Just as the linux kernel uses task_struct to manage processes, AMS must use a data structure to record activity information on the current system in order to manage activities.

  • ActivityRecord records the runtime information for each Activity
final class ActivityRecord extends IApplicationToken.Stub {
    final ActivityManagerService service;
    // Information about Activity parsed from AndroidManifest.xml
    final ActivityInfo info;
    // Trigger the Intent that generated this ActivityRecord
    final Intent intent;
    // Task stack to which the current activity belongs
    TaskRecord task;
    // Information about the process to which the current Activity belongs
    ProcessRecord app;
    ...
}
  • ProcessRecord records the complete information of a process that is currently running
class ProcessRecord {
    // Process name and process id
    final String processName;
    int pid;
    // AMS makes requests to app processes through this Binder interface
    IApplicationThread thread;
    ...
}

After Launcher makes a startActivity request to AMS, AMS does a series of things. Here are only two main points we care about:
AMS creates an ActivityRecord object to record information about that Activity.
(2) If a process does not exist, that is, the corresponding ProcessRecord object does not exist, a ProcessRecord is created to record information about the process to be created, and a request is made to Zygote to create a new process.After the process has been successfully created, record the pid of the new process returned by Zygote in ProcessRecord.

// frameworks/base/services/java/com/android/server/am/ActivityStack.java
final int startActivityLocked(IApplicationThread caller,
            Intent intent, String resolvedType,
            Uri[] grantedUriPermissions,
            int grantedMode, ActivityInfo aInfo, IBinder resultTo,
            String resultWho, int requestCode,
            int callingPid, int callingUid, boolean onlyIfNeeded,
            boolean componentSpecified, ActivityRecord[] outActivity) {
    ...
        // Create information about Activity that will be started and save it in the r variable
        ActivityRecord r = new ActivityRecord(mService, this, callerApp, callingUid,
                intent, resolvedType, aInfo, mService.mConfiguration,
                resultRecord, resultWho, requestCode, componentSpecified);
    ...
}
// frameworks/base/services/java/com/android/server/am/ActivityStack.java
private final void startSpecificActivityLocked(ActivityRecord r,
            boolean andResume, boolean checkConfig) {
    // AMS maintains a ProcessMap <ProcessRecord> mProcessNames to record app processes that are started and run through AMS
    // Nothing found in this map indicates that the app process does not exist
    ProcessRecord app = mService.getProcessRecordLocked(r.processName,
                r.info.applicationInfo.uid);
    ...
    if (app != null && app.thread != null) {
        // If the application process exists, start the corresponding Activity
        try {
            app.addPackage(r.info.packageName);
            realStartActivityLocked(r, app, andResume, checkConfig);
            return;
        } catch (RemoteException e) {
            Slog.w(TAG, "Exception when starting activity "
                        + r.intent.getComponent().flattenToShortString(), e);
        }
    }

    // Application process does not exist, create process first
    mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0, "activity", r.intent.getComponent(), false);
}
// frameworks/base/services/java/com/android/server/am/ActivityManagerService.java
final ProcessRecord startProcessLocked(String processName, ApplicationInfo info, boolean knownToBeDead, int intentFlags,
            String hostingType, ComponentName hostingName, boolean allowWhileBooting) {
    ...
    if (app == null) {
        // Create a ProcessRecord to record information about the process you want to start
        app = newProcessRecordLocked(null, info, processName);
        // Save ProcessRecord in map
        mProcessNames.put(processName, info.uid, app);
    }
    ...
    startProcessLocked(app, hostingType, hostingNameStr);
    ...
}

private final void startProcessLocked(ProcessRecord app,
            String hostingType, String hostingNameStr) {
    // Use Process.start to start a new process
    Process.ProcessStartResult startResult = Process.start("android.app.ActivityThread",
                    app.processName, uid, uid, gids, debugFlags,
                    app.info.targetSdkVersion, null);
   ...
   app.pid = startResult.pid; // Log pid of new process
   ...
}
3. Request and response for Zygote socket connection

We didn't see anything about Zygote socket s just above.This block is actually implemented by the ZygoteProcess class.Process.start() passes through several calls to ZygoteProcess.startViaZygote()`.

Process.start()  
	--> ZygoteProcess.start() 
		--> ZygoteProcess.startViaZygote()
private Process.ProcessStartResult startViaZygote(...) {
    ... // Assembly process for omitting parameters
    synchronized(mLock) {
        return zygoteSendArgsAndGetResult(
                openZygoteSocketIfNeeded(abi), // Set up socket connection
                argsForZygote); // argsForZygote is the parameter to be sent to Zygote, ArrayList type
}
private static Process.ProcessStartResult zygoteSendArgsAndGetResult(
            ZygoteState zygoteState, ArrayList<String> args)
            throws ZygoteStartFailedEx {
    ...
    // Write Stream
    final BufferedWriter writer = zygoteState.writer;
    // Read Stream
    final DataInputStream inputStream = zygoteState.inputStream;
    // Write data according to Zygote protocol
    // Write number of parameters first, format: size\n
    writer.write(Integer.toString(args.size()));
    writer.newLine();
    // Write parameters, format of each parameter: arg\n
    for (int i = 0; i < sz; i++) {
          String arg = args.get(i);
          writer.write(arg);
          writer.newLine();
    }
    writer.flush();
    Process.ProcessStartResult result = new Process.ProcessStartResult();
    // Read the process id returned by Zygote
    result.pid = inputStream.readInt();
    if (result.pid < 0) { // If pid is less than 0, fork fails
       throw new ZygoteStartFailedEx("fork() failed");
    }
    return result;
}

We have seen the general process of ProcessProcess sending and receiving requests to Zygote.So how does Zygote receive and process requests there?

// frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java
boolean runOnce() throws ZygoteInit.MethodAndArgsCaller {
    try {
        // Read data sent by client via socket
        args = readArgumentList();
        descriptors = mSocket.getAncillaryFileDescriptors();
    } catch (IOException ex) {
        Log.w(TAG, "IOException on command socket " + ex.getMessage());
        closeSocket();
        return true;
    }
    ...
    // Parse the acquired data into Arguments types
    parsedArgs = new Arguments(args);
    ...
    // Create APP Process
    pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid,
                    parsedArgs.gids, parsedArgs.debugFlags, rlimits);
    if (pid == 0) {
        ...
        // Processing logic of a walking child process
        handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
        ...
    } else {
        ...
        // Processing logic of the parent-following process
        return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
    }
}

Now that the process is created, the pid of the child process needs to be passed back to AMS via the socket.

// frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java
private boolean handleParentProc(int pid,
            FileDescriptor[] descriptors, FileDescriptor pipeFd, Arguments parsedArgs) {
    ...
   try {
        // Pass the process pid from fork to AMS
        mSocketOutStream.writeInt(pid);
        mSocketOutStream.writeBoolean(usingWrapper);
    } catch (IOException ex) {
        Log.e(TAG, "Error reading from command socket", ex);
        return true;
    }
    ...
}
4. Execution of main method of ActivityThread

At the end of the above process, the subprocess has been successfully created, and the subprocess will initialize itself, load the main class, and execute the main(String[]) method.

ZygoteConnection.handleChildProc() 
    --> RuntimeInit.zygoteInit()
        --> RuntimeInit.zygoteInitNative() 
        --> RuntimeInit.applicationInit()
            --> RuntimeInit.invokeStaticMain()
private static void invokeStaticMain(String className, String[] argv)
            throws ZygoteInit.MethodAndArgsCaller {
    Class<?> cl;
    // Load the main class (android.app.ActivityThread)
    cl = Class.forName(className); 
    Method m;
    // Get main(String[]) method
    m = cl.getMethod("main", new Class[] { String[].class });
    // Check if the main method is public static's
    int modifiers = m.getModifiers();
    if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
        throw new RuntimeException(
                    "Main method is not public and static on " + className);
    }
    // Throw an exception and return to the catch block of ZygoteInit.main() to continue execution
    throw new ZygoteInit.MethodAndArgsCaller(m, argv);
}

The final part of the code encapsulates the reflected Method object and parameters as a ZygoteInit.MethodAndArgsCaller exception, which is caught by the main() method of ZygoteInit.

// ZygoteInit.main()
catch (MethodAndArgsCaller caller) {
    // The subprocess fork() comes out and ends up here
    // In this case, call the main() method of the corresponding class through reflection
    caller.run();
}

// MethodAndArgsCaller.run()
public void run() {
    try {
        // The main() method is ultimately invoked through reflection
        mMethod.invoke(null, new Object[] { mArgs });
    } catch (IllegalAccessException ex) {
        throw new RuntimeException(ex);
    } catch (InvocationTargetException ex) {
        throw new RuntimeException(ex);
    }
}

Finally, the main() method of the ActivityThread class of the app process begins execution.

Posted by drath on Fri, 06 Mar 2020 09:07:22 -0800