preface

Students engaged in Android development are familiar with the Activity startup process. Here only combined with their own understanding, through the blog comb and record the Activity start process key nodes, easy to remember and review in the future.

During the Activity startup process, when the process to start the Activity is not created, ActivityManagerService requests the Zygote process to fork a child process as the APP process via socket communication. After the APP starts, it performs a series of initializations using ActivityThread’s main entry function, and then interacts with ActivityManagerService in the ActivityThread class.

The source code to explore

Article source code based on Android 9.0

Entry function main

[ActivityThread.java]

public static void main(String[] args) {
    // Omit performance and debug Settings
    / /...

    // Set the current process name
    Process.setArgV0("<pre-initialized>");

    // Initialize Looper in the current thread and use it as the main thread Looper.
    Looper.prepareMainLooper();

    // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
    // It will be in the format "seq=114"
    // Get the boot sequence number. ActivityManagerService generates a unique identifier for the process to be started with the
    ProcessRecord (ProcessRecord stores complete information about the process).
    long startSeq = 0;
    if(args ! =null) {
        for (int i = args.length - 1; i >= 0; --i) {
            if(args[i] ! =null&& args[i].startsWith(PROC_START_SEQ_IDENT)) { startSeq = Long.parseLong( args[i].substring(PROC_START_SEQ_IDENT.length())); }}}// Instantiate ActivityThread
    ActivityThread thread = new ActivityThread();
    // Initialize and notify ActivityManagerService
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        // Save the main thread Handler
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    // Start Looper infinite loop to listen for main thread message tasks
    Looper.loop();

    // If executed at this point, it means that the main thread Looper exits, and the main thread will run and exit
    throw new RuntimeException("Main thread loop unexpectedly exited");
}
Copy the code

Activitythread. main is the entry point to the APP process. In this method, Looper is created and the loop is started, so it is treated as the main APP thread and keeps running without exiting, listening for messages in the main thread message queue. The main method instantiates the ActivityThread itself, calls its attach method, performs subsequent initialization, and notifies (AMS) that the ActivityManagerService process has been created.

attach

[ActivityThread.java]

private void attach(boolean system, long startSeq) {
    sCurrentActivityThread = this;
    mSystemThread = system;
    if(! system) {// Omit the JIT check part
        / /...
        
        android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                UserHandle.myUserId());
        // mAppThread is IApplicationThread and is created when ActivityThread is instantiated
        RuntimeInit.setApplicationObject(mAppThread.asBinder());
        / / from ActivityManager singleton members get IActivityManager IActivityManagerSingleton
        / / communication interface (ServiceManager. GetService (Context. ACTIVITY_SERVICE))
        final IActivityManager mgr = ActivityManager.getService();
        try {
            // The IActivityManager Binder communicates with AMS to notify the attachApplication method of AMS.
            // Here we pass the IApplicationThread and startup sequence number to AMS for AMS to communicate to the APP process and AMS to match the startup ProcessRecord.
            mgr.attachApplication(mAppThread, startSeq);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
        // Watch for getting close to heap limit.
        // Omit the heap memory listening processing part
        / /...
    } else {
        // omit the system process startup correlation
        / /...
    }

    // add dropbox logging to libcore
    DropBox.setReporter(new DropBoxReporter());

    ViewRootImpl.ConfigChangedCallback configChangedCallback
            = (Configuration globalConfig) -> {
        synchronized (mResourcesManager) {
            // We need to apply this change to the resources immediately, because upon returning
            // the view hierarchy will be informed about it.
            
            // omit device configuration changes that affect resource monitoring and sending
            // CONFIGURATION_CHANGED Relevant parts of the message
            / /...}}; ViewRootImpl.addConfigCallback(configChangedCallback); }Copy the code

The most important step in the attach method is to execute mgr.attachApplication to notify ActivityManagerService APP creation and transfer the IApplicationThread and startSeq.

attachApplication

The application calls ActivityManagerService in the SystemServer process and uses the attachApplication method in ActivityManagerService.

[ActivityManagerService.java]

@Override
public final void attachApplication(IApplicationThread thread, long startSeq) {
    synchronized (this) {
        // Get the PID and UID of the binder calling side
        int callingPid = Binder.getCallingPid();
        final int callingUid = Binder.getCallingUid();
        // Change the pid and UID of the binder calling end to the current process PID and UID, and return the combination of the original UID and PID
        final long origId = Binder.clearCallingIdentity();
        // AMS checks, sets, and performs follow-up tasks on the APP process
        attachApplicationLocked(thread, callingPid, callingUid, startSeq);
        // Restore the PID and UIDBinder.restoreCallingIdentity(origId); }}Copy the code

Enter attachApplicationLocked:

1. Search ProcessRecord

ProcessRecord records complete information about the running process. Normally, one ProcessRecord corresponds to one process.

[ActivityManagerService.java]

private final boolean attachApplicationLocked(IApplicationThread thread,
        int pid, int callingUid, long startSeq) {
    // Find the application record that is being attached... either via
    // the pid if we are running in multiple processes, or just pull the
    // next app record if we are emulating process with anonymous threads.
    ProcessRecord app;
    long startTime = SystemClock.uptimeMillis();
    // The current calling process is not SystemServer process
    if(pid ! = MY_PID && pid >=0) {
        synchronized (mPidsSelfLocked) {
            MPidsSelfLocked Caches the ProcessRecord corresponding to the running process.app = mPidsSelfLocked.get(pid); }}else {
        app = null;
    }

    // It's possible that process called attachApplication before we got a chance to
    // update the internal state.
    if (app == null && startSeq > 0) {
        // Before AMS tells Zygote to create the application process,
        // start the ProcessRecord and save it in mPendingStarts.
        final ProcessRecord pending = mPendingStarts.get(startSeq);
        // Check pending
        if(pending ! =null && pending.startUid == callingUid
                && handleProcessStartedLocked(pending, pid, pending.usingWrapper,
                        startSeq, true)) { app = pending; }}// If no valid ProcessRecord is found, the application process is considered to have failed to start
    if (app == null) {
        Slog.w(TAG, "No pending application record for pid " + pid
                + " (IApplicationThread " + thread + "); dropping process");
        EventLog.writeEvent(EventLogTags.AM_DROP_PROCESS, pid);
        if (pid > 0&& pid ! = MY_PID) {// Kill the application process
            killProcessQuiet(pid);
            //TODO: killProcessGroup(app.info.uid, pid);
        } else {
            try {
                / / trigger ActivityThread scheduleExit method, terminate the main thread
                thread.scheduleExit();
            } catch (Exception e) {
                // Ignore exceptions.}}return false;
    }
    
    / / omit...
}
Copy the code

AttachApplicationLocked first fetchs ProcessRecord from the cache set that was saved before the process is started. The application process is considered to have started successfully only when ProcessRecord is valid.

ProcessRecord handleProcessStartedLocked method in case of inspection: [ActivityManagerService. Java]

private boolean handleProcessStartedLocked(ProcessRecord app, int pid, boolean usingWrapper,
        long expectedStartSeq, boolean procAttached) {
    // Remove the corresponding ProcessRecord from the collection to be started
    mPendingStarts.remove(expectedStartSeq);
    // isProcStartValidLocked checks to see if an AM command has killed the process and the previous ProcessRecord exists
    ProcessRecord is not marked to start. ProcessRecord does not match the start sequence number saved.
    // If the check fails, the corresponding cause is returned.
    final String reason = isProcStartValidLocked(app, expectedStartSeq);
    if(reason ! =null) {
        Slog.w(TAG_PROCESSES, app + " start not valid, killing pid=" + pid
                + "," + reason);
        app.pendingStart = false;
        // Kill the process group
        Process.killProcessQuiet(pid);
        Process.killProcessGroup(app.uid, app.pid);
        return false;
    }
    // BatteryStatsService Buried point statistics
    mBatteryStatsService.noteProcessStart(app.processName, app.info.uid);
    checkTime(app.startTime, "startProcess: done updating battery stats");

    // Omit the system log and monitoring-related sections
    / /...
    // omit the clean portion of the dirty ProcessRecord cache
    / /...
    
    synchronized (mPidsSelfLocked) {
        // Save ProcessRecord into mPidsSelfLocked cache set
        this.mPidsSelfLocked.put(pid, app);
        
        // Omit the relevant part of the timeout listening message
        / /...
    }
    checkTime(app.startTime, "startProcess: done updating pids map");
    return true;
}
Copy the code

HandleProcessStartedLocked method for ProcessRecord check, check after will save into mPidsSelfLocked cache, so that the follow-up can be obtained by pid.

Back in ActivityManagerService. AttachApplicationLocked method, after getting to ProcessRecord continued to perform.

2. APP process death listening

[ActivityManagerService.java]

private final boolean attachApplicationLocked(IApplicationThread thread,
        int pid, int callingUid, long startSeq) {
    / / omit...
    
    // If this application record is still attached to a previous
    // process, clean it up now.
    // If ProcessRecord holds IApplicationThread, it still contains information about the old process.
    // Need to clean up and try to close application process components.
    if(app.thread ! =null) {
        handleAppDiedLocked(app, true.true);
    }

    // Tell the process all about itself.

    if (DEBUG_ALL) Slog.v(
            TAG, "Binding process pid " + pid + " to record " + app);

    final String processName = app.processName;
    try {
        // Create a death obituary callback
        AppDeathRecipient adr = new AppDeathRecipient(
                app, pid, thread);
        // Set the listening for application process death notification
        thread.asBinder().linkToDeath(adr, 0);
        app.deathRecipient = adr;
    } catch (RemoteException e) {
        // Set the send exception during death listening and re-execute startProcessLocked
        app.resetPackageList(mProcessStats);
        startProcessLocked(app, "link fail", processName);
        return false;
    }
    
    / / omit...
}
Copy the code

ActivityManagerService relies on the Binder death monitoring mechanism to monitor for application process death. If the application process is killed, will trigger AppDeathRecipient. BinderDied callback, the callback invokes the appDiedLocked method, perform cleaning action.

3. Bind to the APP process

[ActivityManagerService.java]

private final boolean attachApplicationLocked(IApplicationThread thread,
        int pid, int callingUid, long startSeq) {
    / / omit...
       
    // omit the member initial Settings in ProcessRecord
    // Omit the related part of the timeout message
    
    try {
        // Omit debug Settings
        // Omit the parts related to adaptation Settings
        / /...
      
        if(app.isolatedEntryPoint ! =null) {
            // This is an isolated process which should just call an entry point instead of
            // being bound to an application.
            thread.runIsolatedEntryPoint(app.isolatedEntryPoint, app.isolatedEntryPointArgs);
        } else if(app.instr ! =null) {
            // The instR type is ActiveInstrumentation, which is related to the Instrument test framework and can be passed
            // AMS registers ActiveInstrumentation with the startInstrumentation method.thread.bindApplication(processName, appInfo, providers, app.instr.mClass, profilerInfo, app.instr.mArguments, app.instr.mWatcher, app.instr.mUiAutomationConnection, testMode, mBinderTransactionTrackingEnabled, enableTrackAllocation, isRestrictedBackupMode || ! normalMode, app.persistent,new Configuration(getGlobalConfiguration()), app.compat,
                    getCommonServicesLocked(app.isolated),
                    mCoreSettingsObserver.getCoreSettingsLocked(),
                    buildSerial, isAutofillCompatEnabled);
        } else {
            / / call IApplicationThread. BindApplication method will be binding information to the application process
            thread.bindApplication(processName, appInfo, providers, null, profilerInfo,
                    null.null.null, testMode, mBinderTransactionTrackingEnabled, enableTrackAllocation, isRestrictedBackupMode || ! normalMode, app.persistent,newConfiguration(getGlobalConfiguration()), app.compat, getCommonServicesLocked(app.isolated), mCoreSettingsObserver.getCoreSettingsLocked(), buildSerial, isAutofillCompatEnabled); }}catch (Exception e) {
        // omit the exception handling part
        return false;
    }
    
    // Omit checking the Activity, Service, and Broadcast parts. (If the process of the target component to be started is not started, the process is started first and then the component is started.)
    // omit oom_adj update part
    / /...
}
Copy the code

ActivityManagerService eventually passes application information and various configuration information to the APP process through the IApplicationThread Binder communication interface.

The getCommonServicesLocked method also saves some common system services IBinder in the ArrayMap and sends them to the APP process to improve the efficiency of using these services.

bindApplication

Back to the process of APP, IApplicationThread bindApplication method will be called ActivityThread. BindApplication method: [ActivityThread. Java]

public final void bindApplication(String processName, ApplicationInfo appInfo,
        List<ProviderInfo> providers, 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, boolean autofillCompatibilityEnabled) {

    // If the common Service from AMS is not empty, add it to the ServiceManager store
    if(services ! =null) {
        // Omit the relevant part of the log
        // Setup the service cache in the ServiceManager
        ServiceManager.initServiceCache(services);
    }

    setCoreSettings(coreSettings);

    // Use AppBindData to wrap various information parameters
    AppBindData data = new AppBindData();
    data.processName = processName;
    data.appInfo = appInfo;
    data.providers = providers;
    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.autofillCompatibilityEnabled = autofillCompatibilityEnabled;
    // Currently running in binder threads, sent via mH to the main thread to process AppBindData
    sendMessage(H.BIND_APPLICATION, data);
}
Copy the code

Binder proxy references to common Services are stored in the bindApplication method, and various information parameters are wrapped in AppBindData and sent to the main thread by the handler.

BIND_APPLICATION calls handleBindApplication: [activityThread.java]

private void handleBindApplication(AppBindData data) {
    // Omit the relevant parts of setting sensitive threads and debugging
    
    // Save device and adaptation configuration information
    mBoundApplication = data;
    mConfiguration = new Configuration(data.config);
    mCompatConfiguration = new Configuration(data.config);
    
    mProfiler = new Profiler();
    // Omit the mProfiler configuration-related parts
    
    // Set the process name
    // send up app name; do this *before* waiting for debugger
    Process.setArgV0(data.processName);
    android.ddm.DdmHandleAppName.setAppName(data.processName,
                                            UserHandle.myUserId());
    VMRuntime.setProcessPackageName(data.appInfo.packageName);
    
    // Start performance monitoring
    if(mProfiler.profileFd ! =null) {
        mProfiler.startProfiling();
    }
    
    // Omit Message, ImageDecoder and other compatible parts
    // Omit the initial time zone setting
    // Omit updating the Configuration
    
    // Get LoadedApk object
    data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
    
    // Omit density, 24Hour, StrictMode Settings, etc
    // Omit the part related to waiting for debugger in debug mode
    // Omit the relevant parts of systrace messages, HTTP proxy Settings, etc
    InstrumentationInfo; // Omit the relevant part of InstrumentationInfo
    
    / / create ContextImpl
    final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
    
    // Omit isolated Process related parts
    / / omit NetworkSecurityConfigProvider related parts
    
    // Create Instrumentation and make it hold an ActivityThread reference
    mInstrumentation = new Instrumentation();
    mInstrumentation.basicInit(this);
    
    // Omit the largeHeap Settings
    
    // Allow disk access during application and provider setup. This could
    // block processing ordered broadcasts, but later processing would
    // probably end up doing the same disk access.
    Application app;
    final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
    final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
    try {
        // If the app is being launched for full backup or restore, bring it up in
        // a restricted environment with the base application class.
        // Create an Application object
        app = data.info.makeApplication(data.restrictedBackupMode, null);

        // Propagate autofill compat state
        app.setAutofillCompatibilityEnabled(data.autofillCompatibilityEnabled);

        mInitialApplication = 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)) {// Initializes the ContentProvider registered by the APP
                installContentProviders(app, data.providers);
                // For process that contains content providers, we want to
                // ensure that the JIT is enabled "at some point".
                mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000); }}// Do this after providers, since instrumentation tests generally start their
        // test thread at this point, and we don't want that racing.
        try {
            mInstrumentation.onCreate(data.instrumentationArgs);
        }
        catch (Exception e) {
            throw new RuntimeException(
                "Exception thrown in onCreate() of "
                + data.instrumentationName + ":" + e.toString(), e);
        }
        try {
            // Call the application.oncreate lifecycle method
            mInstrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {
            if(! mInstrumentation.onException(app, e)) {throw new RuntimeException(
                  "Unable to create application " + app.getClass().getName()
                  + ":"+ e.toString(), e); }}}finally {
        // If the app targets < O-MR1, or doesn't change the thread policy
        // during startup, clobber the policy to maintain behavior of b/36951662
        if(data.appInfo.targetSdkVersion < Build.VERSION_CODES.O_MR1 || StrictMode.getThreadPolicy().equals(writesAllowedPolicy)) { StrictMode.setThreadPolicy(savedPolicy); }}// Omit the preloaded font resources section
}
Copy the code

The handleBindApplication method performs a series of Application initialization configurations, and successively creates LoadedApk, ContextImpl, Instrumentation, and Application.

makeApplication

LoadedApk’s makeApplication method is called in the handleBindApplication method to create the Application. Enter this method to see the internal creation process:

[LoadedApk.java]

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

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

    Application app = null;

    // Get the class name of the custom Application defined in Androidmanifest
    String appClass = mApplicationInfo.className;
    if (forceDefaultAppClass || (appClass == null)) {
        // Use the system default Application
        appClass = "android.app.Application";
    }

    try {
        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);
        }
        / / create ContextImpl
        ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
        // Instantiate Application by reflection and call the Attach method of Application
        // attachBaseContext method. In attachBaseContext, assign ContextImpl to mBase.
        app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
        // Make ContextImpl hold the Application reference
        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
                + ":" + e.toString(), e);
        }
    }
    mActivityThread.mAllApplications.add(app);
    mApplication = app;

    // The incoming instrumentation is null
    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); }}}// Rewrite the R 'constants' for all library apks.
    SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers();
    final int N = packageIdentifiers.size();
    for (int i = 0; i < N; i++) {
        final int id = packageIdentifiers.keyAt(i);
        if (id == 0x01 || id == 0x7f) {
            continue;
        }

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

    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

    return app;
}
Copy the code

conclusion

The main method of ActivityThread creates a Looper on the current thread and designates the current thread as the master thread. Then instantiate the ActivityThread and call its Attach method. Finally, the Looper loop is opened to continuously listen for messages in the main thread message queue.

Create the IApplicationThread Binder interface in the Attach method and use the IActivityManager to implement attachApplication in ActivityManagerService. And pass the IApplicationThread.

In ActivityManagerService. AttachApplication for checking after the new launch process, through IApplicationThread call ActivityThread bindApplication method, And pass in the application information parameters. The bindApplication method packages the information parameters and sends them to the main thread to execute the handleBindApplication method.

In the handleBindApplication method, perform the initial configuration of the Application process, instantiate LoadedApk, ContextImpl, Instrumentation, Application and set each other to hold references. Finally, call back to the application.onCreate lifecycle method.