StartActivityUnchecked When an Activity is not being reused, AMS will try to generate a new Activity. At this point, of course, it is not possible to generate only an ActivityRecord that represents the Activity’s information, but also a new Window for the Activity to use.
Of course, anyone who has read the Android source code will know that when the Activity goes through its onCreate life cycle, it calls the Attach method of the Activity to generate a PhoneWindow. But ever wonder what WMS did before that?
But before I talk about ActivityStack, I need to go over the Classes ActivityDisplay, ActivityStack, TaskRecord, ActivityRecord, etc. The time is ripe. All four data structures inherit from ConfigurationContainer.
“ConfigurationContainer”? In the last article, some of you noticed that the WindowContainer parent is ConfigurationContainer.
ConfigurationContainer family
From this UML class diagram, you can further understand that the set of ActivityDisplay controls is ActivityStack.
But ActivityStack doesn’t actually control The TaskRecord directly. Instead, it controls the TaskStack below through StackWindowController, which controls tasks and AppWindowToken. It then indirectly controls TaskRecord and ActivityRecord.
So let’s look at each ConfigurationContainer constructor from the top down.
ActivityDisplay
ActivityDisplay(ActivityStackSupervisor supervisor, Display display) {
mSupervisor = supervisor;
mDisplayId = display.getDisplayId();
mDisplay = display;
mWindowContainerController = createWindowContainerController();
updateBounds();
}
protected DisplayWindowController createWindowContainerController() {
return new DisplayWindowController(mDisplay, this);
}
Copy the code
You can see that the ActivityStackSupervisor and screen ID information are bound to the current ActivityDisplay. At the same time call createWindowContainerController, create a DisplayWindowController.
Wait, shouldn’t that correspond directly to DisplayContent? Let’s look at the DisplayWindowController constructor.
public DisplayWindowController(Display display, WindowContainerListener listener) { super(listener, WindowManagerService.getInstance()); mDisplayId = display.getDisplayId(); synchronized (mWindowMap) { final long callingIdentity = Binder.clearCallingIdentity(); try { mRoot.createDisplayContent(display, this /* controller */); } finally { Binder.restoreCallingIdentity(callingIdentity); } if (mContainer == null) { throw new IllegalArgumentException("Trying to add display=" + display + " dc=" + mRoot.getDisplayContent(mDisplayId)); }}}Copy the code
RootWindowContainer create DisplayContent
This actually calls RootWindowContainer’s createDisplayContent method.
DisplayContent createDisplayContent(final Display display, DisplayWindowController controller) { final int displayId = display.getDisplayId(); // In select scenarios, it is possible that a DisplayContent will be created on demand // rather than waiting for the controller. In this case, associate the controller and return // the existing display. final DisplayContent existing = getDisplayContent(displayId); if (existing ! = null) { existing.setController(controller); return existing; } final DisplayContent dc = new DisplayContent(display, mService, mWallpaperController, controller); if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Adding display=" + display); final DisplayInfo displayInfo = dc.getDisplayInfo(); final Rect rect = new Rect(); mService.mDisplaySettings.getOverscanLocked(displayInfo.name, displayInfo.uniqueId, rect); displayInfo.overscanLeft = rect.left; displayInfo.overscanTop = rect.top; displayInfo.overscanRight = rect.right; displayInfo.overscanBottom = rect.bottom; if (mService.mDisplayManagerInternal ! = null) { mService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager( displayId, displayInfo); dc.configureDisplayPolicy(); // Tap Listeners are supported for: // 1. All physical displays (multi-display). // 2. VirtualDisplays on VR, AA (and everything else). if (mService.canDispatchPointerEvents()) { if (DEBUG_DISPLAY) { Slog.d(TAG, "Registering PointerEventListener for DisplayId: " + displayId); } dc.mTapDetector = new TaskTapPointerEventListener(mService, dc); mService.registerPointerEventListener(dc.mTapDetector); if (displayId == DEFAULT_DISPLAY) { mService.registerPointerEventListener(mService.mMousePositionTracker); } } } return dc; }Copy the code
RootWindowContainer will try to find the DisplayContent of the existing ID. If there is no DisplayContent, a new ID will be generated and the DisplayWindowController will be passed in and Display information will be set. Mainly overscan related information. Overscan first mentioning here, refers to the scanning area, because when the screen if drawing in the drawing area on the edge of the physical screen, may cause a little distortion, if you take a closer look at your phone, you will find that in fact the edge of the screen with black dots around did not draw up, is actually the overscan limit area. We’ll talk more about that later.
Don’t forget that the DisplayContent constructor automatically adds itself to RootWindowContainer.
In order to do that? AMS and WMS are both in the same process and should be able to call each other. And this is actually what we call componentization, trying to separate the logic between two different functions, different packages. DisplayWindowController is actually a proxy class for DisplayContent.
This kind of train of thought, actually a lot of big factories have tried when realizing componentization in the initial stage.
So we can get a corresponding: ActivityDisplay -> DisplayWindowController ->DisplayContent
And controls the collection of Activitystacks.
ActivityStack
ActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
int windowingMode, int activityType, boolean onTop) {
mStackSupervisor = supervisor;
mService = supervisor.mService;
mHandler = new ActivityStackHandler(mService.mHandler.getLooper());
mWindowManager = mService.mWindowManager;
mStackId = stackId;
mCurrentUser = mService.mUserController.getCurrentUserId();
mTmpRect2.setEmpty();
// Set display id before setting activity and window type to make sure it won't affect
// stacks on a wrong display.
mDisplayId = display.mDisplayId;
setActivityType(activityType);
setWindowingMode(windowingMode);
mWindowContainerController = createStackWindowController(display.mDisplayId, onTop,
mTmpRect2);
postAddToDisplay(display, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
}
T createStackWindowController(int displayId, boolean onTop, Rect outBounds) {
return (T) new StackWindowController(mStackId, this, displayId, onTop, outBounds,
mStackSupervisor.mWindowManager);
}
Copy the code
At this point ActivityStack becomes associated with the application. So will save the current StackId at this time, the current process of userId, the mode of the window, more important is to call createStackWindowController created StackWindowController.
private void postAddToDisplay(ActivityDisplay activityDisplay, Rect bounds, boolean onTop) { mDisplayId = activityDisplay.mDisplayId; setBounds(bounds); onParentChanged(); activityDisplay.addChild(this, onTop ? POSITION_TOP : POSITION_BOTTOM); if (inSplitScreenPrimaryWindowingMode()) { // If we created a docked stack we want to resize it so it resizes all other stacks // in the system. mStackSupervisor.resizeDockedStackLocked( getOverrideBounds(), null, null, null, null, PRESERVE_WINDOWS); }}Copy the code
Each time an ActivityStack is generated, the current ActivityStack is added to the target ActivityDisplay via postAddToDisplay.
If you are in split screen mode, you may want to remember the size of the dock under the ActivityStackSupervisor redesktop. What is dock? In split screen mode, half the screens have a dock stack running apps. Once in this split mode, the form is recalculated.
ActivityStack->StackWindowController->TaskStack. And TaskStack is added to DisplayContent’s TaskWindows Controller.
TaskRecord
Let’s look at the TaskRecord class name:
class TaskRecord extends ConfigurationContainer implements TaskWindowContainerListener
Copy the code
First to see the section on TaskWindowContainerController TaskWindowContainerListener callback interface is implemented here.
What you can see at this point is that TaskRecord does not identify the generic as ActivityRecord. But TaskRecord does manage ActivityRecord. It is simply controlled by a list collection:
/** List of all activities in the task arranged in history order */
final ArrayList<ActivityRecord> mActivities;
Copy the code
As you can see from the comments, this collection is the sequence of all activities in the history.
TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) {
mService = service;
userId = UserHandle.getUserId(info.applicationInfo.uid);
taskId = _taskId;
lastActiveTime = SystemClock.elapsedRealtime();
mAffiliatedTaskId = _taskId;
voiceSession = _voiceSession;
voiceInteractor = _voiceInteractor;
isAvailable = true;
mActivities = new ArrayList<>();
mCallingUid = info.applicationInfo.uid;
mCallingPackage = info.packageName;
setIntent(_intent, info);
setMinDimensions(info);
touchActiveTime();
mService.mTaskChangeNotificationController.notifyTaskCreated(_taskId, realActivity);
}
Copy the code
As you can see, taskId,userId, current screen width, active time, and so on will be saved in TaskRecord.
The creation of TaskRecord
Where is TaskRecord created? And just to refresh my memory, I’m going to bring it up here again. When creating applications for the first time, there is no Task, now will call ActivityStarter. StartActivityUnchecked setTaskFromReuseOrCreateNewTask in method.
This method calls createTaskRecord of the current focus ActivityStack.
TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, boolean toTop, ActivityRecord activity, ActivityRecord source, ActivityOptions options) { final TaskRecord task = TaskRecord.create( mService, taskId, info, intent, voiceSession, voiceInteractor); // add the task to stack first, mTaskPositioner might need the stack association addTask(task, toTop, "createTaskRecord"); final int displayId = mDisplayId ! = INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY; final boolean isLockscreenShown = mService.mStackSupervisor.getKeyguardController() .isKeyguardOrAodShowing(displayId); if (! mStackSupervisor.getLaunchParamsController() .layoutTask(task, info.windowLayout, activity, source, options) && ! matchParentBounds() && task.isResizeable() && ! isLockscreenShown) { task.updateOverrideConfiguration(getOverrideBounds()); } task.createWindowContainer(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) ! = 0); return task; }Copy the code
In this we see several key approaches. The TaskRecord project generates a TaskRecord object, inserts the Task into ActivityStack, and calls createWindowContainer to generate the corresponding WindowContainer.
void addTask(final TaskRecord task, final boolean toTop, String reason) { addTask(task, toTop ? MAX_VALUE : 0, true /* schedulePictureInPictureModeChange */, reason); if (toTop) { // TODO: figure-out a way to remove this call. mWindowContainerController.positionChildAtTop(task.getWindowContainerController(), true /* includingParents */); } } // TODO: This shouldn't allow automatic reparenting. Remove the call to preAddTask and deal // with the fall-out... void addTask(final TaskRecord task, int position, boolean schedulePictureInPictureModeChange, String reason) { // TODO: Is this remove really needed? Need to look into the call path for the other addTask mTaskHistory.remove(task); position = getAdjustedPositionForTask(task, position, null /* starting */); final boolean toTop = position >= mTaskHistory.size(); final ActivityStack prevStack = preAddTask(task, reason, toTop); mTaskHistory.add(position, task); task.setStack(this); updateTaskMovement(task, toTop); postAddTask(task, prevStack, schedulePictureInPictureModeChange); }Copy the code
We can see that possible duplicate TaskRecords are first eliminated from the current ActivityStack history. At this getAdjustedPositionForTask find TaskRecord should be inserted into the current position, then find out whether the Task has been binding ActivityStack, binding will be removed from the original ActivityStack out again. Finally, insert it into ActivityStack’s mTaskHistory.
The latter two functions are the picture-in-picture feature of Android7.0, which calls up the Task to refresh when the Supervisor handles this feature.
getAdjustedPositionForTask
int getAdjustedPositionForTask(TaskRecord task, int suggestedPosition, ActivityRecord starting) { int maxPosition = mTaskHistory.size(); if ((starting ! = null && starting.okToShowLocked()) || (starting == null && task.okToShowLocked())) { // If the task or starting activity can be shown, then whatever position is okay. return Math.min(suggestedPosition, maxPosition); } // The task can't be shown, put non-current user tasks below current user tasks. while (maxPosition > 0) { final TaskRecord tmpTask = mTaskHistory.get(maxPosition - 1); if (! mStackSupervisor.isCurrentProfileLocked(tmpTask.userId) || tmpTask.topRunningActivityLocked() == null) { break; } maxPosition--; } return Math.min(suggestedPosition, maxPosition); }Copy the code
As you can see from this function, the Task is initially recommended to be inserted at the top of the history stack if the mLaunchTaskBehind flag bit is false, or at the bottom of the history stack otherwise. If you start a normal Activity, it’s at the top of the history stack. Of course, in addition to the first time to determine whether the top of the current history stack is visible to the current user, if not, continue to find the visible position.
StackWindowController.positionChildAtTop
When a Task is added, if it is identified as a TaskRecord at the top of the stack, the following processing is performed:
public void positionChildAtTop(TaskWindowContainerController child, boolean includingParents) { if (child == null) { // TODO: Fix the call-points that cause this to happen. return; } synchronized(mWindowMap) { final Task childTask = child.mContainer; if (childTask == null) { Slog.e(TAG_WM, "positionChildAtTop: task=" + child + " not found"); return; } mContainer.positionChildAt(POSITION_TOP, childTask, includingParents); if (mService.mAppTransition.isTransitionSet()) { childTask.setSendingToBottom(false); } mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); }}Copy the code
When the Task is executed for the first time, no WindowContainer exists. But if Taskrecord itself is reused. The Task’s Corresponding WindowContainer is found and inserted at the top of the corresponding WindowContainer in the current StackWindowController. Tasks corresponding to TaskRecord are added to the TaskStack’s location hierarchy.
TaskRecord create Task
CreateWindowContainer is called in the last step of TaskRecord creation.
void createWindowContainer(boolean onTop, boolean showForAllUsers) { if (mWindowContainerController ! = null) { throw new IllegalArgumentException("Window container=" + mWindowContainerController + " already created for task=" + this); } final Rect bounds = updateOverrideConfigurationFromLaunchBounds(); setWindowContainerController(new TaskWindowContainerController(taskId, this, getStack().getWindowContainerController(), userId, bounds, mResizeMode, mSupportsPictureInPicture, onTop, showForAllUsers, lastTaskDescription)); } protected void setWindowContainerController(TaskWindowContainerController controller) { if (mWindowContainerController ! = null) { throw new IllegalArgumentException("Window container=" + mWindowContainerController + " already created for task=" + this); } mWindowContainerController = controller; }Copy the code
Quite simply, a Task is created for the TaskRecord.
At this point, are you beginning to understand the previously incomprehensible parts of the Activity launch and why adjusting TaskRecord adjusts to the form? Because TaskRecord itself contains the corresponding form container Task. It adjusts as TaskRecord adjusts.
Also, we are able to sort out a corresponding relationships: TaskRecord – > TaskWindowContainerController – > Task
ActivityRecord
Again, let’s look at its class name:
final class ActivityRecord extends ConfigurationContainer implements AppWindowContainerListener
Copy the code
First of all, we can see in the section on AppWindowContainerListener callback interface are implemented in ActivityRecord, that is, the callback AppWindowContainerController will handle here.
Then look at its constructor:
ActivityRecord(ActivityManagerService _service, ProcessRecord _caller, int _launchedFromPid, int _launchedFromUid, String _launchedFromPackage, Intent _intent, String _resolvedType, ActivityInfo aInfo, Configuration _configuration, ActivityRecord _resultTo, String _resultWho, int _reqCode, boolean _componentSpecified, boolean _rootVoiceInteraction, ActivityStackSupervisor supervisor, ActivityOptions options, ActivityRecord sourceRecord) { service = _service; appToken = new Token(this, _intent); info = aInfo; . // This starts out true, since the initial state of an activity is that we have everything, // and we shouldn't never consider it lacking in state to be removed if it dies. haveState = true; // If the class name in the intent doesn't match that of the target, this is // probably an alias. We have to create a new ComponentName object to keep track // of the real activity name, so that FLAG_ACTIVITY_CLEAR_TOP is handled properly. if (aInfo.targetActivity == null || (aInfo.targetActivity.equals(_intent.getComponent().getClassName()) && (aInfo.launchMode == LAUNCH_MULTIPLE || aInfo.launchMode == LAUNCH_SINGLE_TOP))) { realActivity = _intent.getComponent(); } else { realActivity = new ComponentName(aInfo.packageName, aInfo.targetActivity); } taskAffinity = aInfo.taskAffinity; stateNotNeeded = (aInfo.flags & FLAG_STATE_NOT_NEEDED) ! = 0; appInfo = aInfo.applicationInfo; nonLocalizedLabel = aInfo.nonLocalizedLabel; labelRes = aInfo.labelRes; if (nonLocalizedLabel == null && labelRes == 0) { ApplicationInfo app = aInfo.applicationInfo; nonLocalizedLabel = app.nonLocalizedLabel; labelRes = app.labelRes; } icon = aInfo.getIconResource(); logo = aInfo.getLogoResource(); theme = aInfo.getThemeResource(); realTheme = theme; if (realTheme == 0) { realTheme = aInfo.applicationInfo.targetSdkVersion < HONEYCOMB ? android.R.style.Theme : android.R.style.Theme_Holo; } if ((aInfo.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) ! = 0) { windowFlags |= LayoutParams.FLAG_HARDWARE_ACCELERATED; } if ((aInfo.flags & FLAG_MULTIPROCESS) ! = 0 && _caller ! = null && (aInfo.applicationInfo.uid == SYSTEM_UID || aInfo.applicationInfo.uid == _caller.info.uid)) { processName = _caller.processName; } else { processName = aInfo.processName; } if ((aInfo.flags & FLAG_EXCLUDE_FROM_RECENTS) ! = 0) { intent.addFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); } packageName = aInfo.applicationInfo.packageName; launchMode = aInfo.launchMode; Entry ent = AttributeCache.instance().get(packageName, realTheme, com.android.internal.R.styleable.Window, userId); if (ent ! = null) { fullscreen = ! ActivityInfo.isTranslucentOrFloating(ent.array); hasWallpaper = ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false); noDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false); } else { hasWallpaper = false; noDisplay = false; } setActivityType(_componentSpecified, _launchedFromUid, _intent, options, sourceRecord); immersive = (aInfo.flags & FLAG_IMMERSIVE) ! = 0; requestedVrComponent = (aInfo.requestedVrComponent == null) ? null : ComponentName.unflattenFromString(aInfo.requestedVrComponent); mShowWhenLocked = (aInfo.flags & FLAG_SHOW_WHEN_LOCKED) ! = 0; mTurnScreenOn = (aInfo.flags & FLAG_TURN_SCREEN_ON) ! = 0; mRotationAnimationHint = aInfo.rotationAnimation; lockTaskLaunchMode = aInfo.lockTaskLaunchMode; if (appInfo.isPrivilegedApp() && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) { lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT; } if (options ! = null) { pendingOptions = options; mLaunchTaskBehind = options.getLaunchTaskBehind(); final int rotationAnimation = pendingOptions.getRotationAnimationHint(); // Only override manifest supplied option if set. if (rotationAnimation >= 0) { mRotationAnimationHint = rotationAnimation; } final PendingIntent usageReport = pendingOptions.getUsageTimeReport(); if (usageReport ! = null) { appTimeTracker = new AppTimeTracker(usageReport); } final boolean useLockTask = pendingOptions.getLockTaskMode(); if (useLockTask && lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_DEFAULT) { lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED; }}}Copy the code
You can roughly see the instantiation of the Activity at this point, mainly by setting a lot of startup parameters. It also generates an important object Token. This Token is actually an IApplicationToken:
static class Token extends IApplicationToken.Stub
Copy the code
Identifies a unique Activity.
But even if there is no logic in the constructor, the rest of the correspondence can be written: ActivityRecord->AppWindowContainer->AppWindowToken.
The creation of an ActivityRecord and the associated initialization of a WindowContainer
ActivityRecord is extremely important as a host of activity-related information. But you can’t see when the ActivityRecord is created, or where the WindowContainer corresponding to the ActivityRecord is created
As a refresher, ActivityRecord is actually generated by checking the process’s permissions in ActivityStarter’s startActivity.
After the ActivityRecord is generated, startActivityUnChecked is called, and the Task is found or created. The binding between ActivityRecord and TaskRecord is completed.
There are four main cases:
- 1. Start with the NEW_TASK parameter, create a TaskRecord, and bind ActivityRecord to it.
- 2. Bind ActivityRecord to the Activity initiator’s TaskRecord if the Activity initiator has been started.
- 3. Start with mInTask, (almost invisible, seen in AMS test code)
- 4. The rest (almost impossible)
These four methods will be called addOrReparentStartingActivity methods, add or replace ActivityRecord binding.
private void addOrReparentStartingActivity(TaskRecord parent, String reason) { if (mStartActivity.getTask() == null || mStartActivity.getTask() == parent) { parent.addActivityToTop(mStartActivity); } else { mStartActivity.reparent(parent, parent.mActivities.size() /* top */, reason); }}Copy the code
Let’s talk about the first creation, the first branch:
void addActivityToTop(ActivityRecord r) { addActivityAtIndex(mActivities.size(), r); } void addActivityAtIndex(int index, ActivityRecord r) { TaskRecord task = r.getTask(); if (task ! = null && task ! = this) { throw new IllegalArgumentException("Can not add r=" + " to task=" + this + " current parent=" + task); } r.setTask(this); // Remove r first, and if it wasn't already in the list and it's fullscreen, count it. if (! mActivities.remove(r) && r.fullscreen) { // Was not previously in list. numFullscreen++; } // Only set this based on the first activity if (mActivities.isEmpty()) { if (r.getActivityType() == ACTIVITY_TYPE_UNDEFINED) { // Normally non-standard activity type for the activity record will be set when the // object is created, however we delay setting the standard application type until // this point so that the task can set the type for additional activities added in // the else condition below. r.setActivityType(ACTIVITY_TYPE_STANDARD); } setActivityType(r.getActivityType()); isPersistable = r.isPersistable(); mCallingUid = r.launchedFromUid; mCallingPackage = r.launchedFromPackage; // Clamp to [1, max]. maxRecents = Math.min(Math.max(r.info.maxRecents, 1), ActivityManager.getMaxAppRecentsLimitStatic()); } else { // Otherwise make all added activities match this one. r.setActivityType(getActivityType()); } final int size = mActivities.size(); if (index == size && size > 0) { final ActivityRecord top = mActivities.get(size - 1); if (top.mTaskOverlay) { // Place below the task overlay activity since the overlay activity should always // be on top. index--; } } index = Math.min(size, index); mActivities.add(index, r); updateEffectiveIntent(); if (r.isPersistable()) { mService.notifyTaskPersisterLocked(this, false); } // Sync. with window manager updateOverrideConfigurationFromLaunchBounds(); final AppWindowContainerController appController = r.getWindowContainerController(); if (appController ! = null) { // Only attempt to move in WM if the child has a controller. It is possible we haven't // created controller for the activity we are starting yet. mWindowContainerController.positionChildAt(appController, index); } // Make sure the list of display UID whitelists is updated // now that this record is in a new task. mService.mStackSupervisor.updateUIDsPresentOnDisplay(); }Copy the code
If the Task overlay is not an ActivityRecord, add it to the top. Otherwise, add it to the next position.
public void positionChildAt(AppWindowContainerController childController, int position) { synchronized(mService.mWindowMap) { final AppWindowToken aToken = childController.mContainer; if (aToken == null) { Slog.w(TAG_WM, "Attempted to position of non-existing app : " + childController); return; } final Task task = mContainer; if (task == null) { throw new IllegalArgumentException("positionChildAt: invalid task=" + this); } task.positionChildAt(position, aToken, false /* includeParents */); }}Copy the code
At the same time to recognize if ActivityRecord itself has been around for AppWindowContainerController, will call TaskWindowContainerController, Call AppWindowContainerController, AppToken inserted into the corresponding Task.
Once the TaskRecord lookup and ActivityRecord binding are complete, the Task and AppToken lookup and binding are complete. ResumeFocusedStackTopActivityLocked will prepare to communicate across processes.
And must be done before resumeFocusedStackTopActivityLocked WMS related processing. This is the startActivityLocked method that I intentionally left out in the Activity launch process.
ActivityStarter starts a new Activity
To avoid going too far, I’m also using some of the original startActivityUnChecked logic here.
mTargetStack.startActivityLocked(mStartActivity, topFocused, newTask, mKeepCurTransition, mOptions); if (mDoResume) { final ActivityRecord topTaskActivity = mStartActivity.getTask().topRunningActivityLocked(); if (! mTargetStack.isFocusable() || (topTaskActivity ! = null && topTaskActivity.mTaskOverlay && mStartActivity ! = topTaskActivity)) { .... } else { if (mTargetStack.isFocusable() && ! mSupervisor.isFocusedStack(mTargetStack)) { mTargetStack.moveToFront("startActivityUnchecked"); } mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity, mOptions); } } else if (mStartActivity ! = null) { ... }Copy the code
You can see that at this point mTargetStack means that if you find the focus that is currently eligible ActivityStack will call startActivityLocked. As a refresher, mStartActivity is the newly generated ActivityRecord, topFocused is the Activity currently being displayed, and newTask is the TaskRecord that matches the Intent.
Let’s see what startActivityLocked has done that’s interesting.
ActivityStack startActivityLocked
void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity, boolean newTask, boolean keepCurTransition, ActivityOptions options) { TaskRecord rTask = r.getTask(); final int taskId = rTask.taskId; // mLaunchTaskBehind tasks get placed at the back of the task stack. if (! r.mLaunchTaskBehind && (taskForIdLocked(taskId) == null || newTask)) { // Last activity in task had been removed or ActivityManagerService reusing task. // Insert or replace. // Might not even be in. r); } TaskRecord task = null; if (! newTask) { ... } // Place a new activity at top of stack, so it is next to interact with the user. // If we are not placing the new activity frontmost, we do not want to deliver the // onUserLeaving callback to the actual frontmost activity final TaskRecord activityTask = r.getTask(); . task = activityTask; // TODO: Need to investigate if it is okay for the controller to already be created by the // time we get to this point. I think it is, But need to double check. / / Use the test in b / 34179495 to trace the call path. / / core event 2 if (r. gutierrez etWindowContainerController () == null) { r.createWindowContainer(); } task.setFrontOfTask(); if (! isHomeOrRecentsStack() || numActivities() > 0) { ... if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) ! = 0) {... } else { int transit = TRANSIT_ACTIVITY_OPEN; if (newTask) { ... transit = TRANSIT_TASK_OPEN; } } mWindowManager.prepareAppTransition(transit, keepCurTransition); mStackSupervisor.mNoAnimActivities.remove(r); } boolean doShow = true; if (newTask) { // Even though this activity is starting fresh, we still need // to reset it to make sure we apply affinities to move any // existing activities from other tasks in to it. // If the caller has requested that the target task be // reset, then do so. if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) ! = 0) { resetTaskIfNeededLocked(r, r); doShow = topRunningNonDelayedActivityLocked(null) == r; } } else if (options ! = null && options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) { doShow = false; } if (r.mLaunchTaskBehind) { ... } else if (SHOW_APP_STARTING_PREVIEW && doShow) { // Figure out if we are transitioning from another activity that is // "has the same starting icon" as the next one. This allows the // window manager to keep the previous window it had previously // created, if it still had one. TaskRecord prevTask = r.getTask(); ActivityRecord prev = prevTask.topRunningActivityWithStartingWindowLocked(); if (prev ! = null) { // We don't want to reuse the previous starting preview if: // (1) The current activity is in a different task. if (prev.getTask() ! = prevTask) { prev = null; } // (2) The current activity is already displayed. else if (prev.nowVisible) { prev = null; R.howstartingwindow (prev, newTask, isTaskSwitch(r, focusedTopActivity)); } } else { ... }}Copy the code
In this approach, I’ve pulled out the parts that need attention, which are actually divided into three core events.
Core event 1 inserts TaskRecord into mTaskHistory
private void insertTaskAtTop(TaskRecord task, ActivityRecord starting) { // TODO: Better place to put all the code below... may be addTask... mTaskHistory.remove(task); // Now put task at top. final int position = getAdjustedPositionForTask(task, mTaskHistory.size(), starting); mTaskHistory.add(position, task); updateTaskMovement(task, true); mWindowContainerController.positionChildAtTop(task.getWindowContainerController(), true /* includingParents */); }Copy the code
You can see from the TaskRecord creation section that when the TaskRecord is first created, it is only done by adding the TaskRecord to mTaskHistory, creating the corresponding TaskWindowContainer, and not adding it to the TaskStack. In this case, the newly generated TaskWindowConatainer is added to the corresponding StackWindowConatainer. But to keep mTaskHistory and TaskWindowConatiner consistent, when you move TaskWindowConatiner, you also move the TaskRecord in mTaskHistory.
The core event, ActivityRecord, creates the corresponding WindowContainer.
if (r.getWindowContainerController() == null) {
r.createWindowContainer();
}
Copy the code
The ActivityRecord does not have its own AppToken. The ActivityRecord creation method is called:
void createWindowContainer() { if (mWindowContainerController ! = null) { throw new IllegalArgumentException("Window container=" + mWindowContainerController + " already created for r=" + this); } inHistory = true; final TaskWindowContainerController taskController = task.getWindowContainerController(); // TODO(b/36505427): Maybe this call should be moved inside updateOverrideConfiguration() task.updateOverrideConfigurationFromLaunchBounds(); // Make sure override configuration is up-to-date before using to create window controller. updateOverrideConfiguration(); mWindowContainerController = new AppWindowContainerController(taskController, appToken, this, Integer.MAX_VALUE /* add on top */, info.screenOrientation, fullscreen, (info.flags & FLAG_SHOW_FOR_ALL_USERS) ! = 0, info.configChanges, task.voiceSession ! = null, mLaunchTaskBehind, isAlwaysFocusable(), appInfo.targetSdkVersion, mRotationAnimationHint, ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L); task.addActivityToTop(this); // When an activity is started directly into a split-screen fullscreen stack, we need to // update the initial multi-window modes so that the callbacks are scheduled correctly when // the user leaves that mode. mLastReportedMultiWindowMode = inMultiWindowMode(); mLastReportedPictureInPictureMode = inPinnedWindowingMode(); }Copy the code
You can see that the logic at this point is very similar to the logic that binds ActivityRecord to TaskRecord.
Don’t forget, when we create a AppWindowContainerController, its constructor creates a AppWindowToken, and added to the DisplayContent mTokenMap.
At this point DisplayContent will collect the new Windows Token. For later use
ShowStartingWindow ActivityRecord Displays the flash screen
void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
boolean fromRecents) {
if (mWindowContainerController == null) {
return;
}
if (mTaskOverlay) {
// We don't show starting window for overlay activities.
return;
}
final CompatibilityInfo compatInfo =
service.compatibilityInfoForPackageLocked(info.applicationInfo);
final boolean shown = mWindowContainerController.addStartingWindow(packageName, theme,
compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(),
allowTaskSnapshot(),
mState.ordinal() >= RESUMED.ordinal() && mState.ordinal() <= STOPPED.ordinal(),
fromRecents);
if (shown) {
mStartingWindowState = STARTING_WINDOW_SHOWN;
}
}
Copy the code
At this point, the first startup Window will be added, and the Window will exist like a splash screen. What does this flash screen refer to?
<style name="Theme"> <! -- Window attributes --> <item name="windowBackground">@drawable/screen_background_selector_dark</item>Copy the code
That’s actually what it means. In other words, the best place for a splash screen is right here. This is also the fastest, because we have not started the App process at this time, we just created a PhoneWindow form in WindowManager and drew it directly through the Surface.
Isn’t that true, look inside AppWindowContainerController method.
public boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags, IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning, boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents) { synchronized(mWindowMap) { ... final WindowState mainWin = mContainer.findMainWindow(); if (mainWin ! = null && mainWin.mWinAnimator.getShown()) { return false; } final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot( mContainer.getTask().mTaskId, mContainer.getTask().mUserId, false /* restoreFromDisk */, false /* reducedResolution */); final int type = getStartingWindowType(newTask, taskSwitch, processRunning, allowTaskSnapshot, activityCreated, fromRecents, snapshot); if (type == STARTING_WINDOW_TYPE_SNAPSHOT) { return createSnapshot(snapshot); } // If this is a translucent window, then don't show a starting window -- the current // effect (a full-screen opaque starting window that fades away to the real contents // when it is ready) does not work for this. if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Checking theme of starting window: 0x" + Integer.toHexString(theme)); if (theme ! = 0) { AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme, com.android.internal.R.styleable.Window, mService.mCurrentUserId); if (ent == null) { ... return false; } final boolean windowIsTranslucent = ent.array.getBoolean( com.android.internal.R.styleable.Window_windowIsTranslucent, false); final boolean windowIsFloating = ent.array.getBoolean( com.android.internal.R.styleable.Window_windowIsFloating, false); final boolean windowShowWallpaper = ent.array.getBoolean( com.android.internal.R.styleable.Window_windowShowWallpaper, false); final boolean windowDisableStarting = ent.array.getBoolean( com.android.internal.R.styleable.Window_windowDisablePreview, false); . if (windowIsTranslucent) { return false; } if (windowIsFloating || windowDisableStarting) { return false; } if (windowShowWallpaper) { if (mContainer.getDisplayContent().mWallpaperController.getWallpaperTarget() == null) { ... windowFlags |= FLAG_SHOW_WALLPAPER; } else { return false; } } } if (mContainer.transferStartingWindow(transferFrom)) { return true; } // There is no existing starting window, and we don't want to create a splash screen, so // that's it! if (type ! = STARTING_WINDOW_TYPE_SPLASH_SCREEN) { return false; }... mContainer.startingData = new SplashScreenStartingData(mService, pkg, theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags, mContainer.getMergedOverrideConfiguration()); scheduleAddStartingWindow(); } return true; }Copy the code
It’s interesting to see from this StartingWindow that I can roughly see the efforts of Google’s engineers on the UI experience.
This can be divided into two cases, the first using snapshots and the second using XML attributes written to Apk packages.
- 1. When snapshots are allowed and the orientation of the current screen is the same as that of the snapshot, the GraphicBuffer of the TaskSnapshotController in WMS is captured and drawn onto the screen. This is a little bit too big. The TaskSnapshotCache is used to record the TaskSnapshotCache frame before the Task leaves. When opened, the TaskSnapshotCache frame is drawn to the screen before the app is launched.
- 2. When using the splash screen page, directly use the PackageManagerService to read relevant information in the configuration file, and then generate SplashScreenStartingData to start drawing the first frame after the icon is pressed. This work is then put into the WMS animation Handler to perform asynchronously.
App startup optimization error correction
Here we can see a familiar figure:
Window_windowIsTranslucent
Copy the code
Some information on the Internet said that we need to optimize the startup, we need to set the flag bit to the transparent state, so that the App will be transparent at the beginning of the startup. Reduced black/white screen time. That’s actually where it comes from, when we click on the icon on Home the first frame is actually this StartingWindow. However, if the always flag bit is set, the follow-up to StartingWindow is not executed (hence, no subsequent blank screen). So it looks like it’s transparent.
But please note that this time we have not start the App fundamental process (process is behind startSpecificActivityLocked method to check boot), the online optimization method is wrong at all. If the startup time is too long or not, it will lead to a problem. The transparency time is too long. After clicking icon, there is no response for a long time.
PhoneWindowManager addSplashScreen creates a startup window
PhoneWindowManager is one of the core classes of WMS. This contains a large number of WMS policies, inherited from Windows ManagerPolicy.
/** {@inheritDoc} */
@Override
public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
int logo, int windowFlags, Configuration overrideConfig, int displayId) {
...
WindowManager wm = null;
View view = null;
try {
Context context = mContext;
...
if (theme != context.getThemeResId() || labelRes != 0) {
try {
context = context.createPackageContext(packageName, CONTEXT_RESTRICTED);
context.setTheme(theme);
} catch (PackageManager.NameNotFoundException e) {
// Ignore
}
}
if (overrideConfig != null && !overrideConfig.equals(EMPTY)) {
...
final TypedArray typedArray = overrideContext.obtainStyledAttributes(
com.android.internal.R.styleable.Window);
final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
...
}
final PhoneWindow win = new PhoneWindow(context);
win.setIsStartingWindow(true);
CharSequence label = context.getResources().getText(labelRes, null);
// Only change the accessibility title if the label is localized
if (label != null) {
win.setTitle(label, true);
} else {
win.setTitle(nonLocalizedLabel, false);
}
win.setType(
WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
...
win.setFlags(
windowFlags|
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
windowFlags|
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
win.setDefaultIcon(icon);
win.setDefaultLogo(logo);
win.setLayout(WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT);
final WindowManager.LayoutParams params = win.getAttributes();
params.token = appToken;
params.packageName = packageName;
params.windowAnimations = win.getWindowStyle().getResourceId(
com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
params.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
if (!compatInfo.supportsScreen()) {
params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
}
params.setTitle("Splash Screen " + packageName);
addSplashscreenContent(win, context);
wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
view = win.getDecorView();
...
wm.addView(view, params);
..
return view.getParent() != null ? new SplashScreenSurface(view, appToken) : null;
} catch (WindowManager.BadTokenException e) {
...
} catch (RuntimeException e) {
...
} finally {
if (view != null && view.getParent() == null) {
Log.w(TAG, "view not successfully added to wm, removing view");
wm.removeViewImmediate(view);
}
}
return null;
}
private void addSplashscreenContent(PhoneWindow win, Context ctx) {
final TypedArray a = ctx.obtainStyledAttributes(R.styleable.Window);
final int resId = a.getResourceId(R.styleable.Window_windowSplashscreenContent, 0);
a.recycle();
if (resId == 0) {
return;
}
final Drawable drawable = ctx.getDrawable(resId);
if (drawable == null) {
return;
}
// We wrap this into a view so the system insets get applied to the drawable.
final View v = new View(ctx);
v.setBackground(drawable);
win.setContentView(v);
}
Copy the code
At this point, we see a familiar class, PhoneWindow. PhoneWindow is familiar to every Android developer, and in fact hosts our interface when an Activity is created.
At the moment, will generate a PhoneWindow at this time, then will read windowSplashscreenContent properties in the resource file as the view of the current PhoneWindow.
<item name="android:windowSplashscreenContent"></item>
Copy the code
Finally, the PhoneWindow DecorView is read out and added to the WMS.
Next, WMS adds the Window instance operation.
conclusion
After this article on the Activity start process of the various clues series, is not the ActivityRecord, TaskRecord have a deeper understanding of it?
Here we can add a diagram for the relationship between AMS and WMS configurationContainers.