preface

As an application engineer, in addition to writing business code, performance optimization is also something we need to focus on!

If you want to do startup optimization, understanding the startup process can be a tricky task.

So what else should we know besides the code about the startup process?

One, process those things

From the early days of learning Android, you probably know that each Android App represents a process.

1. Why do I need to start the process?

Why start a new process?

In Linux, threads and processes are not that different. The kernel does not have special scheduling algorithms or special data structures for threads. Instead, threads are treated as a process that shares certain resources with other processes.

See? Threads can share resources, such as address space, so you don’t want to send wechat and other applications to know what you sent.

2. How do I start an application process

Starting an application process is not easy. Linux processes are fork(), and Android processes have Zygote as their parent. Zygote is one of Android’s two main processes.

When you click on the App icon, the process Launcher notifies AMS(ActivityManagerService). AMS calls its startProcessLocked method, and then Process#start(). We can see where the Socket is connected:

public static ZygoteState connect(LocalSocketAddress address) throws IOException {
    / /...
    final LocalSocket zygoteSocket = new LocalSocket();
    try {
        zygoteSocket.connect(address);
        zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());
        zygoteWriter = new BufferedWriter(new OutputStreamWriter(zygoteSocket.getOutputStream()), 256);
    } catch (IOException ex) {
        / /...
    }
    / /...
    return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter, Arrays.asList(abiListString.split(",")));
}
Copy the code

The Zygote process opens the Socket and waits for a connection, which eventually triggers its Zygote# forkandWte () method:

static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
                             int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
                             int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
                             boolean isTopApp, String[] pkgDataInfoList, String[] whitelistedDataInfoList,
                             boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) {
    ZygoteHooks.preFork();
    int pid = nativeForkAndSpecialize(
            uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
            fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp,
            pkgDataInfoList, whitelistedDataInfoList, bindMountAppDataDirs,
            bindMountAppStorageDirs);
    / /...
    ZygoteHooks.postForkCommon();
    return pid;
}
Copy the code

As you can see, a new process is created and the process ID is returned.

Binder thread pools must be created to communicate with other services or processes such as AMS and PMS.

Next, to the highlight, the system calls the RuntimeInit#findStaticMain() method, which eventually calls a Runnable method:

static class MethodAndArgsCaller implements Runnable {
    / /...
    public void run(a) {
        try {
            mMethod.invoke(null.new Object[] { mArgs });
        } catch (IllegalAccessException ex) {
            // ...
        } catch (InvocationTargetException ex) {
            // ...}}}Copy the code

MMethod is the ActivityThread#main() method, the parameters passed in earlier, and the final reflection to activate our ActivityThread.

3. Android’s unique multi-process transmission mode

You thought you were parsing ActivityThread#main(), no, we didn’t.

We talked about sockets above, but Binder is the unique multi-process communication method in Android, and I’ll mention it many times below.

To put it simply, Android processes have independent memory addresses, but they all have to communicate with the hardware layer through the kernel’s limitations.

If there is no kernel, a lot of bad things can happen. For example, if I have 8GB of ram, I can’t play with the rest of the application.

Multi-process communication also has to be through the kernel, and the process is generally assigned virtual addresses, each virtual address has a physical address corresponding to it. When process communication occurs, data is typically copied from user space of process A (the memory space that the user can manipulate) to the buffer of the kernel, and from the buffer of the kernel to user space of process B.

Binder, on the other hand, uses Mmap to map virtual addresses in the kernel to virtual addresses in user space. With Binder, when process B copies data from process A to the memory cache, the whole communication process ends. The data is mapped directly to the memory space of process B.

Binder: Binder Binder Binder Binder Binder Binder Binder Binder Binder Binder Binder Binder Binder Binder Binder Binder Binder Binder Binder

Binder implementation is a typical C\S structure:

  • Client: The requester of the service.
  • Server: Service provider.
  • Service Manager: in order toServerprovideBinderRegistration services forClientprovideBinderThe enquiry service ofServer,ClientandService ManagerAll communications are through Binder.
  • Binder driveResponsible for the establishment of communication mechanism with Binder, providing a range of low-level support.

From the figure above, Binder communication is as follows:

  1. ServerService ManagerThe registration:ServerBinder entities are also created when processes are created to provide services toClient, must beBinderThe entity registers a name.
  2. ClientthroughService ManagerAccess to services:ClientKnow that in serviceBinderAfter the entity name, pass the name fromService ManagerTo obtainBinderReferences to entities.
  3. ClientUse of servicesServerTo communicate:ClientBy calling theBinderEntities andServerTo communicate.

The above knowledge may be a little superficial, just throw a brick to introduce jade, if you want to learn in depth, we can understand.

Binder is the most efficient cross-process communication method in Android. Zygote chose Socket instead of Binder as the cross-process communication method.

In fact, I am also a little confused, you can take a look at the following answer:

Zygote process with Binder mechanism

The dex thing

We know that when you unzip an application’s APK file, there may be several dex files in it.

It is created by converting all the Java bytecode files (.class) into tools.

How can these dex files be run directly?

We can also see from the picture, which needs to be discussed according to the situation, because Android virtual machine is divided into Dalvik and ART.

In the case of Dalvik, the installation process extracts.odex, but this is just an optimized bytecode file. Finally, during the run process, Dalvik needs to be Jit(just-in-time compilation) and converted into machine code that the machine can recognize.

In the case of ART, it will be parsed into an.oat file during installation, which will contain actual machine code instead of bytecode, and will run and start up much faster without Jit.

However, in ART, the entire installation process and application upgrade process can be time-consuming. Therefore, after Android 7.0, both JIT and AOT are adopted.

  • The first startup uses JIT to parse the dex contained in the hotspot function into bytecode.
  • When the application is idle, the AOT process is executed to compile.

After this long string, the dex file becomes machine-readable machine code. Again, the Android system doesn’t recognize.class files directly, it needs to map.dex into memory. And load classes in APP into memory through PathClassLoader and DexClassLoader.

Enter the Android startup process

As we already know from above, our app startup entry is in the ActivityThread#main() method.

1. Enable the communication mechanism during the process

Before we look at the startup process, I think we need to look at the communication mechanism of the startup process, which is also the main line of the startup process, although I know you can’t wait!

Those of you who are familiar with the startup process know the roles of ActivityThread, ApplicationThread, and ActivityManagerService:

role role
ActivityThread (hereinafter referred to asAT) : Application startup entry for application startup.
ActivityManagerService (hereinafter referred to asAMS) : One of the most important system services in the Android system, responsible for the startup, management and scheduling of four components, as well as the management of application processes.
ApplicationThread AMSATA bridge of communication,ATInner class of.

The Zygote process is one of the two pillars of the Android system. The other is the SystemServer process.

AMS is implemented with Binder, so ActivityThread can communicate with AMS in application processes that are located in SystemServer processes.

So how does AMS contact ActivityThread?

Binder, as it happens, implements ApplicationThread, but it is an anonymous Binder (Binder not registered with Server Manager), and I think it is designed for more security reasons.

The communication between AT and AMS is as described in the figure.

2. The entrance ActivityThread

It’s time to explain the code!

Enter the main method:

public static void main(String[] args) {
    / /...
    Looper.prepareMainLooper();
    / /... omit
    
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);
    
    / /...
    Looper.loop();
}
Copy the code

The key is the ActivityThread#attach() method, where the system variable passes false:

private void attach(boolean system, long startSeq) {
    // ...
    if(! system) {//. ...
        RuntimeInit.setApplicationObject(mAppThread.asBinder());
        final IActivityManager mgr = ActivityManager.getService();
        try {
            mgr.attachApplication(mAppThread, startSeq);
        } catch (RemoteException ex) {
            // ...
        }
        // Watch for getting close to heap limit.
        BinderInternal.addGcWatcher(new Runnable() {
            @Override public void run(a) {
                / /...}}); }else {
        / /...
    }

    / /...
}
Copy the code

We can see that this method gets AMS directly and then passes the ApplicationThread object directly.

2. Enter the AMS

Click on the ActivityManagerService#attachApplication() method, which in turn calls the ActivityManagerService#attachApplicationLocked() method to simplify things:

private final boolean attachApplicationLocked(IApplicationThread thread, int pid) { ProcessRecord app; if (pid ! = MY_PID && pid >= 0) { synchronized (mPidsSelfLocked) { app = mPidsSelfLocked.get(pid); ProcessRecord}}... ApplicationInfo appInfo = app.instrumentationInfo ! = null ? app.instrumentationInfo : app.info; thread.bindApplication(processName, appInfo, providers, app.instrumentationClass, profilerInfo, app.instrumentationArguments, app.instrumentationWatcher, app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace, isRestrictedBackupMode || ! normalMode, app.persistent, new Configuration(mConfiguration), app.compat, getCommonServicesLocked(app.isolated), mCoreSettingsObserver.getCoreSettingsLocked()); . return true; }Copy the code

Note that in AMS, the above method directly calls ApplicationThread#bindApplication(), which is cross-process.

As mentioned earlier, ApplicationThread is also a Binder, and we did not find any code for ApplicationThread to register Binder with Service Manager, which further confirms that it is an anonymous Binder. If you don’t believe me, take a look at the code for ApplicationThread:

private class ApplicationThread extends IApplicationThread.Stub {
    / /...
}

public class ActivityManagerService extends IActivityManager.Stub
        implements Watchdog.Monitor.BatteryStatsImpl.BatteryCallback {
    // ...    
}
Copy the code

Binder and ApplicationThread/ActivityManagerService are both implemented with AIDL.

3. Enter the ApplicationThread

ApplicationThread is not handled by itself. Instead, it is handed to an instance of H. H is a Handler:

public final void bindApplication(/ /... Parameter omitted) {
    / /...
    AppBindData data = new AppBindData();
    / /...
    sendMessage(H.BIND_APPLICATION, data);
}
Copy the code

H is also an inner class for ActivityThread, so call ActivityThread#handleBindApplication() directly to simplify things:

private void handleBindApplication(AppBindData data) {
    mBoundApplication = data;
    Process.setArgV0(data.processName);// Set the process name
    // ...
    // Get LoadedApk object
    data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
    // ...
    // Create ContextImpl context
    final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
    / /...
    try {
        // Where data.info refers to LoadedApk, the target Application object is created by reflection
        Application app = data.info.makeApplication(data.restrictedBackupMode, null);
        // Initialize the ContentProvider
        installContentProviders(app, data.providers);
        mInitialApplication = app;
        // ...
        mInstrumentation.onCreate(data.instrumentationArgs);
        / / callback onCreate
        mInstrumentation.callApplicationOnCreate(app);
    } finally{ StrictMode.setThreadPolicy(savedPolicy); }}Copy the code

We can see that the following things have been done:

  1. createContextImpl
  2. createApplication
  3. Initialize theContentProvider
  4. The callbackApplication#onCreate()

4. Create Application

Data.info is of type LoadedApk, which holds a lot of apK-related information.

Enter the LoadedApk#makeApplication() method, which is the Application creation method, and notice that the second argument is passed null:

public Application makeApplication(boolean forceDefaultAppClass,
                                   Instrumentation instrumentation) {
    Application app = null;
    / /...
    try {
        final java.lang.ClassLoader cl = getClassLoader();
        // ...
        ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
        // ...
        app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
    } catch (Exception e) {
        / /...
    }
    if(instrumentation ! =null) {
        // instrumentation is empty, so can't enter this method
        try {
            instrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {
            / /...}}return app;
}
Copy the code

Develop a practical Application methods and to mActivityThread mInstrumentation, its type Instrumentation, this class is a big housekeeper, whether the Application or Activity, In the end, it will handle:

public Application newApplication(ClassLoader cl, String className, Context context) {
    // Application created with reflection
    Application app = getFactory(context.getPackageName())
            .instantiateApplication(cl, className);
    app.attach(context);
    return app;
}
Copy the code

Once the Application is created, the Application# Attach () method is called. In this method, we see the familiar method:

final void attach(Context context) {
    attachBaseContext(context);
    // ...
}
Copy the code

The common method Application#attachBaseContext() is called back at this point.

5. Initialize the ContentProvider

There’s nothing to say here, except that the ContentProvider is initialized earlier than the Application#onCreate() method.

Because I’ve called an SDK in ContentProvider#onCreate() and the SDK just initialized in Application#onCreate() and the result flashed back.

6. CallApplication#oncreate()

Going back to the method in Step 3, the mInstrumentation is of type Instrumentation, which is also created by reflection, and ultimately the Instrumentation#callApplicationOnCreate method is executed:

public void callApplicationOnCreate(Application app) {
    app.onCreate();
}
Copy the code

In this method, we see our Application#onCreate() method execute, which is where we normally initialize the SDK.

After this is done, the first Activity will be started through AMS, or the same way of communication, and will not continue with the others.

conclusion

See here, I believe that everyone has a preliminary understanding of the application, if there is any dispute, see the comment area!

Talk about some knowledge points, you may think that learning a knowledge point is more boring.

Learning about the Linux kernel, learning about the composition of Apk, and looking at the startup process of an application can be fun when you connect the dots.

Refer to the article

Dalvik,ART and ODEX Love and Grow Together AOT, Android AMS and APP Process Communication, Understanding Application Creation Process, In-depth Understanding of Android VIRTUAL Machine, Linux Kernel Design and Implementation