前言
經過上文,我們熟悉了WMS中WindowContainer和WindowContainerController中各自的職責以及各自功能場景,本文將和大家論述一下在WMS在Actvity中的工作流程。
如果遇到問題,歡迎在http://www.lxweimin.com/p/c7cc335b880a討論。
正文
如果看過我寫的Activity啟動流程(二)一文中有一節,startActivityUnchecked當沒有復用Activity時候,AMS會想辦法生成一個新的Activity。此時,當然不可能只生成一個象征著Activity信息的ActivityRecord,同時也要生成一個新的Window供Activity使用。
當然,如果閱讀過Android源碼的人一定會明白,在Activity走onCreate的生命周期的時候,會調用Activity的attach方法生成一個PhoneWindow。但是有沒有想過WMS在這之前又做了什么呢?
實際上在startActivityUnchecked這個方法中有一個可以的方法不知道有人注意到沒有。如果先前跟著任玉剛,git袁這些大佬讀過Activity的啟動流程,就會注意到這個方法:ActivityStack.startActivityLocked。
不過在聊ActivityStack之前,我需要過一遍ActivityDisplay,ActivityStack,TaskRecord,ActivityRecord等類。此時時機已經成熟了。這四個數據結構都繼承于ConfigurationContainer。
ConfigurationContainer是什么?上一篇文章有人細心一點就會注意到WindowContainer父類正是ConfigurationContainer。
ConfigurationContainer家族
通過這個UML類圖能夠進一步的了解到ActivityDisplay控制的集合是ActivityStack。
但是ActivityStack實際上并不是直接控制TaskRecord,而是通過StackWindowController控制下面的TaskStack,從而控制Task以及AppWindowToken,進而間接控制TaskRecord以及ActivityRecord。
那么我們從頂層往底層看每一個ConfigurationContainer的構造函數。
ActivityDisplay
ActivityDisplay(ActivityStackSupervisor supervisor, Display display) {
mSupervisor = supervisor;
mDisplayId = display.getDisplayId();
mDisplay = display;
mWindowContainerController = createWindowContainerController();
updateBounds();
}
protected DisplayWindowController createWindowContainerController() {
return new DisplayWindowController(mDisplay, this);
}
能夠看到的是此時會把ActivityStackSupervisor 以及屏幕id信息綁定到當前的ActivityDisplay。同時調用createWindowContainerController,創建一個DisplayWindowController。
等等,不應該是直接對應到DisplayContent嗎?我們看看DisplayWindowController的構造函數。
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));
}
}
}
RootWindowContainer創建DisplayContent
這里面實際上調用了RootWindowContainer的createDisplayContent方法。
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;
}
RootWindowContainer會嘗試著查找當前已經存在的id對應的DisplayContent,沒有,則生成新的id,并且把DisplayWindowController 傳進去,同時設置Display相關信息,主要是overscan相關的信息。overscan這里先提及一下,是指過掃描區域,因為當屏幕如果把繪制區域繪制在物理屏幕邊緣上,可能造成一點失真,如果你仔細看看你自己的手機,你會發現其實屏幕周圍有點點黑色的邊緣沒有繪制上來,實際上就是這個overscan把區域限制住了。之后還會更加詳細講解。
別忘了DisplayContent的構造函數中會自動的添加自己到RootWindowContainer中。
為了要這么設計?AMS和WMS都在一個進程,按照道理應該都可以互相調用。實際上這種方式就是我們常說的組件化,盡量把兩個不同職能,不同包之間的邏輯分開DisplayWindowController其實就是DisplayContent的代理類。
這種思路,其實很多大廠在實現組件化初期的時候嘗試過。
因此我們能夠得到一個對應:ActivityDisplay -> DisplayWindowController ->DisplayContent
并且控制著ActivityStack的集合。
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);
}
此時ActivityStack就開始和應用互相關聯了。因此此時將會保存當前的StackId,當前進程的userId,窗口的mode,更加重要的是調用createStackWindowController創建了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);
}
}
當每一次生成一個ActivityStack的時候,會通過postAddToDisplay把當前的ActivityStack添加到目標的ActivityDisplay。
如果是分屏模式,可能需要記住ActivityStackSupervisor重新桌面下的dock的大小。dock是什么?在分屏模式下,有一半的屏幕會有一個dock stack在運行程序。一旦進入到這種分屏模式,就會重新計算窗體大小。
此時我們就能找到一個對應關系:ActivityStack->StackWindowController->TaskStack.
同時TaskStack會添加到DisplayContent的TaskWindowController。
TaskRecord
我們來看看TaskRecord的類名:
class TaskRecord extends ConfigurationContainer implements TaskWindowContainerListener
首先能看到上一節中TaskWindowContainerController的回調接口TaskWindowContainerListener是在這里實現的。
此時能夠看到的是TaskRecord并沒有是確定泛型是ActivityRecord。但是TaskRecord確實是用來管理ActivityRecord的。不過是通過一個list集合來控制:
/** List of all activities in the task arranged in history order */
final ArrayList<ActivityRecord> mActivities;
能從注釋上看到,這個集合是所有Activity在歷史記錄中的順序。
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);
}
能看到的是,在TaskRecord中將會保存taskId,userId,當前屏幕寬高,活躍時間等等。
TaskRecord的創建
文件:/frameworks/base/services/core/java/com/android/server/am/ActivityStack.java
TaskRecord哪里創建的呢?為了活躍一下記憶,這邊再一次提出來。當第一次創建應用的時候,并不存在Task,此時會調用ActivityStarter.startActivityUnchecked方法中的setTaskFromReuseOrCreateNewTask。
而這個方法會調用當前焦點ActivityStack的createTaskRecord。
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;
}
在這里面我們看到了幾個關鍵的方法。TaskRecord借助工程生成了一個TaskRecord對象,接著會把Task插入到ActivityStack,并且調用createWindowContainer生成對應的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);
}
我們能夠看到,首先會從當前ActivityStack的歷史中干掉可能存在重復的TaskRecord。此時會getAdjustedPositionForTask找到當前TaskRecord應該插入的位置,接著找一下這個Task是否已經綁定過ActivityStack,綁定過則要從原來的ActivityStack移除出來。最后再插入到ActivityStack的mTaskHistory中。
后面兩個函數是Android7.0的畫中畫特性,喚起Supervisor處理這個特性時候Task需要完成的刷新工作。
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);
}
從這一個函數能看到,Task一開始建議插入的位置,如果mLaunchTaskBehind這個標志位為false則是歷史記錄棧頂,否則則為歷史記錄棧底。如果是一般的Activity啟動,都是歷史記錄棧頂位置。當然初次之外還要判斷當前歷史記錄棧頂是否是當前用戶可見,不可見則不斷往下找可見的位置。
StackWindowController.positionChildAtTop
當添加Task的時候,如果判斷是棧頂的TaskRecord,則會做如下處理:
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();
}
}
第一次Task的時候,還沒存在對應的WindowContainer則不需要理會。但是如果本身Taskrecord是被復用的。則會找到Task對應的WindowContainer,插入到當前StackWindowController中對應的WindowContainer的頂部。此時則會把TaskRecord對應的Task添加到TaskStack對應的位置層級上。
TaskRecord創建Task
在TaskRecord創建的最后一步會調用createWindowContainer。
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;
}
很簡單,此時就創建了一個Task對應到TaskRecord中。
講到這里,是不是開始理解之前Activity啟動中無法理解的部分,為什么調整TaskRecord會調整到窗體。因為TaskRecord本身就包含了對應的Task這個窗體容器。會隨著TaskRecord調整而調整。
同樣的,我們能夠整理出一個對應的關系:TaskRecord -> TaskWindowContainerController ->Task
ActivityRecord
同樣的,我們來看看它的類名:
final class ActivityRecord extends ConfigurationContainer implements AppWindowContainerListener
首先我們能看到上一節中AppWindowContainerListener 回調接口是在ActivityRecord實現的,也就是說AppWindowContainerController的回調將會在這里處理。
接下來啊看看他的構造函數:
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;
}
}
}
能大致的看到這個時候Activity的實例化,主要只是把啟動的參數大量的設置進來。并且生成了一個很重要的對象Token。這個Token實際上就是一個IApplicationToken:
static class Token extends IApplicationToken.Stub
用于標識唯一的Activity。
不過就算在構造函數沒有相應的邏輯,剩下的對應關系啊,我們也能寫出來:
ActivityRecord->AppWindowContainer->AppWindowToken.
ActivityRecord的創建與相關的WindowContainer的初始化
ActivityRecord作為Activity相關信息的承載者極為的重要。但是卻沒看見ActivityRecord究竟是在什么時候創建,沒有看到ActivityRecord對應的WindowContainer在哪里創建
回顧一下,實際上ActivityRecord的生成是在ActivityStarter的startActivity中經過對進程,權限的校驗才生成的。
當生成完了ActivityRecord,則會調用startActivityUnChecked,在這一步驟處理查找到對應Task或者新建Task,完成ActivityRecord和TaskRecord的綁定。
這里分主要分四種情況:
- 1.啟動帶了NEW_TASK的參數,另起一個TaskRecord,把ActivityRecord綁定在上面。
- 2.啟動本身有著Activity的發起者,則把ActivityRecord綁定到發起者的TaskRecord。
- 3.啟動帶著mInTask,(幾乎不見,在AMS測試代碼中見到)
- 4.剩下的情況(幾乎沒有可能)
這四個方法都會調用addOrReparentStartingActivity方法,添加或者更換ActivityRecord的綁定。
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);
}
}
在之前那一篇章,主要這個方法是在Task可以復用的情形。這一次,我們來聊聊第一次創建的情形,走第一個分支的情況:
文件:/frameworks/base/services/core/java/com/android/server/am/TaskRecord.java
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();
}
我們能夠很輕易的看到此時如果Task的mActivities的頂部只要不是mTaskOverlay的ActivityRecord,則直接加到頂部,否則加到下一個位置。
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 */);
}
}
同時判斷到如果ActivityRecord本身已經存在了AppWindowContainerController ,則會調用TaskWindowContainerController,調用AppWindowContainerController ,把AppToken插入到對應的Task位置。
完成了TaskRecord的查找和ActivityRecord綁定,也就完成了Task和AppToken的查找和綁定。resumeFocusedStackTopActivityLocked將會準備進行跨進程通信。
而在resumeFocusedStackTopActivityLocked之前必定做了WMS相關的處理。也就是我在Activity啟動流程中故意遺漏的startActivityLocked方法。
ActivityStarter啟動新的Activity
為了避免跨度太大,這里也上了原來的startActivityUnChecked部分邏輯。
mTargetStack.startActivityLocked(mStartActivity, topFocused, newTask, mKeepCurTransition,
mOptions);
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) {
...
}
能看到此時mTargetStack就是指找到當前符合條件的焦點ActivityStack將會調用startActivityLocked方法。這里回顧一下,mStartActivity就是指新生成的ActivityRecord,topFocused是當前正在顯示的Activity,newTask是根據Intent.flag找到符合標準的TaskRecord。
我們來看看startActivityLocked做了什么有趣的事情。
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 is reusing task.
// Insert or replace.
// Might not even be in.
//核心事件1
insertTaskAtTop(rTask, 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 test in b/34179495 to trace the call path.
//核心事件2
if (r.getWindowContainerController() == 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;
}
}
//核心事件3
r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity));
}
} else {
...
}
}
在這個方法中,我把需要注意的部分抽出來,實際上分為三個核心事件。
核心事件1 把TaskRecord插入到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 */);
}
從TaskRecord創建小節可知,當第一次創建的時候只是做了把TaskRecord添加到mTaskHistory,創建對應的TaskWindowContainer,并沒有添加到TaskStack中。在這個方法中則是把新生成的TaskWindowConatainer添加到對應的StackWindowConatainer。不過為了保證mTaskHistory和TaskWindowConatiner的統一,當移動TaskWindowConatiner的時候,也原地移動了TaskRecord在mTaskHistory的位置。
核心事件二 ActivityRecord創建對應的WindowContainer。
if (r.getWindowContainerController() == null) {
r.createWindowContainer();
}
當檢測到ActivityRecord中沒有自己的AppToken。則會調用ActivityRecord的創建方法:
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();
}
能看到此時的邏輯其實和ActivityRecord綁定TaskRecord的邏輯十分相似。
別忘了,當我們創建了一個AppWindowContainerController,其構造函數就會創建一個AppWindowToken,并且添加到DisplayContent的mTokenMap中。
此時DisplayContent就會收集到新的WindowToken。以供后面的使用
核心事件三 showStartingWindow ActivityRecord開始展示閃屏
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;
}
}
此時會添加第一個啟動的啟動Window,這個Window類似閃屏一樣的存在。這個閃屏是指什么的?
<style name="Theme">
<!-- Window attributes -->
<item name="windowBackground">@drawable/screen_background_selector_dark</item>
實際上就是指這個。換句話說,最佳的閃屏頁面的地方一定是這里。速度最快的也是這里,因為此時根本還沒有啟動我們的App應用進程,只是創建了一個PhoneWindow窗體在WindowManager并且通過Surface直接繪制出來。
是不是這樣,進去看看AppWindowContainerController的方法。
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;
}
很有意思,從這個StartingWindow,我大致能看到了Google工程師在UI體驗上的努力了。
這里可以分為兩個情況,第一個情況是使用快照,第二個情況是使用寫在Apk包中的xml屬性。
1.當允許使用快照,并且當前屏幕的橫豎屏方向和快照的一致時候,將會獲取保存WMS中TaskSnapshotController的Task中GraphicBuffer,把它繪制到屏幕上去。這里稍微有點跨度大了點。實際上就是把Task離開前的那一幀記錄到TaskSnapshotController的緩存(TaskSnapshotCache)中,打開的時候,在啟動app應用之前就會先把退出后臺前的那一幀畫面先繪制到屏幕上。
2.使用閃屏頁面的時候,直接通過PackageManagerService讀取配置文件中相關的信息,接著生成SplashScreenStartingData,啟動繪制按下圖標后的第一幀畫面。接著會把這個工作放給WMS的動畫Handler中異步執行。
App 啟動優化謬誤糾正
這里面我們能夠看到一個很熟悉的身影:
Window_windowIsTranslucent
網上有些資料說,我們要做啟動優化,就要把這個標志位設置為透明狀態,讓App啟動一開始的時候是透明的。減少黑/白屏時間。實際上出處就在這里,當我們點擊Home上的圖標的第一幀實際上就是這個StartingWindow。但是如果設置了Translucent
標志位,將會不執行StartingWindow的后續(也就沒有后續的白屏)。所以看起來,就像透明一樣。
但是請注意這個時候我們根本還沒有啟動App進程(進程是后面的startSpecificActivityLocked方法才校驗啟動),這種網上的優化方法壓根是錯誤的。啟動時間該長還是長,反而會導致一點,透明時間過長,點擊了icon半天沒反應,用戶以為手機卡主了,問題最后還是拋回給這個應用。
PhoneWindowManager addSplashScreen創建啟動窗口
PhoneWindowManager是WMS的核心類之一。這里面包含了大量的WMS的策略,繼承于WindowManagerPolicy。
/** {@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);
}
此時,我們能看到一個熟悉類,PhoneWindow。PhoneWindow對于每一個Android開發者來說都不陌生,實際上在Activity創建的時候,就是由這個PhoneWindow承載了我們的界面。
此刻,此時會生成一個PhoneWindow,接著會讀取windowSplashscreenContent屬性中的資源文件作為當前PhoneWindow的view。
<item name="android:windowSplashscreenContent"></item>
最后再把PhoneWindow的DecorView讀取出來添加到WMS中。
接下來就會進入到正戲,WMS添加Window實例操作。
總結
經過這一篇文章的對Activity啟動流程的各個線索串聯起來,是不是對ActivityRecord,TaskRecord有了更加深刻的了解呢?
這里我們可以針對AMS,WMS的各個ConfigurationContainer之間的關系,添加畫一個圖。
同時本文也點出了,網上App啟動優化一些不是很正確的觀點。實際上這就是閱讀Android源碼的優點。下面我們將會深入WMS的添加實例Window,我這中間會跳過setContentView這些源碼流程,這一塊內容挺多的應該要專門拖出來聊聊,而不應該放在WMS中囫圇吞棗般的過一遍。