Android APP performance optimization startup process analysis

1. Background

The duration of APP startup will greatly affect the user experience, and it will have a lot of impact on product evaluation and user mood. The startup optimization detection of Youlu Education APP is carried out. Through relevant inspections, the logic that takes a long time to start is found out and optimized, in order to further reduce the impact of startup time on users.

2. Goal

Analyze the startup process of the application, find out where it can be optimized, and optimize it in combination with the current project.

3. Process

Before talking about the startup process of the application, you must first understand the startup process of the Android system, because the startup of the Application is inseparable from the processing of the system.

1. Analysis of the startup process of the Android system

  1. BootLoader starts the kernel and init process;
  2. The init process splits out a daemon process, such as Android Debug Damon, USB Damon, these daemon processes will handle some hardware-related interfaces;
  3. The init process starts a Zygote process. The Zygote process initializes the first VM and preloads the Framework and some common resources.
    The zygote process will open a Socket interface to monitor requests. Once a request is received, Zygote will incubate a new VM based on its own preloaded VM and create a new process.
  4. After starting Zygote, the init process will start the Runtime process. Zygote will hatch a super management process - System Server. System Server will start all system core services, such as Activity Manager Service and hardware-related services.
  5. At this time, it is ready to start its first App process-Home process.

The Android system has been started, and some core services have also been started, and then the Launcher application is started.

2. Analysis of App startup process

When is the App process created?
When an application calls a page in another application, if the target process does not exist, a new process will be created and started.
Application startup process
first look at the flow chart
insert image description here

  1. Click the icon on the desktop or click to jump to another application in the foreground application
  2. Then call the StartActivity(Intent intent) method;
    this method will eventually call ActivityManagerService through Binder IPC, here referred to as AMS.
  3. AMS does the following:
    1. Finally, the pointing information of this Intent object will be collected through the resolveIntent() method of PackageManager (there will be many classes and method calls in the middle).
    2. Use the grantUriPermissionLocked() method to verify whether the user has sufficient permissions to call the target Activity;
    3. Query whether ProcessRecord exists.
      If not, AMS will create a new process to instantiate the target Activity.

Next, let's talk about the creation process of the App process.

App process creation

  1. Call the startProcessLocked() method to create a new process and pass parameters to the Zygote process through the Socket channel mentioned above. The Zygote process incubates itself, and calls the ZygoteInit.main() method to instantiate the ActivityThread object, and finally returns the pid of the new process.
  2. ActivityThread calls the Looper.prepare() and Looper.loop() methods in turn to open the message loop.

At this time, the process has been created, but how to link it with the Application itself?

Application binding

  1. Call the bindApplication() method in ActivityThread to send a BIND_APPLICATION message to the message queue.
  2. Process the previous binding message through the handleBindApplication() method;
  3. Call the makeApplication() method to load the Application class into memory.

The following is a partial source code snippet
ActivityThread.java
sendMessage(H.BIND_APPLICATION, data);

@Override
public final void bindApplication(String processName, ApplicationInfo appInfo,
                                  ProviderInfoList providerList, ComponentName instrumentationName,
                                  ProfilerInfo profilerInfo, Bundle instrumentationArgs,
                                  IInstrumentationWatcher instrumentationWatcher,
                                  IUiAutomationConnection instrumentationUiConnection, int debugMode,
                                  boolean enableBinderTracking, boolean trackAllocation,
                                  boolean isRestrictedBackupMode, boolean persistent, Configuration config,
                                  CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
                                  String buildSerial, AutofillOptions autofillOptions,
                                  ContentCaptureOptions contentCaptureOptions, long[] disabledCompatChanges,
                                  SharedMemory serializedSystemFontMap) {
    
    
    if (services != null) {
    
    
        if (false) {
    
    
            // Test code to make sure the app could see the passed-in services.
            for (Object oname : services.keySet()) {
    
    
                if (services.get(oname) == null) {
    
    
                    continue; // AM just passed in a null service.
                }
                String name = (String) oname;

                // See b/79378449 about the following exemption.
                switch (name) {
    
    
                    case "package":
                    case Context.WINDOW_SERVICE:
                        continue;
                }

                if (ServiceManager.getService(name) == null) {
    
    
                    Log.wtf(TAG, "Service " + name + " should be accessible by this app");
                }
            }
        }

        // Setup the service cache in the ServiceManager
        ServiceManager.initServiceCache(services);
    }

    setCoreSettings(coreSettings);

    AppBindData data = new AppBindData();
    data.processName = processName;
    data.appInfo = appInfo;
    data.providers = providerList.getList();
    data.instrumentationName = instrumentationName;
    data.instrumentationArgs = instrumentationArgs;
    data.instrumentationWatcher = instrumentationWatcher;
    data.instrumentationUiAutomationConnection = instrumentationUiConnection;
    data.debugMode = debugMode;
    data.enableBinderTracking = enableBinderTracking;
    data.trackAllocation = trackAllocation;
    data.restrictedBackupMode = isRestrictedBackupMode;
    data.persistent = persistent;
    data.config = config;
    data.compatInfo = compatInfo;
    data.initProfilerInfo = profilerInfo;
    data.buildSerial = buildSerial;
    data.autofillOptions = autofillOptions;
    data.contentCaptureOptions = contentCaptureOptions;
    data.disabledCompatChanges = disabledCompatChanges;
    data.mSerializedSystemFontMap = serializedSystemFontMap;
    sendMessage(H.BIND_APPLICATION, data);
}

handleMessage(msg)

public void handleMessage(Message msg) {
    
    
    if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
    switch (msg.what) {
    
    
        case BIND_APPLICATION:
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
            AppBindData data = (AppBindData)msg.obj;
            handleBindApplication(data);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            break;
        ...
    }
    ...
}

handleBindApplication(data)

    @UnsupportedAppUsage
    private void handleBindApplication(AppBindData data) {
    
    
        ...
        try {
    
    
            // If the app is being launched for full backup or restore, bring it up in
            // a restricted environment with the base application class.
            app = data.info.makeApplication(data.restrictedBackupMode, null);

            // Propagate autofill compat state
            app.setAutofillOptions(data.autofillOptions);

            // Propagate Content Capture options
            app.setContentCaptureOptions(data.contentCaptureOptions);
            sendMessage(H.SET_CONTENT_CAPTURE_OPTIONS_CALLBACK, data.appInfo.packageName);

            mInitialApplication = app;
            final boolean updateHttpProxy;
            synchronized (this) {
    
    
                updateHttpProxy = mUpdateHttpProxyOnBind;
                // This synchronized block ensures that any subsequent call to updateHttpProxy()
                // will see a non-null mInitialApplication.
            }
            if (updateHttpProxy) {
    
    
                ActivityThread.updateHttpProxy(app);
            }

            // don't bring up providers in restricted mode; they may depend on the
            // app's custom Application class
            if (!data.restrictedBackupMode) {
    
    
                if (!ArrayUtils.isEmpty(data.providers)) {
    
    
                    installContentProviders(app, data.providers);
                }
            }
            ...
        }
        
        ...
    }

Note that data.info.makeApplication(data.restrictedBackupMode, null) and installContentProviders(app, data.providers)
data.info type is LoadedApk
LoadedApk.java

@UnsupportedAppUsage
public Application makeApplication(boolean forceDefaultAppClass,
                                   Instrumentation instrumentation) {
    
    
    if (mApplication != null) {
    
    
        return mApplication;
    }

    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");

    Application app = null;

    String appClass = mApplicationInfo.className;
    if (forceDefaultAppClass || (appClass == null)) {
    
    
        appClass = "android.app.Application";
    }

    try {
    
    
        final java.lang.ClassLoader cl = getClassLoader();
        if (!mPackageName.equals("android")) {
    
    
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                             "initializeJavaContextClassLoader");
            initializeJavaContextClassLoader();
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        }

        // Rewrite the R 'constants' for all library apks.
        SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers(
            false, false);
        for (int i = 0, n = packageIdentifiers.size(); i < n; i++) {
    
    
            final int id = packageIdentifiers.keyAt(i);
            if (id == 0x01 || id == 0x7f) {
    
    
                continue;
            }

            rewriteRValues(cl, packageIdentifiers.valueAt(i), id);
        }

        ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
        // The network security config needs to be aware of multiple
        // applications in the same process to handle discrepancies
        NetworkSecurityConfigProvider.handleNewApplication(appContext);
        app = mActivityThread.mInstrumentation.newApplication(
            cl, appClass, appContext);
        appContext.setOuterContext(app);
    } catch (Exception e) {
    
    
        if (!mActivityThread.mInstrumentation.onException(app, e)) {
    
    
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            throw new RuntimeException(
                "Unable to instantiate application " + appClass
                + " package " + mPackageName + ": " + e.toString(), e);
        }
    }
    mActivityThread.mAllApplications.add(app);
    mApplication = app;

    if (instrumentation != null) {
    
    
        try {
    
    
            instrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {
    
    
            if (!instrumentation.onException(app, e)) {
    
    
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                throw new RuntimeException(
                    "Unable to create application " + app.getClass().getName()
                    + ": " + e.toString(), e);
            }
        }
    }

    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

    return app;
}

Among them, mApplicationInfo.className is the Application customized by App. If it is empty, the default appClass = "android.app.Application" will be used. initializeJavaContextClassLoader() is the logic call for initializing the class loader, and you can continue to track its logic. It can be found that after the sharedUserId is set to the same, the logical app of a virtual machine can be shared
= mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext) At this time, you can see that it is the instantiation of Application in Instrumentation

Look at some excerpted code
ActivityThread.java

mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);

...
// Restore instance state
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state, r.persistentState);

...
// Call postOnCreate()
mInstrumentation.callActivityOnPostCreate(activity, r.state, r.persistentState);

...
mInstrumentation.callActivityOnNewIntent(r.activity, intent);

...
 mInstrumentation.callActivityOnPause(r.activity);

...
mInstrumentation.callActivityOnDestroy(r.activity);

...
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);

...
mInstrumentation.callApplicationOnCreate(app);


Among them: callActivityOnCreate, callApplicationOnCreate, newActivity, callActivityOnNewIntent, etc. In all life cycle calls of application and activity through Instrumentation in the ActivityThread class, the corresponding methods of Instrumentation will be called first to realize the corresponding functions. There are more correct related functions in the Instrumentation class. method.

Continue into Instrumentation.java

 static public Application newApplication(Class<?> clazz, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
    
    
        Application app = (Application)clazz.newInstance();
        app.attach(context);
        return app;
    }

Here is the Application class creation time
Application.java

final void attach(Context context) {
    
    
    attachBaseContext(context);
    mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}

Finally found the attachBaseContext method we are most familiar with.
Let's find the onCreate timing of ContentProvider again, first go back to the above-mentioned publishContentProviders method of ActivityThread.java, you can see that there is an
installContentProviders method that is executed in front, check this method
ActivityThread.java

    @UnsupportedAppUsage
    private void installContentProviders(
            Context context, List<ProviderInfo> providers) {
    
    
        final ArrayList<ContentProviderHolder> results = new ArrayList<>();

        for (ProviderInfo cpi : providers) {
    
    
            if (DEBUG_PROVIDER) {
    
    
                StringBuilder buf = new StringBuilder(128);
                buf.append("Pub ");
                buf.append(cpi.authority);
                buf.append(": ");
                buf.append(cpi.name);
                Log.i(TAG, buf.toString());
            }
            ContentProviderHolder cph = installProvider(context, null, cpi,
                    false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
            if (cph != null) {
    
    
                cph.noReleaseNeeded = true;
                results.add(cph);
            }
        }

        try {
    
    
            ActivityManager.getService().publishContentProviders(
                getApplicationThread(), results);
        } catch (RemoteException ex) {
    
    
            throw ex.rethrowFromSystemServer();
        }
    }
private IActivityManager.ContentProviderHolder installProvider(Context context,IActivityManager.ContentProviderHolder holder, ProviderInfo info,boolean noisy, boolean noReleaseNeeded, boolean stable) {
    
    
    ...
    final java.lang.ClassLoader cl = c.getClassLoader();
                localProvider = (ContentProvider)cl.
                    loadClass(info.name).newInstance();
                provider = localProvider.getIContentProvider();
                if (provider == null) {
    
    
                    Slog.e(TAG, "Failed to instantiate class " +
                          info.name + " from sourceDir " +
                          info.applicationInfo.sourceDir);
                    return null;
                }
                if (DEBUG_PROVIDER) Slog.v(
                    TAG, "Instantiating local provider " + info.name);
                // XXX Need to create the correct context for this provider.
                localProvider.attachInfo(c, info);
    ...

}

Here we load the ContentProvider class instance from ClassLoader and call the attachInfo method.
Let's look at ContentProvider.java

private void attachInfo(Context context, ProviderInfo info, boolean testing) {
    
    
    ...
    ContentProvider.this.onCreate();
    ...
}

A brief summary of the above startup process:

  1. ActivityThread.attath()
  2. AMS.attachApplication()
  3. ActivityThread.handleBindApplication()
  4. Application.attachBaseContext()
  5. ActivityThread.installContentProviders
  6. ContentProvider.onCreate()
  7. AMS.publishContentProviders()
  8. Application.onCreate()

So for App startup optimization, we can intervene from three aspects: Application.attachBaseContext(), ContentProvider.onCreate(), and Application.onCreate().

Guess you like

Origin blog.csdn.net/u011106915/article/details/124515690