Figure out how to start the Android application process

Outline:

  • A brief review of
  • AMS sends socket requests
  • Zygote processes socket requests
  • Start a binder thread pool
  • conclusion
  • Details added
  • The resources

This article is about 2.5K words and takes about 11 minutes to read.

Android source code based on 8.0.

A brief review of

Let’s review the Android startup process:

After the init process forks the Zygote process, the Zygote process creates a server socket and waits for AMS to initiate a socket request.

Meanwhile, Zygote forks the SystemServer process to start various system services, including AMS, which starts the desktop Launcher and waits for the user to click the App icon to start the application process.

Then look at the start of system services, whether the init process started by the independent process system services such as SurfaceFlinger, or started by the SystemServer process independent process system services such as AMS, are registered and obtained in the ServiceManager process. Android’s Binder mechanism is used for cross-process communication.

The ServiceManager process itself is a system service that handles the acquisition and registration requirements of other system services by starting the process, starting the Binder mechanism, publishing itself, and waiting for requests.

AMS sends socket requests

The Startup of an Android application process is passive. When you click the icon in the desktop Launcher to start an application component such as an Activity, if the Activity’s process does not exist, the process is created and started.

Click on the App icon after call will come ActivityStackSupervisor startSpecificActivityLocked method through layer upon layer,

//ActivityStackSupervisor.java
final ActivityManagerService mService;

void startSpecificActivityLocked(...). {
    ProcessRecord is a data structure that encapsulates process informationProcessRecord app = mService.getProcessRecordLocked(...) ;// If the process is started and the binder handle IApplicationThread is available, start the Activity directly
    if(app ! =null&& app.thread ! =null) {
        realStartActivityLocked(r, app, andResume, checkConfig);
        return;
    }
	// Otherwise, let AMS start the processmService.startProcessLocked(...) ; }Copy the code

App. Thread is not a thread, but a binder handle. Application processes need to obtain the AMS handle IActivityManager to use AMS, and the system needs to notify the application and manage the application life cycle, so it also needs to hold the binder handle IApplicationThread of application processes.

That is, they hold each other’s binder handles for two-way communication.

How is the IApplicationThread handle passed to AMS?

After receiving the socket request, Zygote processes the request parameters and executes the entry function main for ActivityThread.

//ActivityThread.java
public static void main(String[] args) {
    // Create looper for the main thread
    Looper.prepareMainLooper();
    // ActivityThreads are not threads, but plain Java objects
    ActivityThread thread = new ActivityThread();
    // Tell AMS that the application is started
    thread.attach(false);
	// Run looper to start the message loop
    Looper.loop();
}

private void attach(boolean system) {
    // Get the BINDER handle IActivityManager for AMS
    final IActivityManager mgr = ActivityManager.getService();
    // Tell AMS that the application is started and pass in its own binder handle, IApplicationThread
    mgr.attachApplication(mAppThread);
}
Copy the code

So for AMS,

  1. AMS sends a socket request to Zygote to start the application. Zygote forks out the process after receiving the request and returns the PID of the process to AMS.
  2. After the application process is started, run the main function, use attachApplication to tell AMS that the application process is started, and pass in the binder handle IApplicationThread.

When these two steps are complete, the application startup process is complete.

Here’s what AMS’s startProcessLocked does when it starts the application process.

//ActivityManagerService.java
final ProcessRecord startProcessLocked(...).{
    ProcessRecord app = getProcessRecordLocked(processName, info.uid, keepIfLarge);
    // If the process information is not empty and you have got the application process PID returned by Zygote
    // The AMS request has been made, and Zygote has responded to the request and forked out
    if(app ! =null && app.pid > 0) {
        // But app.thread is still empty, indicating that the application has not yet registered its binder handle to AMS
        // If the process is being started, return directly to avoid repeated creation
        if (app.thread == null) {
            returnapp; }}// Call overloaded methodsstartProcessLocked(...) ; }Copy the code

The purpose of identifying app.thread is to prevent the application process from being repeatedly pulled (created) if another component needs to be started while the application process is starting.

Moving on to the overloaded method startProcessLocked,

//ActivityManagerService.java
private final void startProcessLocked(...).{
    // The main thread class name of the application process
    if (entryPoint == null) entryPoint = "android.app.ActivityThread"; ProcessStartResult startResult = Process.start(entryPoint, ...) ; }//Process.java
public static final ProcessStartResult start(...).{
    returnzygoteProcess.start(...) ; }Copy the code

Come to ZygoteProcess,

//ZygoteProcess.java
public final Process.ProcessStartResult start(...).{
    returnstartViaZygote(...) ; }private Process.ProcessStartResult startViaZygote(...).{
    ArrayList<String> argsForZygote = new ArrayList<String>();
    / /... Handling various parameters
    return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
}
Copy the code

Among them:

  1. OpenZygoteSocketIfNeeded Opens the local socket
  2. ZygoteSendArgsAndGetResult sends the request parameters, which took ActivityThread class names
  3. The data structure returned by return ProcessStartResult will have the PID field

To sort it out:

Note: The Zygote process starts with the vm instance already created, so the application process forked by Zygote can be inherited without creating it.

Here’s how Zygote handles socket requests.

Zygote processes socket requests

In ZygoteInit’s main function, the server socket is created.

//ZygoteInit.java
public static void main(String argv[]) {
    // The Server class encapsulates the socket
    ZygoteServer zygoteServer = new ZygoteServer();
    // Create a server socket with the name socketName (zygote)
    zygoteServer.registerServerSocket(socketName);
    // Enter an infinite loop and wait for AMS to send a request
    zygoteServer.runSelectLoop(abiList);
}
Copy the code

See ZygoteServer,

//ZygoteServer.java
void registerServerSocket(String socketName) {
    int fileDesc;
    // The real socket name is prefixed with "ANDROID_SOCKET_" + "zygote"
    final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;

    String env = System.getenv(fullSocketName);
    fileDesc = Integer.parseInt(env);

    // create file descriptor fd
    FileDescriptor fd = new FileDescriptor();
    fd.setInt$(fileDesc);
    // Create LocalServerSocket object
    mServerSocket = new LocalServerSocket(fd);
}

void runSelectLoop(String abiList){
    // Enter an infinite loop
    while (true) {
        for (int i = pollFds.length - 1; i >= 0; --i) {
            if (i == 0) {
                / /...
            } else {
                // Get a connection object ZygoteConnection and call its runOnce
                boolean done = peers.get(i).runOnce(this); }}}}Copy the code

To the runOnce of ZygoteConnection,

//ZygoteConnection.java
boolean runOnce(ZygoteServer zygoteServer){
    // Read the parameter list of the socket request
    String args[] = readArgumentList();
    // Create an application process
    intpid = Zygote.forkAndSpecialize(...) ;if (pid == 0) {
        // If it is an application process (a child of Zygote fork), process the request parameters
        handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
        return true;
    } else {
        returnhandleParentProc(pid, descriptors, serverPipeFd, parsedArgs); }}Copy the code

The handleChildProc method calls the ZygoteInit method of ZygoteInit and does three things:

  1. Starting binder thread pools (analyzed later)
  2. Retrieve the ActivityThread class and execute its main function. Execute Thread. attach to inform AMS and return its binder handle
  3. Execute looper.loop () to start the message loop(earlier in the code)

Then the application process starts. Just to sort it out,

Let’s see how binder thread pools are started.

Start a binder thread pool

Zygote uses sockets instead of binders for cross-process communication, so the binder mechanism for application processes is not inherited but started by the process itself after it is created.

When a Zygote receives a socket request, it gets a ZygoteConnection, and its runOnce calls handleChildProc.

//ZygoteConnection.java
private void handleChildProc(...).{ ZygoteInit.zygoteInit(...) ; }//ZygoteInit.java
public static final void zygoteInit(...).{
    RuntimeInit.commonInit();
    // Enter the native layer
    ZygoteInit.nativeZygoteInit();
    RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
}
Copy the code

Come to AndroidRuntime CPP,

//AndroidRuntime.cpp
static void com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env, jobject clazz){
    gCurRuntime->onZygoteInit();
}
Copy the code

Come to app_main CPP,

//app_main.cpp
virtual void onZygoteInit(a)
{
    // Get the singleton
    sp<ProcessState> proc = ProcessState::self();
    // The binder thread pool is started here
    proc->startThreadPool();
}
Copy the code

Look at the ProcessState. CPP,

//ProcessState.cpp
sp<ProcessState> ProcessState::self(a)
{
    // Singleton mode, return ProcessState object
    if(gProcess ! =NULL) {
        return gProcess;
    }
    gProcess = new ProcessState("/dev/binder");
    return gProcess;
}

//ProcessState constructor
ProcessState::ProcessState(const char *driver)
    : mDriverName(String8(driver))
        , mDriverFD(open_driver(driver)) // Open the binder driver
        ,/ /...
{
    if (mDriverFD >= 0) {
        // Mmap is a memory-mapped file that maps mDriverFD to the current memory space
        mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, 
                        MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0); }}// The binder thread pool is started
void ProcessState::startThreadPool(a)
{
    if(! mThreadPoolStarted) { mThreadPoolStarted =true;
        spawnPooledThread(true); }}void ProcessState::spawnPooledThread(bool isMain)
{
    if (mThreadPoolStarted) {
        Binder:${pid}_${increment number}"
        String8 name = makeBinderThreadName();
        sp<Thread> t = new PoolThread(isMain);
        // Run binder threads
        t->run(name.string()); }}Copy the code

There are two macro definitions for ProcessState that are worth noting. If you are interested in the article about the maximum amount of data a Binder communication can transmit,

//ProcessState.cpp
// The maximum size that a Binder communication can transmit is 1MB-4KB*2
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
// Binder driver file descriptor fd is limited to a maximum of 15 threads
#define DEFAULT_MAX_BINDER_THREADS 15
Copy the code

Binder thread PoolThread

class PoolThread : public Thread
{
public:
    explicit PoolThread(bool isMain)
        : mIsMain(isMain){}
protected:
    virtual bool threadLoop(a)
    {	// Register binder threads into the thread pool of binder drivers
        IPCThreadState::self()->joinThreadPool(mIsMain);
        return false;
    }
    
    const bool mIsMain;
};
Copy the code

Come to IPCThreadState CPP,

//IPCThreadState.cpp
void IPCThreadState::joinThreadPool(bool isMain)
{
    // Drive write data to binder: an infinite loop
    mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
    status_t result;
    do {
        // Enter an infinite loop, waiting for instructions to arrive
        result = getAndExecuteCommand();
    } while(result ! = -ECONNREFUSED && result ! = -EBADF);// Drive data to binder to exit the loop
    mOut.writeInt32(BC_EXIT_LOOPER);
}

status_t IPCThreadState::getAndExecuteCommand(a)
{
    // Read data from binder driver to get instructions
    cmd = mIn.readInt32();
    // Execute the command
    result = executeCommand(cmd);
    return result;
}
Copy the code

Binder startup process:

  1. Open binder drive
  2. Map memory and allocate buffers
  3. Run the binder thread into an infinite loop, waiting for instructions

conclusion

To sum up, the startup of an Android application process can be summarized as follows:

  1. Click the App icon on the desktop Launcher
  2. AMS initiates a socket request
  3. The Zygote process receives the request and processes the parameters
  4. The Zygote process forks an application process. The application process inherits the VM instance
  5. The application starts the Binder thread pool, runs the Main function of the ActivityThread class, and starts the Looper loop

Complete flow chart:

Binder is widely used as a binder. Binder is widely used as a binder

Series of articles:

  • Graphic | Android launch
  • Graphic | a figure out the Android system services

Details added

  • Throw exception emptying stack frame: Zygote does not execute the ActivityThread’s main function directly. Instead, Zygote catches and executes the ActivityThread by throwing an exception that clears the call stack generated by the initialization process and makes the ActivityThread’s main function look like an entry function for an application process.

The resources

  • Books – Android advanced decryption
  • Blog – How much data can be transmitted with a Binder communication

More sexy articles, pay attention to the original technology public account: Halliday EI