Android startup animation process - end stage

(1) Launch of Launcher

You can know from the Android startup process that SystemServer will call AMS.systemReady at last.

//frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public void systemReady(final Runnable goingCallback, TimingsTraceLog traceLog) {

	traceBeginAndSlog("StartSystemUI");
	try {
		//startSystemUi
		startSystemUi(context, windowManagerF);
	} catch (Throwable e) {
		reportWtf("starting System UI", e);
	}
	traceEnd();

	//startHomeActivity
	traceLog.traceBegin("PhaseActivityManagerReady");
	startHomeActivityLocked(currentUserId, "systemReady");
	...
}

Intent getHomeIntent() {
        Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null);
        intent.setComponent(mTopComponent);
        intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
        if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
            intent.addCategory(Intent.CATEGORY_HOME);
        }
        return intent;
}

When the System process starts the key services in the System, it will start the Launcher.

After an Activity component is started, it will be recorded. When the main thread it is running on is idle, the main thread will send an Activity component idle notification to the ActivityManagerService.
Since the application Launcher is the first application to be started in the system, that is, its root Activity component is the first Activity component to be started in the system, when the ActivityManagerService receives its idle notification, it can know that the system has just been started.
In this case, the ActivityManagerService will stop displaying the startup animation so that the application Lancher interface can be displayed on the screen.

Note:
If a thread wants to process some transactions when it is idle, it must register an idle message processor with the thread's message queue. The custom idle message handler must inherit from the MessageQueue.IdleHandler class and override the member function queueIdle. When a thread is idle, that is, when there are no new messages to be processed in the message queue, the member function queueIdle of the registered idle message processor will be called.

The main thread of the application is described by the ActivityThread class. Whenever a new Activity component is started, the ActivityThread class will register an idle message processor of type Idler with the message queue of the main thread of the application described by it. In this way, the main thread of an application can send an Activity component idle notification to the ActivityManagerService when it is idle, which is equivalent to notifying the ActivityManagerService that a new Activity component is ready.

In addition, each newly started Activity component has only one chance to send an idle notification to the ActivityManagerService.

(2) Exit animation

After the main thread of the application notifies AMS that the Activity component is idle, AMS will call mStackSupervisor.activityIdleInternalLocked to check the booting status and decide whether to report the end of the startup process to AMS, that is, to end the display of the startup animation.

(A)ActivityStack.activityIdleInternal

final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout,
        Configuration config) {
    ......
    boolean booting = false;
    boolean enableScreen = false;
    boolean activityRemoved = false;

    ActivityRecord r = ActivityRecord.forToken(token);
    if (r != null) {
        ......
        if (isFrontStack(r.task.stack) || fromTimeout) {
            booting = mService.mBooting;
            mService.mBooting = false;
            if (!mService.mBooted) {
                mService.mBooted = true;
                enableScreen = true;
            }
        }
    }
    ......

    if (booting || enableScreen) {
        mService.postFinishBooting(booting, enableScreen);
    }
    ......
    return r;
}

(B)AMS.postFinishBooting

final MainHandler mHandler;

void postFinishBooting(boolean finishBooting, boolean enableScreen) {
    mHandler.sendMessage(mHandler.obtainMessage(FINISH_BOOTING_MSG,
            finishBooting? 1 : 0, enableScreen ? 1 : 0));
}

final class MainHandler extends Handler {

    @Override
    public void handleMessage(Message msg) {
        ......
        case FINISH_BOOTING_MSG: {
            if (msg.arg1 != 0) {
                finishBooting();
            }
            if (msg.arg2 != 0) {
                enableScreenAfterBoot();
            }
            break;
        }
    }
}

The enableScreenAfterBoot method is finally invoked so that the screen can be displayed to show the interface of the application Launcher.

(C)AMS.enableScreenAfterBoot

void enableScreenAfterBoot() {
    EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_ENABLE_SCREEN,
            SystemClock.uptimeMillis());
    mWindowManager.enableScreenAfterBoot();
 
    synchronized (this) {
        updateEventDispatchingLocked();
    }
}

(D)WMS.enableScreenAfterBoot

public void enableScreenAfterBoot() {
        synchronized(mWindowMap) {
            if (mSystemBooted) {
                return;
            }
            // It is used to record whether the system has been started and completed. true represents completion
            mSystemBooted = true;
            hideBootMessagesLocked();
            // Set 30s timeout
            mH.sendEmptyMessageDelayed(H.BOOT_TIMEOUT, 30 * 1000);
        }
        mPolicy.systemBooted();
        performEnableScreen();
    }

The member variable mSystemBooted of WMS is used to record whether the system has been started or not. If the startup is completed, the value of this member variable will be equal to true. At this time, the WMS member function enablescreen afterboot directly return s. Otherwise, the WMS member function enablescreen afterboot first sets the value of this member variable to true, Then another member function performEnableScreen is called to stop displaying the startup animation.

(E)WMS.performEnableScreen

private void performEnableScreen() {

	...
	if (!mBootAnimationStopped) {
             Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);
            // stop boot animation
            // formerly we would just kill the process, but we now ask it to exit so it
            // can choose where to stop the animation.
             // (1) Set the property to notify the bootanim process to exit
            SystemProperties.set("service.bootanim.exit", "1");
            mBootAnimationStopped = true;
      }

	 // Execute SurfaceFlinger to end the boot animation
	try {
		IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");
			if (surfaceFlinger != null) {
				Parcel data = Parcel.obtain();
				data.writeInterfaceToken("android.ui.ISurfaceComposer");
				//(2) When the SurfaceFlinger service receives a message of type IBinder::FIRST_CALL_TRANSACTION, i.e. the type is boot_ When finished requests inter process communication, it will give the request to its member function bootFinished to process.
				surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED
					data, null, 0);
				data.recycle();
			}
	} catch (RemoteException ex) {
		ProtoLog.e(WM_ERROR, "Boot completed: SurfaceFlinger is dead!");
	}
}
...
try {
	mActivityManager.bootAnimationComplete();
} catch (RemoteException e) {
}
	mPolicy.enableScreenAfterBoot();

(F)SurfaceFlinger.bootFinished

void SurfaceFlinger::bootFinished()
{
    if (mBootFinished == true) {
		ALOGE("Extra call to bootFinished");
		return;
	}
	mBootFinished = true;

	// stop boot animation
	property_set("service.bootanim.exit", "1");
	...
}

In the above two places, the attribute "service.bootanim.exit" will be set to "1". When the value of the attribute "service.bootanim.exit" is set to "1", android() will exit, and the startup animation will naturally end. Since android() exits and the return value is false, the BootAnimation::threadLoop() thread ends.

static const char EXIT_PROP_NAME[] = "service.bootanim.exit";

void BootAnimation::checkExit() {
      // Allow surface flinger to gracefully request shutdown
      char value[PROPERTY_VALUE_MAX];
      property_get(EXIT_PROP_NAME, value, "0");
      int exitnow = atoi(value);
      if (exitnow) {
      	   // If the attribute value is 1, it means that the AMS layer is started, the launcher interface is ready, and the Thread requests to exit
          requestExit();
          mCallbacks->shutdown();
      }
}

So far, the analysis of Android system startup animation is all over.

(3) Summary

  • When we set a system attribute, the init process will receive a system attribute change event. When the name of the changed system attribute is equal to "ctl.start" or "ctl.stop", it actually sends a command to start or stop the service to the init process;
  • BootAnimation determines whether to end playing the boot animation by checking the value of the attribute service.bootanim.exit after each frame is drawn, and this value will be notified by the launcher application to AMS and WMS when the root Activity is in idle state after startup. In fact, during this process, SurfaceFlinger will also be set once;

Posted on Tue, 23 Nov 2021 23:08:28 -0500 by northcave