出自:http://blog.csdn.net/luoshengyang/article/details/8577789
在Android系統中,Activity組件在啟動之后,並且在它的窗口顯示出來之前,可以顯示一個啟動窗口。這個啟動窗口可以看作是Activity組件的預覽窗口,是由WindowManagerService服務統一管理的,即由WindowManagerService服務負責啟動和結束。在本文中,我們就詳細分析WindowManagerService服務啟動和結束Activity組件的啟動窗口的過程。
Activity組件的啟動窗口是由ActivityManagerService服務來決定是否要顯示的。如果需要顯示,那么ActivityManagerService服務就會通知WindowManagerService服務來為正在啟動的Activity組件顯示一個啟動窗口,而WindowManagerService服務又是通過窗口管理策略類PhoneWindowManager來創建這個啟動窗口的。這個過程如圖1所示。
圖1 Activity窗口的啟動窗品的創建過程
窗口管理策略類PhoneWindowManager創建完成Activity組件的啟動窗口之后,就會請求WindowManagerService服務將該啟動窗口顯示出來。當Activity組件啟動完成,並且它的窗口也顯示出來的時候,WindowManagerService服務就會結束顯示它的啟動窗口。
注意,Activity組件的啟動窗口是由ActivityManagerService服務來控制是否顯示的,也就是說,Android應用程序是無法決定是否要要Activity組件顯示啟動窗口的。接下來,我們就分別分析Activity組件的啟動窗口的顯示和結束過程。
一. Activity組件的啟動窗口的顯示過程
從前面Android應用程序啟動過程源代碼分析一文可以知道,Activity組件在啟動的過程中,會調用ActivityStack類的成員函數startActivityLocked。注意,在調用ActivityStack類的成員函數startActivityLocked的時候,Actvitiy組件還處於啟動的過程,即它的窗口尚未顯示出來,不過這時候ActivityManagerService服務會檢查是否需要為正在啟動的Activity組件顯示一個啟動窗口。如果需要的話,那么ActivityManagerService服務就會請求WindowManagerService服務為正在啟動的Activity組件設置一個啟動窗口。這個過程如圖2所示。
圖2 Activity組件的啟動窗口的顯示過程
這個過程可以分為6個步驟,接下來我們就詳細分析每一個步驟。
Step 1. ActivityStack.startActivityLocked
- public class ActivityStack {
- ......
- // Set to false to disable the preview that is shown while a new activity
- // is being started.
- static final boolean SHOW_APP_STARTING_PREVIEW = true;
- ......
- private final void startActivityLocked(ActivityRecord r, boolean newTask,
- boolean doResume) {
- final int NH = mHistory.size();
- ......
- int addPos = -1;
- ......
- // Place a new activity at top of stack, so it is next to interact
- // with the user.
- if (addPos < 0) {
- addPos = NH;
- }
- ......
- // Slot the activity into the history stack and proceed
- mHistory.add(addPos, r);
- ......
- if (NH > 0) {
- // We want to show the starting preview window if we are
- // switching to a new task, or the next activity's process is
- // not currently running.
- boolean showStartingIcon = newTask;
- ProcessRecord proc = r.app;
- if (proc == null) {
- proc = mService.mProcessNames.get(r.processName, r.info.applicationInfo.uid);
- }
- if (proc == null || proc.thread == null) {
- showStartingIcon = true;
- }
- ......
- mService.mWindowManager.addAppToken(
- addPos, r, r.task.taskId, r.info.screenOrientation, r.fullscreen);
- 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;
- }
- }
- 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.
- ActivityRecord prev = mResumedActivity;
- 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.task != r.task) prev = null;
- // (2) The current activity is already displayed.
- else if (prev.nowVisible) prev = null;
- }
- mService.mWindowManager.setAppStartingWindow(
- r, r.packageName, r.theme, r.nonLocalizedLabel,
- r.labelRes, r.icon, prev, showStartingIcon);
- }
- } else {
- // If this is the first activity, don't do any fancy animations,
- // because there is nothing for it to animate on top of.
- mService.mWindowManager.addAppToken(addPos, r, r.task.taskId,
- r.info.screenOrientation, r.fullscreen);
- }
- ......
- if (doResume) {
- resumeTopActivityLocked(null);
- }
- }
- ......
- }
這個函數定義在文件frameworks/base/services/java/com/android/server/am/ActivityStack.java中。
參數r描述的就是正在啟動的Activity組件,而參數newTask和doResume描述的是是否要將該Activity組件放在一個新的任務中啟動,以及是否要馬上將該Activity組件啟動起來。
ActivityStack類的成員變量mHistory指向的是一個ArrayList,它描述的便是系統的Activity組件堆棧。ActivityStack類的成員函數startActivityLocked首先找到正在啟動的Activity組件r在系統的Activity組件堆棧中的位置addPos,然后再將正在啟動的Activity組件r保存在這個位置上。
變量NH記錄的是將正在啟動的Activity組件r插入到系統的Activity組件堆棧中之前系統中已經啟動了的Activity組件的個數。如果變量NH的值大於0,那么就說明系統需要執行一個Activity組件切換操作,即需要在系統當前激活的Activity組件和正在啟動的Activity組件r之間執行一個切換操作,使得正在啟動的Activity組件r成為系統接下來要激活的Activity組件。在切換的過程,需要顯示切換動畫,即給系統當前激活的Activity組件顯示一個退出動畫,而給正在啟動的Activity組件r顯示一個啟動動畫,以及需要為正在啟動的Activity組件r顯示一個啟動窗口。另一方面,如果變量NH的值等於0,那么系統就不需要執行Activity組件切換操作,或者為為正在啟動的Activity組件r顯示一個啟動窗口,這時候只需要為正在啟動的Activity組件r創建一個窗口令牌即可。
ActivityStack類的成員變量mService指向的是一個ActivityManagerService對象,這個ActivityManagerService對象就是系統的Activity組件管理服務,它的成員變量mWindowManager指向的是一個WindowManagerService對象,這個WindowManagerService對象也就是系統的Window管理服務。通過調用WindowManagerService類的成員函數addAppToken就可以為正在啟動的Activity組件r創建一個窗口令牌,這個過程可以參考前面Android窗口管理服務WindowManagerService對窗口的組織方式分析一文。
在變量NH的值大於0的情況下,ActivityStack類的成員函數startActivityLocked首先檢查用來運行Activity組件r的進程是否已經啟動起來了。如果已經啟動起來,那么用來描述這個進程的ProcessRecord對象proc的值就不等於null,並且這個ProcessRecord對象proc的成員變量thread的值也不等於null。如果用來運行Activity組件r的進程還沒有啟動起來,或者Activity組件r需要運行在一個新的任務中,那么變量showStartingIcon的值就會等於true,用來描述在系統當前處於激活狀態的Activity組件沒有啟動窗口的情況下,要為Activity組件r創建一個新的啟動窗口,否則的話,就會將系統當前處於激活狀態的Activity組件的啟動窗口復用為Activity組件r的啟動窗口。
系統當前處於激活狀態的Activity組件是通過ActivityStack類的成員變量mResumedActivity來描述的,它的啟動窗口可以復用為Activity組件r的啟動窗口還需要滿足兩個額外的條件:
1. Activity組件mResumedActivity與Activity組件r運行在同一個任務中,即它們的成員變量task指向的是同一個TaskRecord對象;
2. Activity組件mResumedActivity當前是不可見的,即它的成員變量nowVisible的值等於false。
這兩個條件意味着Activity組件mResumedActivity與Activity組件r運行在同一個任務中,並且Activity組件mResumedActivity的窗口還沒有顯示出來就需要切換到Activity組件r去。
ActivityStack類的靜態成員變量SHOW_APP_STARTING_PREVIEW是用描述系統是否可以為正在啟動的Activity組件顯示啟動窗口,只有在它的值等於true,以及正在啟動的Activity組件的窗口接下來是要顯示出來的情況下,即變量doShow的值等於true,ActivityManagerService服務才會請求WindowManagerService服務為正在啟動的Activity組件設置啟動窗口。
一般來說,一個正在啟動的Activity組件的窗口接下來是需要顯示的,但是正在啟動的Activity組件可能會設置一個標志位,用來通知ActivityManagerService服務在它啟動的時候,對它運行在的任務進行重置。一個任務被重置之后,可能會導致其它的Activity組件轉移到這個任務中來,並且位於位於這個任務的頂端。在這種情況下,系統接下來要顯示的窗口就不是正在啟動的Activity組件的窗口的了,而是位於正在啟動的Activity組件所運行在的任務的頂端的那個Activity組件的窗口。正在啟動的Activity組件所運行在的任務同時也是一個前台任務,即它頂端的Activity組件就是系統Activity組件堆棧頂端的Activity組件。
調用參數r所指向一個ActivityRecord對象的成員變量intent所描述的一個Intent對象的成員函數getFlags就可以獲得正在啟動的Activity組件的標志值,當這個標志值的FLAG_ACTIVITY_RESET_TASK_IF_NEEDED位等於1的時候,就說明正在啟動的Activity組件通知ActivityManagerService服務對它運行在的任務進行重置。重置一個任務是通過調用ActivityStack類的成員函數resetTaskIfNeededLocked來實現的。重置了正在啟動的Activity組件所運行在的任務之后,再調用ActivityStack類的成員函數topRunningNonDelayedActivityLocked來檢查位於系統Activity組件堆棧頂端的Activity組件是否就是正在啟動的Activity組件,就可以知道正在啟動的Activity組件的窗口接下來是否是需要顯示的。如果需要顯示的話,那么變量doShow的值就等於true。
ActivityManagerService服務請求WindowManagerService服務為正在啟動的Activity組件設置啟動窗口是通過調用WindowManagerService類的成員函數setAppStartingWindow來實現的。注意,ActivityManagerService服務在請求WindowManagerService服務為正在啟動的Activity組件設置啟動窗口之前,同樣會調用WindowManagerService類的成員函數addAppToken來創建窗口令牌。
ActivityManagerService服務請求WindowManagerService服務為正在啟動的Activity組件設置啟動窗口之后,如果參數doResume的值等於true,那么就會調用ActivityStack類的成員函數resumeTopActivityLocked繼續執行啟動參數r所描述的一個Activity組件的操作,這個過程可以參考前面Android應用程序啟動過程源代碼分析一文。
接下來,我們就繼續分析WindowManagerService類的成員函數setAppStartingWindow的實現,以便可以了解WindowManagerService服務是如何為正在啟動的Activity組件設置啟動窗口的。
Step 2. WindowManagerService.setAppStartingWindow
WindowManagerService類的成員函數setAppStartingWindow定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中,它的實現比較長,我們分段來閱讀:
- public class WindowManagerService extends IWindowManager.Stub
- implements Watchdog.Monitor {
- ......
- public void setAppStartingWindow(IBinder token, String pkg,
- int theme, CharSequence nonLocalizedLabel, int labelRes, int icon,
- IBinder transferFrom, boolean createIfNeeded) {
- ......
- synchronized(mWindowMap) {
- ......
- AppWindowToken wtoken = findAppWindowToken(token);
- ......
- // If the display is frozen, we won't do anything until the
- // actual window is displayed so there is no reason to put in
- // the starting window.
- if (mDisplayFrozen || !mPolicy.isScreenOn()) {
- return;
- }
- if (wtoken.startingData != null) {
- return;
- }
參數token描述的是要設置啟動窗口的Activity組件,而參數transferFrom描述的是要將啟動窗口轉移給Activity組件token的Activity組件。從Step 1可以知道,這兩個Activity組件是運行在同一個任務中的,並且參數token描述的Activity組件Activity組件是正在啟動的Activity組件,而參數transferFrom描述的Activity組件是系統當前激活的Activity組件。
這段代碼首先調用WindowManagerService類的成員函數findAppWindowToken來獲得與參數token對應的一個類型為AppWindowToken的窗口令牌wtoken。如果這個AppWindowToken對象的成員變量startingData的值不等於null,那么就說明參數token所描述的Activity組件已經設置過啟動窗口了,因此,WindowManagerService類的成員函數setAppStartingWindow就不用往下處理了。
這段代碼還會檢查系統屏幕當前是否處於凍結狀態,即WindowManagerService類的成員變量mDisplayFrozen的值是否等於true,或者系統屏幕當前是否處於黑屏狀態,即indowManagerService類的成員變量mPolicy所指向的一個PhoneWindowManager對象的成員函數isScreenOn的返回值是否等於false。如果是處於上述兩種狀態的話,那么WindowManagerService類的成員函數setAppStartingWindow就不用往下處理的。因為在這兩種狀態下,為token所描述的Activity組件設置的啟動窗口是無法顯示的。
我們接着往下閱讀代碼:
- if (transferFrom != null) {
- AppWindowToken ttoken = findAppWindowToken(transferFrom);
- if (ttoken != null) {
- WindowState startingWindow = ttoken.startingWindow;
- if (startingWindow != null) {
- if (mStartingIconInTransition) {
- // In this case, the starting icon has already
- // been displayed, so start letting windows get
- // shown immediately without any more transitions.
- mSkipAppTransitionAnimation = true;
- }
- ......
- final long origId = Binder.clearCallingIdentity();
- // Transfer the starting window over to the new
- // token.
- wtoken.startingData = ttoken.startingData;
- wtoken.startingView = ttoken.startingView;
- wtoken.startingWindow = startingWindow;
- ttoken.startingData = null;
- ttoken.startingView = null;
- ttoken.startingWindow = null;
- ttoken.startingMoved = true;
- startingWindow.mToken = wtoken;
- startingWindow.mRootToken = wtoken;
- startingWindow.mAppToken = wtoken;
- ......
- mWindows.remove(startingWindow);
- mWindowsChanged = true;
- ttoken.windows.remove(startingWindow);
- ttoken.allAppWindows.remove(startingWindow);
- addWindowToListInOrderLocked(startingWindow, true);
- // Propagate other interesting state between the
- // tokens. If the old token is displayed, we should
- // immediately force the new one to be displayed. If
- // it is animating, we need to move that animation to
- // the new one.
- if (ttoken.allDrawn) {
- wtoken.allDrawn = true;
- }
- if (ttoken.firstWindowDrawn) {
- wtoken.firstWindowDrawn = true;
- }
- if (!ttoken.hidden) {
- wtoken.hidden = false;
- wtoken.hiddenRequested = false;
- wtoken.willBeHidden = false;
- }
- if (wtoken.clientHidden != ttoken.clientHidden) {
- wtoken.clientHidden = ttoken.clientHidden;
- wtoken.sendAppVisibilityToClients();
- }
- if (ttoken.animation != null) {
- wtoken.animation = ttoken.animation;
- wtoken.animating = ttoken.animating;
- wtoken.animLayerAdjustment = ttoken.animLayerAdjustment;
- ttoken.animation = null;
- ttoken.animLayerAdjustment = 0;
- wtoken.updateLayers();
- ttoken.updateLayers();
- }
- updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES);
- mLayoutNeeded = true;
- performLayoutAndPlaceSurfacesLocked();
- Binder.restoreCallingIdentity(origId);
- return;
- } else if (ttoken.startingData != null) {
- // The previous app was getting ready to show a
- // starting window, but hasn't yet done so. Steal it!
- ......
- wtoken.startingData = ttoken.startingData;
- ttoken.startingData = null;
- ttoken.startingMoved = true;
- Message m = mH.obtainMessage(H.ADD_STARTING, wtoken);
- // Note: we really want to do sendMessageAtFrontOfQueue() because we
- // want to process the message ASAP, before any other queued
- // messages.
- mH.sendMessageAtFrontOfQueue(m);
- return;
- }
- }
- }
如果參數transferFrom的值不等於null,那么就需要檢查它所描述的Activity組件是否設置有啟動窗口。如果設置有的話,那么就需要將它的啟動窗口設置為參數token所描述的Activity組件的啟動窗口。
參數transferFrom所描述的Activity組件所設置的啟動窗口保存在與它所對應的一個類型為AppWindowToken的窗口令牌的成員變量startingWindow或者startingData中,因此,這段代碼首先調用WindowManagerService類的成員函數findAppWindowToken來獲得與參數transferFrom對應的一個AppWindowToken對象ttoken。如果AppWindowToken對象ttoken的成員變量startingWindow的值不等於null,那么就說明參數transferFrom所描述的Activity組件的啟動窗口已經創建出來了。另一方面,如果AppWindowToken對象ttoken的成員變量startingData的值不等於null,那么就說明用來描述參數transferFrom所描述的Activity組件的啟動窗口的相關數據已經准備好了,但是這個啟動窗口還未創建出來。接下來我們就分別分析這兩種情況。
我們首先分析AppWindowToken對象ttoken的成員變量startingWindow的值不等於null的情況。
這時候如果WindowManagerService類的成員變量mStartingIconInTransition的值等於true,那么就說明參數transferFrom所描述的Activity組件所設置的啟動窗口已經在啟動的過程中了。在這種情況下,就需要跳過參數token所描述的Activity組件和參數transferFrom所描述的Activity組件的切換過程,即將WindowManagerService類的成員變量mSkipAppTransitionAnimation的值設置為true,這是因為接下來除了要將參數transferFrom所描述的Activity組件的啟動窗口轉移給參數token所描述的Activity組件之外,還需要將參數transferFrom所描述的Activity組件的窗口狀態轉移給參數token所描述的Activity組件的窗口。
將參數transferFrom所描述的Activity組件的啟動窗口轉移給參數token所描述的Activity組件需要執行以下幾個操作:
1. 將AppWindowToken對象ttoken的成員變量startingData、startingView和startingWindow的值設置到AppWindowToken對象wtoken的對應成員變量中去,其中,成員變量startingData指向的是一個StartingData對象,它描述的是用來創建啟動窗口的相關數據,成員變量startingView指向的是一個View對象,它描述的是啟動窗口的頂層視圖,成員變量startingWindow指向的是一個WindowState對象,它描述的就是啟動窗口。
2. 將AppWindowToken對象ttoken的成員變量startingData、startingView和startingWindow的值設置為null,這是因為參數transferFrom所描述的Activity組件的啟動窗口已經轉移給參數token所描述的Activity組件了。
3. 將原來屬於參數transferFrom所描述的Activity組件的啟動窗口startingWindow的成員變量mToken、mRootToken和mAppToken的值設置為wtoken,因為這個啟動窗口現在已經屬於參數token所描述的Activity組件了。
將參數transferFrom所描述的Activity組件的窗口狀態轉移給參數token所描述的Activity組件的窗口需要執下幾個操作:
1. 將啟動窗口startingWindow從窗口堆棧中刪除,即從WindowManagerService類的成員變量mWindows所描述的一個ArrayList中刪除。
2. 將啟動窗口startingWindow從屬於窗口令牌ttoken的窗口列表中刪除,即從AppWindowToken對象ttoken的成員變量windows和allAppWindows所描述的兩個ArrayList中刪除。
3. 調用WindowManagerService類的成員函數addWindowToListInOrderLocked重新將啟動窗口startingWindow插入到窗口堆棧中去。注意,因為這時候啟動窗口startingWindow已經被設置為參數token所描述的Activity組件了,因此,在重新將它插入到窗口堆棧中去的時候,它就會位於參數token所描述的Activity組件的窗口的上面,這一點可以參考前面Android窗口管理服務WindowManagerService對窗口的組織方式分析一文。
4. 如果AppWindowToken對象ttoken的成員變量allDrawn和firstWindowDrawn的值等於true,那么就說明與AppWindowToken對象ttoken對應的所有窗口或者第一個窗口已經繪制好了,這時候也需要分別將AppWindowToken對象wtoken的成員變量allDrawn和firstWindowDrawn的值設置為true,以便可以迫使那些與AppWindowToken對象wtoken對應的窗口接下來可以馬上顯示出來。
5. 如果AppWindowToken對象ttoken的成員變量hidden的值等於false,那么就說明參數transferFrom所描述的Activity組件是處於可見狀態的,這時候就需要將AppWindowToken對象wtoken的成員變量hidden、hiddenRequested和willBeHidden的值也設置為false,以便表示參數token所描述的Activity組件也是處於可見狀態的。
6. AppWindowToken類的成員變量clientHidden描述的是對應的Activity組件在應用程序進程這一側的可見狀態。如果AppWindowToken對象wtoken和ttoken的成員變量clientHidden的值不相等,那么就需要將AppWindowToken對象ttoken的成員變量clientHidden的值設置給AppWindowToken對象wtoken的成員變量clientHidden,並且調用AppWindowToken對象wtoken的成員函數sendAppVisibilityToClients來通知相應的應用程序進程,運行在它里面的參數token所描述的Activity組件的可見狀態。
7. 如果AppWindowToken對象ttoken的成員變量animation的值不等於null,那么就說明參數transferFrom所描述的Activity組件的窗口正在顯示動畫,那么就需要將該動畫轉移給參數token所描述的Activity組件的窗口,即將AppWindowToken對象ttoken的成員變量animation、animating和animLayerAdjustment的值設置到AppWindowToken對象wtoken的對應成員變量,並且將AppWindowToken對象ttoken的成員變量animation和animLayerAdjustment的值設置為null和0。最后還需要重新計算與AppWindowToken對象ttoken和wtoken所對應的窗口的Z軸位置。
8. 由於前面的操作導致窗口堆棧的窗口發生了變化,因此就需要調用WindowManagerService類的成員函數updateFocusedWindowLocked來重新計算系統當前可獲得焦點的窗口,以及調用WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLocked來刷新系統的UI。
我們接着分析AppWindowToken對象ttoken的成員變量startingData的值不等於null的情況。
這時候由於WindowManagerService服務還沒有參數transferFrom所描述的Activity組件創建啟動窗口,因此,這段代碼只需要將用創建這個啟動窗口的相關數據轉移給參數token所描述的Activity組件就可以了,即將AppWindowToken對象ttoken的成員變量startingData的值設置給AppWindowToken對象wtoken的成員變量startingData,並且將AppWindowToken對象ttoken的成員變量startingData的值設置為null。
由於這時候參數token所描述的Activity組件的啟動窗口還沒有創建出來,因此,接下來就會向WindowManagerService服務所運行在的線程的消息隊列的頭部插入一個類型ADD_STARTING的消息。當這個消息被處理的時候,WindowManagerService服務就會為參數token所描述的Activity組件創建一個啟動窗口。
WindowManagerService類的成員變量mH指向的是一個類型為H的對象。H是WindowManagerService的一個內部類,它是從Handler類繼承下來的,因此,調用它的成員函數sendMessageAtFrontOfQueue就可以往一個線程的消息隊列的頭部插入一個消息。又由於 WindowManagerService類的成員變量mH所指向的一個H對象是在WindowManagerService服務所運行在的線程中創建的,因此,調用它的成員函數sendMessageAtFrontOfQueue發送的消息是保存在WindowManagerService服務所運行在的線程的消息隊列中的。
如果參數transferFrom所描述的Activity組件沒有啟動窗口或者啟動窗口數據轉移給參數token所描述的Activity組件,那么接下來就可能需要為參數token所描述的Activity組件創建一個新的啟動窗口,如最后一段代碼所示:
- // There is no existing starting window, and the caller doesn't
- // want us to create one, so that's it!
- if (!createIfNeeded) {
- return;
- }
- // If this is a translucent or wallpaper 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 (theme != 0) {
- AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
- com.android.internal.R.styleable.Window);
- if (ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowIsTranslucent, false)) {
- return;
- }
- if (ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowIsFloating, false)) {
- return;
- }
- if (ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowShowWallpaper, false)) {
- return;
- }
- }
- mStartingIconInTransition = true;
- wtoken.startingData = new StartingData(
- pkg, theme, nonLocalizedLabel,
- labelRes, icon);
- Message m = mH.obtainMessage(H.ADD_STARTING, wtoken);
- // Note: we really want to do sendMessageAtFrontOfQueue() because we
- // want to process the message ASAP, before any other queued
- // messages.
- mH.sendMessageAtFrontOfQueue(m);
- }
- }
- ......
如果參數createIfNeeded的值等於false,那么就說明不可以為參數token所描述的Activity組件創建一個新的啟動窗口,因此,這時候WindowManagerService類的成員函數setAppStartingWindow就直接返回而不往下處理了。
另一方面,如果參數token所描述的Activity組件的窗口設置有一個主題,即參數theme的值不等於0,那么該窗口就有可能是:
1. 背景是半透明的;
2. 浮動窗口,即是一個壁紙窗口或者一個輸入法窗口;
3. 需要顯示壁紙(背景也是半透明的)。
由於浮動窗口和背景半透明的窗口是不可以顯示啟動窗口的,因此,在上述三種情況下,WindowManagerService類的成員函數setAppStartingWindow也是直接返回而不往下處理了。
通過了上面的檢查之后,這段代碼就可以為參數token所描述的Activity組件創建一個啟動窗口了,不過這個啟動窗口不是馬上就創建的,而通過一個類型為ADD_STARTING的消息來驅動創建的。這個類型為ADD_STARTING的消息是需要發送到WindowManagerService服務所運行在的線程的消息隊列的頭部去的。在發送這個類型為ADD_STARTING的消息之前,這段代碼首先會創建一個StartingData對象,並且保存在AppWindowToken對象wtoken的成員變量startingData中,用來封裝創建啟動窗口所需要的數據。
如上所述,通過調用WindowManagerService類的成員變量mH的成員函數sendMessageAtFrontOfQueue可以向WindowManagerService服務所運行在的線程的消息隊列的頭部發送一個類型為ADD_STARTING的消息。注意,在發送這個消息之前,這段代碼還會將WindowManagerService類的成員變量mStartingIconInTransition的值設置為true,以便可以表示WindowManagerService服務正在為正在啟動的Activity組件創建啟動窗口。
接下來,我們就繼續分析定義在WindowManagerService內部的H類的成員函數sendMessageAtFrontOfQueue的實現,以便可以了解Activity組件的啟動窗口的創建過程。
Step 3. H.sendMessageAtFrontOfQueue
H類的成員函數sendMessageAtFrontOfQueue是從父類Handler繼承下來的,因此,這一步調用的實際上是Handler類的成員函數sendMessageAtFrontOfQueue。Handler類的成員函數sendMessageAtFrontOfQueue用來向消息隊列頭部的插入一個新的消息,以便這個消息可以下一次消息循環中就能得到處理。在前面Android應用程序線程消息循環模型分析一文中,我們已經分析過往消息隊列發送消息的過程了,這里不再詳述。
從上面的調用過程可以知道,這一步所發送的消息的類型為ADD_STARTING,並且是向WindowManagerService服務所運行在的線程的消息隊列發送的。當這個消息得到處理的時候,H類的成員函數handleMessage就會被調用,因此,接下來我們就繼續分析H類的成員函數handleMessage的實現,以便可以了解Activity組件的啟動窗口的創建過程。
Step 4. H.handleMessage
- public class WindowManagerService extends IWindowManager.Stub
- implements Watchdog.Monitor {
- ......
- private final class H extends Handler {
- ......
- public void handleMessage(Message msg) {
- switch (msg.what) {
- ......
- case ADD_STARTING: {
- final AppWindowToken wtoken = (AppWindowToken)msg.obj;
- final StartingData sd = wtoken.startingData;
- if (sd == null) {
- // Animation has been canceled... do nothing.
- return;
- }
- ......
- View view = null;
- try {
- view = mPolicy.addStartingWindow(
- wtoken.token, sd.pkg,
- sd.theme, sd.nonLocalizedLabel, sd.labelRes,
- sd.icon);
- } catch (Exception e) {
- ......
- }
- if (view != null) {
- boolean abort = false;
- synchronized(mWindowMap) {
- if (wtoken.removed || wtoken.startingData == null) {
- // If the window was successfully added, then
- // we need to remove it.
- if (wtoken.startingWindow != null) {
- ......
- wtoken.startingWindow = null;
- wtoken.startingData = null;
- abort = true;
- }
- } else {
- wtoken.startingView = view;
- }
- ......
- }
- if (abort) {
- try {
- mPolicy.removeStartingWindow(wtoken.token, view);
- } catch (Exception e) {
- ......
- }
- }
- }
- } break;
- ......
- }
- }
- }
- ......
- }
這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
參數msg指向一個Message對象,從前面的Step 2可以知道,它的成員變量what的值等於ADD_STARTING,而成員變量obj指向的是一個AppWindowToken對象。這個AppWindowToken對象描述的就是要創建啟動窗口的Activity組件。H類的成員函數handleMessage首先獲得該AppWindowToken對象,並且保存在變量wtoken中。
有了AppWindowToken對象wtoken,H類的成員函數handleMessage就可以通過它的成員變量startingData來獲得一個StartingData對象,並且保存在變量sd中。StartingData對象sd里面保存了創建啟動窗口所需要的參數,因此,H類的成員函數handleMessage就可以通過這些參數來調用外部類WindowManagerService的成員變量mPolicy所指向的一個PhoneWindowManager對象的成員函數addStartingWindow來為AppWindowToken對象wtoken所描述的Activity組件創建啟動窗口。
如果能夠成功地為AppWindowToken對象wtoken所描述的Activity組件創建啟動窗口,那么PhoneWindowManager類的成員函數addStartingWindow就返回該Activity組件的啟動窗口的頂層視圖。H類的成員函數handleMessage獲得這個視圖之后,就會將它保存在變量view中。
由於在創建這個啟動窗口的過程中,AppWindowToken對象wtoken所描述的Activity組件可能已經被移除,即AppWindowToken對象wtoken的成員變量removed的值等於true,或者它的啟動窗口已經被轉移給另外一個Activity組件了,即AppWindowToken對象wtoken的成員變量startingData的值等於null。在這兩種情況下,如果AppWindowToken對象wtoken的成員變量startingWindow的值不等於null,那么就說明前面不僅成功地為AppWindowToken對象wtoken所描述的Activity組件創建了啟動窗口,並且這個啟動窗口也已經成功地增加到WindowManagerService服務中去了,因此,就需要將該啟動窗口從WindowManagerService服務中刪除,這是通過調用外部類WindowManagerService的成員變量mPolicy所指向的一個PhoneWindowManager對象的成員函數removeStartingWindow來實現的。注意,在刪除之前,還會將AppWindowToken對象wtoken的成員變量startingWindow和startingData的值均設置為null,以表示它所描述的Activity組件沒有一個關聯的啟動窗口。
另一方面,如果AppWindowToken對象wtoken所描述的Activity組件沒有被移除,並且它的啟動窗口了沒有轉移給另外一個Activity組件,那么H類的成員函數handleMessage就會將前面得到的啟動窗口的頂層視圖保存在AppWindowToken對象wtoken的成員變量startingView中。注意,這時候AppWindowToken對象wtoken的成員變量startingWindow會指向一個WindowState對象,這個WindowState對象是由PhoneWindowManager類的成員函數addStartingWindow請求WindowManagerService服務創建的。
接下來,我們就繼續分析PhoneWindowManager類的成員函數addStartingWindow的實現,以便可以了解它是如何為一個Activity組件創建一個啟動窗口,並且將這個啟動窗口增加到WindowManagerService服務中去的。
Step 5. PhoneWindowManager.addStartingWindow
- public class PhoneWindowManager implements WindowManagerPolicy {
- ......
- static final boolean SHOW_STARTING_ANIMATIONS = true;
- ......
- public View addStartingWindow(IBinder appToken, String packageName,
- int theme, CharSequence nonLocalizedLabel,
- int labelRes, int icon) {
- if (!SHOW_STARTING_ANIMATIONS) {
- return null;
- }
- if (packageName == null) {
- return null;
- }
- try {
- Context context = mContext;
- boolean setTheme = false;
- ......
- if (theme != 0 || labelRes != 0) {
- try {
- context = context.createPackageContext(packageName, 0);
- if (theme != 0) {
- context.setTheme(theme);
- setTheme = true;
- }
- } catch (PackageManager.NameNotFoundException e) {
- // Ignore
- }
- }
- if (!setTheme) {
- context.setTheme(com.android.internal.R.style.Theme);
- }
- Window win = PolicyManager.makeNewWindow(context);
- if (win.getWindowStyle().getBoolean(
- com.android.internal.R.styleable.Window_windowDisablePreview, false)) {
- return null;
- }
- Resources r = context.getResources();
- win.setTitle(r.getText(labelRes, nonLocalizedLabel));
- win.setType(
- WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
- // Force the window flags: this is a fake window, so it is not really
- // touchable or focusable by the user. We also add in the ALT_FOCUSABLE_IM
- // flag because we do know that the next window will take input
- // focus, so we want to get the IME window up on top of us right away.
- win.setFlags(
- WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
- WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
- WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
- WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
- 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.setTitle("Starting " + packageName);
- WindowManagerImpl wm = (WindowManagerImpl)
- context.getSystemService(Context.WINDOW_SERVICE);
- View view = win.getDecorView();
- if (win.isFloating()) {
- // Whoops, there is no way to display an animation/preview
- // of such a thing! After all that work... let's skip it.
- // (Note that we must do this here because it is in
- // getDecorView() where the theme is evaluated... maybe
- // we should peek the floating attribute from the theme
- // earlier.)
- return null;
- }
- ......
- wm.addView(view, params);
- // Only return the view if it was successfully added to the
- // window manager... which we can tell by it having a parent.
- return view.getParent() != null ? view : null;
- } catch (WindowManagerImpl.BadTokenException e) {
- // ignore
- ......
- } catch (RuntimeException e) {
- // don't crash if something else bad happens, for example a
- // failure loading resources because we are loading from an app
- // on external storage that has been unmounted.
- ......
- }
- return null;
- }
- ......
- }
這個函數定義在文件frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java中。
PhoneWindowManager類有一個靜態成員變量SHOW_STARTING_ANIMATIONS。如果它的值等於false,那么就禁止顯示Activity組件的啟動窗口。此外,如果參數packageName的值等於null,那么禁止顯示Activity組件的啟動窗口,即調用者需要指定要顯示啟動窗口的Activity組件的包名稱。
如果參數theme和labelRes的值不等於0,那么就說明調用者指定了啟動窗口的主題和標題,這時候就需要創建一個代表了要顯示啟動窗口的Activity組件的運行上下文,以便可以從里面獲得指定的主題和標題。否則的話,啟動窗口的運行上下文就是通過PhoneWindowManager類的成員變量mContext來描述的。PhoneWindowManager類的成員變量mContext是在其構造函數中初始化的,它描述的WindowManagerService服務的運行上下文。注意,如果調用者沒有指定啟動窗口的主題,那么默認使用的主題就為com.android.internal.R.style.Theme。
初始化好啟動窗口的運行上下文之后,即初始化好變量context所指向的一個Context對象之后,PhoneWindowManager類的成員函數addStartingWindow接下來就可以以它來參數來調用PolicyManager類的成員函數makeNewWindow來創建一個窗口了,並且將這個窗口保存在變量win中。從前面Android應用程序窗口(Activity)的窗口對象(Window)的創建過程分析一文可以知道,PolicyManager類的成員函數makeNewWindow創建的是一個類型為PhoneWindow的窗口。注意,如果這個類型為PhoneWindow的窗口的com.android.internal.R.styleable.Window_windowDisablePreview屬性的值等於true,那么就說明不允許為參數appToken所描述的Activity組件顯示啟動窗口。
PolicyManager類的成員函數makeNewWindow接下來還會繼續設置前面所創建的窗口win的以下屬性:
1. 窗口類型:設置為WindowManager.LayoutParams.TYPE_APPLICATION_STARTING,即設置為啟動窗口類型;
2. 窗口標題:由參數labelRes、nonLocalizedLabel,以及窗口的運行上下文context來確定;
3. 窗口標志:分別將indowManager.LayoutParams.FLAG_NOT_TOUCHABLE、WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE和WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM位設置為1,即不可接受觸摸事件和不可獲得焦點,但是可以接受輸入法窗口;
4. 窗口大小:設置為WindowManager.LayoutParams.MATCH_PARENT,即與父窗口一樣大,但是由於這是一個頂層窗口,因此實際上是指與屏幕一樣大;
5. 布局參數:包括窗口所對應的窗口令牌(token)和包名(packageName),以及窗口所使用的動畫類型(windowAnimations)和標題(title)。
從第5點可以看出,一個Activity組件的啟動窗口和它本身的窗口都是對應同一個窗口令牌的,因此, 它們在窗口堆棧中就屬於同一組窗口。
設置好窗口win的屬性之后,接下來調用它的成員函數getDecorView就可以將它的頂層視圖創建出來,並且保存在變量view中。從前面Android應用程序窗口(Activity)的視圖對象(View)的創建過程分析一文可以知道,這個頂層視圖的類型為DecorView。
從前面Android應用程序窗口(Activity)實現框架簡要介紹和學習計划一文可以知道,每一個進程都有一個本地窗口管理服務,這個本地窗口管理服務是由WindowManagerImpl類來實現的,負責維護進程內的所有窗口的視圖對象。通過調用WindowManagerImpl類的成員函數addView就可以將一個窗口的視圖增加到本地窗口管理服務中去,以便這個視圖可以接受本地窗口管理服務的管理。同時,WindowManagerImpl類的成員函數addView還會請求WindowManagerService服務為其所增加的窗口視圖創建一個WindowState對象,以便WindowManagerService服務可以維護對應的窗口的運行狀態。
注意,如果前面所創建的窗口win是一個浮動的窗口,即它的成員函數isFloating的值等於true,那么窗口win就不能作為啟動窗口來使用。這里之所以要在創建了窗口win的頂層視圖之后,再來判斷窗口win是否是一個浮動窗口,是因為一個窗口只有在創建了頂層視圖之后,才能確定是否是浮動窗口。
如果能夠成功地將前面所創建的啟動窗口win的頂層視圖view增加到本地窗口管理服務中,那么頂層視圖view就會有一個父視圖,即它的成員函數getParent的返回值不等於null,這時候PhoneWindowManager類的成員函數addStartingWindow就會將頂層視圖view返回給調用者,表示已經成功地為參數appToken所描述的Activity組件創建了一個啟動窗口。從前面Android應用程序窗口(Activity)的視圖對象(View)的創建過程分析一文可以知道,一個窗口的頂層視圖的父視圖就是通過一個ViewRoot對象來描述的,這個ViewRoot對象負責渲染它的子視圖的UI,以及和WindowManagerService服務通信。
從上面的描述就可以知道,一個Activity組件的啟動窗口在創建完成之后,就會通過調用WindowManagerImpl類的成員函數addView來將其增加到WindowManagerService服務中去,因此,接下來我們就繼續分析WindowManagerImpl類的成員函數addView的實現。
Step 6. WindowManagerImpl.addView
這個函數定義在文件frameworks/base/core/java/android/view/WindowManagerImpl.java中,它的具體實現可以參考前面Android應用程序窗口(Activity)的視圖對象(View)的創建過程分析一文。總的來說,這一步最終會通過調用一個實現了IWindowSession接口的Binder代理對象的成員函數add向WindowManagerService服務發送一個Binder進程間通信請求,這個Binder進程間通信請求最終是由WindowManagerService類的成員函數addWindow來處理的,即為指定的窗口創建一個WindowState對象,以便WindowManagerService服務可以維護它的狀態。這個過程又可以參考前面Android應用程序窗口(Activity)與WindowManagerService服務的連接過程分析一文。
這一步執行完成之后,一個新創建的Activity組件的啟動窗口就增加到WindowManagerService服務中去了,這樣,WindowManagerService服務就可以下次刷新系統UI時,將該啟動窗口顯示出來。
接下來我們再繼續分析Activity組件的啟動窗口結束顯示的過程。
二. Activity組件的啟動窗口的結束過程
Activity組件啟動完成之后,它的窗口就會顯示出來,這時候WindowManagerService服務就需要將它的啟動窗口結束掉。我們知道,在WindowManagerService服務中,每一個窗口都對應有一個WindowState對象。每當WindowManagerService服務需要顯示一個窗口的時候,就會調用一個對應的WindowState對象的成員函數performShowLocked。WindowState類的成員函數performShowLocked在執行的過程中,就會檢查當前正在處理的WindowState對象所描述的窗口是否設置有啟動窗口。如果有的話,那么就會將它結束掉。這個過程如圖3所示。
圖3 Activity組件的啟動窗口的結束過程
這個過程可以分為13個步驟,接下來我們就詳細分析每一個步驟。
Step 1. WindowState.performShowLocked
- public class WindowManagerService extends IWindowManager.Stub
- implements Watchdog.Monitor {
- ......
- private final class WindowState implements WindowManagerPolicy.WindowState {
- ......
- // This must be called while inside a transaction.
- boolean performShowLocked() {
- ......
- if (mReadyToShow && isReadyForDisplay()) {
- ......
- if (!showSurfaceRobustlyLocked(this)) {
- return false;
- }
- mLastAlpha = -1;
- mHasDrawn = true;
- mLastHidden = false;
- mReadyToShow = false;
- enableScreenIfNeededLocked();
- applyEnterAnimationLocked(this);
- int i = mChildWindows.size();
- while (i > 0) {
- i--;
- WindowState c = mChildWindows.get(i);
- if (c.mAttachedHidden) {
- c.mAttachedHidden = false;
- if (c.mSurface != null) {
- c.performShowLocked();
- // It hadn't been shown, which means layout not
- // performed on it, so now we want to make sure to
- // do a layout. If called from within the transaction
- // loop, this will cause it to restart with a new
- // layout.
- mLayoutNeeded = true;
- }
- }
- }
- if (mAttrs.type != TYPE_APPLICATION_STARTING
- && mAppToken != null) {
- mAppToken.firstWindowDrawn = true;
- if (mAppToken.startingData != null) {
- ......
- // If this initial window is animating, stop it -- we
- // will do an animation to reveal it from behind the
- // starting window, so there is no need for it to also
- // be doing its own stuff.
- if (mAnimation != null) {
- mAnimation = null;
- // Make sure we clean up the animation.
- mAnimating = true;
- }
- mFinishedStarting.add(mAppToken);
- mH.sendEmptyMessage(H.FINISHED_STARTING);
- }
- mAppToken.updateReportedVisibilityLocked();
- }
- }
- return true;
- }
- ......
- }
- ......
- }
這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
一個窗口只有在准備就緒顯示之后,才能調用WindowState類的成員函數performShowLocked來顯示它。一個窗口在什么情況下才是准備就緒顯示的呢?一是用來描述它的一個WindowState對象的成員變量mReadyToShow的值等於true,二是調用該WindowState對象的成員函數isReadyForDisplay的返回值也等於true。WindowState類的成員函數isReadyForDisplay主要驗證一個窗口中是否是處於可見狀態、並且不是處於銷毀的狀態。一個窗口必須要處於可見狀態,並且不是正在被銷毀,那么它才是准備就緒顯示的。此外,一個窗口如果是一個子窗口,那么只有當它以及它的父窗口都是可見的,那么它才是處於可見狀態的。
通過了上面的檢查之后,WindowState類的成員函數performShowLocked就會調用WindowManagerService類的成員函數showSurfaceRobustlyLocked來通知SurfaceFlinger服務來顯示當前正在處理的窗口。
一旦WindowManagerService類的成員函數showSurfaceRobustlyLocked成功地通知了SurfaceFlinger服務來顯示當前正在處理的窗口,那么它的返回值就會等於true,接下來WindowState類的成員函數performShowLocked還會對當前正在正在處理的窗口執行以下操作:
1. 將對應的WindowState對象的成員變量mLastAlpha的值設置為-1,以便以后在顯示窗口之前,都可以更新窗口的Alpha通道值。
2. 將對應的WindowState對象的成員變量mHasDrawn的值設置為true,以便可以表示窗口的UI繪制出來了。
3. 將對應的WindowState對象的成員變量mLastHidden的值設置為false,以便可以表示窗口當前是可見的。
4. 將對應的WindowState對象的成員變量mReadyToShow的值設置為false,以便可以表示窗口已經顯示過了,避免重復地請求SurfaceFlinger服務顯示沒有發生變化的窗口。
5. 確保屏幕對WindowManagerService服務是可用的,這是通過調用WindowManagerService類的成員函數enableScreenIfNeededLocked來實現的。系統在啟動完成之前,屏幕是用來顯示開機動畫的,這時候屏幕是被開機動畫占用的。等到系統啟動完成之后,屏幕就應該是被WindowManagerService服務占用的,這時候就需要停止顯示開機動畫。WindowManagerService類的成員函數enableScreenIfNeededLocked就是確保開機動畫已經停止顯示。
6. 給窗口設置一個進入動畫或者顯示動畫,這是通過調用WindowManagerService類的成員函數applyEnterAnimationLocked來實現的。默認是設置為顯示動畫,但是如果窗口之前是不可見的,那么就會設置為進入動畫。
由於當前正在處理的窗口可能有子窗口,因此就需要在顯示完成當前正在處理的窗口之后,繼續將它的子窗口顯示出來。如果一個窗口具有子窗口,那么這些子窗口就會保存在一個對應的WindowState對象的成員變量mChildWindows所描述的一個ArrayList中。注意,只有那些父窗口上一次是不可見的,並且具有繪圖表面的子窗口才需要顯示。顯示子窗口是通過遞歸調用WindowState類的成員函數performShowLocked來實現的。
最后,如果當前正在處理的窗口是一個Acitivy組件相關的窗口,並且不是Acitivy組件的啟動窗口,即當前正在處理的WindowState對象的成員變量mAppToken的值不等於null,並且成員變量mAttrs所指向的一個WindowManager.LayoutParams對象的成員變量type的值不等於TYPE_APPLICATION_STARTING,那么就需要檢查該Acitivy組件是否設置有啟動窗口。如果設置有啟動窗口的話,那么就需要結束顯示該啟動窗口,因為與該Acitivy組件相關的其它窗口已經顯示出來了。
從前面第一部分的內容可以知道,只要當前正在處理的WindowState對象的成員變量mAppToken不等於null,並且它所指向的一個AppWindowToken對象的成員變量startingData的值也不等於null,那么就說明當前正在處理的窗口是一個Acitivy組件相關的窗口,並且這個Acitivy組件設置有一個啟動窗口。在這種情況下,WindowState類的成員函數performShowLocked就會調用WindowManagerService類的成員變量mH所指向的一個H對象的成員函數sendEmptyMessage來向WindowManagerService服務所運行在的線程發送一個類型為FINISHED_STARTING的消息,表示要結束顯示一個Acitivy組件的啟動窗口。在發送這個消息之前,WindowState類的成員函數performShowLocked還會將用來描述要結束顯示啟動窗口的Activity組件的一個AppWindowToken對象增加到WindowManagerService類的成員變量mFinishedStarting所描述的一個ArrayList中去。
注意,如果這時候當前正在處理的窗口還在顯示動畫,即當前正在處理的WindowState對象的成員變量mAnimation的值不等於null,那么WindowState類的成員函數performShowLocked就會同時將該動畫結束掉,即將當前正在處理的WindowState對象的成員變量mAnimation的值設置為null,但是會將另外一個成員變量mAnimating的值設置為true,以便可以在其它地方對當前正在處理的窗口的動畫進行清理。
還有一個地方需要注意的是,如果當前正在處理的窗口是一個Acitivy組件相關的窗口,那么WindowState類的成員函數performShowLocked還會調用當前正在處理的WindowState對象的成員變量mAppToken所指向的一個AppWindowToken對象的成員函數updateReportedVisibilityLocked來向ActivityManagerService服務報告該Acitivy組件的可見性。
接下來,我們就繼續分析在WindowManagerService類內部定義的H類的成員函數sendEmptyMessage的實現,以便可以了解Acitivy組件的啟動窗口的結束過程。
Step 2. H.sendEmptyMessage
H類的成員函數sendEmptyMessage是從父類Handler繼承下來的,因此,這一步調用的實際上是Handler類的成員函數sendEmptyMessage。Handler類的成員函數sendEmptyMessage用來向消息隊列頭部的插入一個新的消息,以便這個消息可以下一次消息循環中就能得到處理。在前面Android應用程序線程消息循環模型分析一文中,我們已經分析過往消息隊列發送消息的過程了,這里不再詳述。
從上面的調用過程可以知道,這一步所發送的消息的類型為FINISHED_STARTING,並且是向WindowManagerService服務所運行在的線程的消息隊列發送的。當這個消息得到處理的時候,H類的成員函數handleMessage就會被調用,因此,接下來我們就繼續分析H類的成員函數handleMessage的實現,以便可以了解Activity組件的啟動窗口的結束過程。
Step 3. H.handleMessage
- public class WindowManagerService extends IWindowManager.Stub
- implements Watchdog.Monitor {
- ......
- private final class H extends Handler {
- ......
- public void handleMessage(Message msg) {
- switch (msg.what) {
- ......
- case FINISHED_STARTING: {
- IBinder token = null;
- View view = null;
- while (true) {
- synchronized (mWindowMap) {
- final int N = mFinishedStarting.size();
- if (N <= 0) {
- break;
- }
- AppWindowToken wtoken = mFinishedStarting.remove(N-1);
- ......
- if (wtoken.startingWindow == null) {
- continue;
- }
- view = wtoken.startingView;
- token = wtoken.token;
- wtoken.startingData = null;
- wtoken.startingView = null;
- wtoken.startingWindow = null;
- }
- try {
- mPolicy.removeStartingWindow(token, view);
- } catch (Exception e) {
- ......
- }
- }
- } break;
- ......
- }
- }
- }
- ......
- }
這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
從前面的Step 1可以知道,這時候用來描述需要結束啟動窗口的Activity組件的AppWindowToken對象都都保存在WindowManagerService類的成員變量mFinishedStarting所描述的一個ArrayList中,因此,通過遍歷保存在該ArrayList中的每一個AppWindowToken對象,就可以結束對應的啟動窗口。
從前面第一部分的內容可以知道,如果一個Activity組件設置有啟動窗口,那么這個啟動窗口的頂層視圖就會保存在用來描述該Activity組件的一個AppWindowToken對象的成員變量startingView中。獲得了一個啟動窗口的頂層視圖之后,就可以調用WindowManagerService類的成員變量mPolicy所指向的一個PhoneWindowManager對象的成員函數removeStartingWindow來通知WindowManagerService服務刪除該啟動窗口,從而可以結束顯示該啟動窗口。
注意,在調用PhoneWindowManager類的成員函數removeStartingWindow來通知WindowManagerService服務刪除一個啟動窗口之前,還需要將用來描述該啟動窗口的宿主Activity組件的一個AppWindowToken對象的成員變量startingData、startingView和startingWindow的值設置為null,因為這三個成員變量保存的數據都是與啟動窗口相關的。
接下來,我們就繼續分析PhoneWindowManager類的成員函數removeStartingWindow的實現,以便可以了解Activity組件的啟動窗口的結束過程。
Step 4. PhoneWindowManager.removeStartingWindow
- public class PhoneWindowManager implements WindowManagerPolicy {
- ......
- public void removeStartingWindow(IBinder appToken, View window) {
- ......
- if (window != null) {
- WindowManagerImpl wm = (WindowManagerImpl) mContext.getSystemService(Context.WINDOW_SERVICE);
- wm.removeView(window);
- }
- }
- ......
- }
這個函數定義在文件frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java中。
PhoneWindowManager類的成員函數removeStartingWindow首先獲得一個本地窗口管理服務。從前面第一部分的Step 5可以知道,這個本地窗口管理服務是由WindowManagerImpl類來實現的。獲得了本地窗口管理服務之后,就可以調用它的成員函數removeView來請求WindowManagerService服務刪除參數window所描述的一個啟動窗口。
Step 5. WindowManagerImpl.removeView
- public class WindowManagerImpl implements WindowManager {
- ......
- public void removeView(View view) {
- synchronized (this) {
- int index = findViewLocked(view, true);
- View curView = removeViewLocked(index);
- if (curView == view) {
- return;
- }
- ......
- }
- }
- ......
- private View[] mViews;
- private ViewRoot[] mRoots;
- ......
- }
這個函數定義在文件frameworks/base/core/java/android/view/WindowManagerImpl.java中。
從前面Android應用程序窗口(Activity)的視圖對象(View)的創建過程分析一文可以知道, 在應用程序進程中,每一個窗口的頂層視圖都對應有一個ViewRoot對象,它們的對應關系是由WindowManagerImpl類來維護的。當前正在處理的進程即為WindowManagerService服務所運行在的進程,也就是System進程,它與普通的應用程序進程一樣,也可以創建窗口,即它在內部也會通過WindowManagerImpl類管理窗口的頂層視圖及其所對應的ViewRoot對象。
WindowManagerImpl類是如何管理進程中的窗口頂層視圖與ViewRoot對象的對應關系的呢?在WindowManagerImpl類中,有兩個成員變量mViews和mRoots,它們分別指向兩個大小相等的View數組和ViewRoot數組,用來保存進程中的窗口頂層視圖對象和ViewRoot對象,其中,第mViews[i]個窗口頂層視圖與第mRoots[i]個ViewRoot對象是一一對應的。
WindowManagerImpl類的成員函數removeView首先調用另外一個成員函數findViewLocked來找到參數view所描述的一個啟動窗口的頂層視圖在數組mViews中的位置index,然后再以這個位置為參數來調用另外一個成員函數removeViewLocked,以便可以刪除該啟動窗口的頂層視圖。
Step 6. WindowManagerImpl.removeViewLocked
- public class WindowManagerImpl implements WindowManager {
- ......
- View removeViewLocked(int index) {
- ViewRoot root = mRoots[index];
- View view = root.getView();
- ......
- root.die(false);
- ......
- return view;
- }
- ......
- }
這個函數定義在文件frameworks/base/core/java/android/view/WindowManagerImpl.java中。
從前面的調用過程可以知道,WindowManagerImpl類的成員函數removeViewLocked要刪除的是在數組mViews中第index個位置的視圖,同時,與這個即將要被刪除的視圖所對應的ViewRoot對象保存在數組mRoots中的第index個位置上。有了這個ViewRoot對象之后,就可以調用它的成員函數die來刪除指定的窗口了。
Step 7. ViewRoot.die
- public final class ViewRoot extends Handler implements ViewParent,
- View.AttachInfo.Callbacks {
- ......
- public void die(boolean immediate) {
- if (immediate) {
- doDie();
- } else {
- sendEmptyMessage(DIE);
- }
- }
- ......
- }
這個函數定義在frameworks/base/core/java/android/view/ViewRoot.java文件中。
當參數immediate的值等於true的時候,ViewRoot類的成員函數die直接調用另外一個成員函數doDie來刪除與當前正在處理的ViewRoot對象所對應的一個View對象,否則的話,ViewRoot類的成員函數die就會先向當前線程的消息隊列發送一個類型為DIE消息,然后等到該消息被處理的時候,再調用ViewRoot類的成員函數doDie來刪除與當前正在處理的ViewRoot對象所對應的一個View對象。
因此,無論如何,ViewRoot類的成員函數die最終都會調用到另外一個成員函數doDie來來刪除與當前正在處理的ViewRoot對象所對應的一個View對象,接下來我們就繼續分析它的實現。
Step 8. ViewRoot.doDie
- public final class ViewRoot extends Handler implements ViewParent,
- View.AttachInfo.Callbacks {
- ......
- void doDie() {
- ......
- synchronized (this) {
- ......
- if (mAdded) {
- mAdded = false;
- dispatchDetachedFromWindow();
- }
- }
- }
- ......
- }
這個函數定義在frameworks/base/core/java/android/view/ViewRoot.java文件中。
ViewRoot類有一個成員變量mAdded,當它的值等於true的時候,就表示當前正在處理的ViewRoot對象有一個關聯的View對象,因此,這時候就可以調用另外一個成員函數dispatchDetachedFromWindow來刪除這個View對象。由於刪除了這個View對象之后,當前正在處理的ViewRoot對象就不再關聯有View對象了,因此,ViewRoot類的成員函數doDie在調用另外一個成員函數dispatchDetachedFromWindow之前,還會將成員變量mAdded的值設置為false。
Step 9. ViewRoot.dispatchDetachedFromWindow
- public final class ViewRoot extends Handler implements ViewParent,
- View.AttachInfo.Callbacks {
- ......
- static IWindowSession sWindowSession;
- ......
- final W mWindow;
- ......
- void dispatchDetachedFromWindow() {
- ......
- try {
- sWindowSession.remove(mWindow);
- } catch (RemoteException e) {
- }
- ......
- }
- ......
- }
這個函數定義在frameworks/base/core/java/android/view/ViewRoot.java文件中。
從前面Android應用程序窗口(Activity)與WindowManagerService服務的連接過程分析一文可以知道,每一個與UI相關的應用程序進程,都與WindowManagerService服務建立有一個連接,這個連接是通過一個實現了IWindowSession接口的Binder代理對象來描述的,並且這個Binder代理對象就保存在ViewRoot類的靜態成員變量sWindowSession中,它引用了運行在WindowManagerService服務這一側的一個類型為Session的Binder本地對象。
注意,由於當前進程即為WindowManagerService服務所運行在的進程,因此,這時候ViewRoot類的靜態成員變量sWindowSession保存的其實不是一個實現了IWindowSession接口的Binder代理對象,而是一個實現了IWindowSession接口的類型為Session的Binder本地對象。這是因為Binder驅動發現Client和Service是位於同一個進程的時候,就會將Service的本地接口直接返回給Client,而不會將Service的代理對象返回給Client,這樣就可以避免在同一進程中執行Binder進程間調用也會經過Binder驅動來中轉。有關Binder進程間通信的內容,可以參考前面Android進程間通信(IPC)機制Binder簡要介紹和學習計划這個系列的文章。
從前面Android應用程序窗口(Activity)與WindowManagerService服務的連接過程分析一文還可以知道,進程中的每一個窗口都有一個對應的W對象,這個W對象就保存在ViewRoot類的成員變量mWindow中。有了這個W對象之后,ViewRoot類的成員函數dispatchDetachedFromWindow就可以調用靜態成員變量sWindowSession所描述的一個Session對象的成員函數remove來通知WindowManagerService服務刪除一個對應的WindowState對象。從前面的調用過程可以知道,這個WindowState對象描述的是一個Activity組件的啟動窗口,因此,WindowManagerService服務刪除了這個WindowState對象之后,就相當於是將一個Activity組件的啟動窗口結束掉了。
接下來,我們就繼續分析Session類的成員函數remove的實現,以便可以了解Activity組件的啟動窗口的結束過程。
Step 10. Session.remove
- public class WindowManagerService extends IWindowManager.Stub
- implements Watchdog.Monitor {
- ......
- private final class Session extends IWindowSession.Stub
- implements IBinder.DeathRecipient {
- ......
- public void remove(IWindow window) {
- removeWindow(this, window);
- }
- ......
- }
- ......
- }
這個函數定義在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中。
Session類的成員函數remove的實現很簡單,它只是調用外部類WindowManagerService的成員函數removeWindow來執行參數window所描述的一個窗口的刪除操作。
Step 11. WindowManagerService.removeWindow
- public class WindowManagerService extends IWindowManager.Stub
- implements Watchdog.Monitor {
- ......
- public void removeWindow(Session session, IWindow client) {
- synchronized(mWindowMap) {
- WindowState win = windowForClientLocked(session, client, false);
- if (win == null) {
- return;
- }
- removeWindowLocked(session, win);
- }
- }
- ......
- }
這個函數定義在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中。
WindowManagerService類的成員函數removeWindow首先調用成員函數windowForClientLocked來找到與參數client所對應的一個WindowState對象,接着再調用成員函數removeWindowLocked來刪除這個WindowState對象,以便可以結束掉這個WindowState對象所描述的一個啟動窗口。
Step 12. WindowManagerService.removeWindowLocked
- public class WindowManagerService extends IWindowManager.Stub
- implements Watchdog.Monitor {
- ......
- public void removeWindowLocked(Session session, WindowState win) {
- ......
- // Visibility of the removed window. Will be used later to update orientation later on.
- boolean wasVisible = false;
- // First, see if we need to run an animation. If we do, we have
- // to hold off on removing the window until the animation is done.
- // If the display is frozen, just remove immediately, since the
- // animation wouldn't be seen.
- if (win.mSurface != null && !mDisplayFrozen && mPolicy.isScreenOn()) {
- // If we are not currently running the exit animation, we
- // need to see about starting one.
- if (wasVisible=win.isWinVisibleLw()) {
- int transit = WindowManagerPolicy.TRANSIT_EXIT;
- if (win.getAttrs().type == TYPE_APPLICATION_STARTING) {
- transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
- }
- // Try starting an animation.
- if (applyAnimationLocked(win, transit, false)) {
- win.mExiting = true;
- }
- }
- if (win.mExiting || win.isAnimating()) {
- // The exit animation is running... wait for it!
- ......
- win.mExiting = true;
- win.mRemoveOnExit = true;
- mLayoutNeeded = true;
- updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES);
- performLayoutAndPlaceSurfacesLocked();
- if (win.mAppToken != null) {
- win.mAppToken.updateReportedVisibilityLocked();
- }
- ......
- return;
- }
- }
- removeWindowInnerLocked(session, win);
- ......
- updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL);
- ......
- }
- ......
- }
這個函數定義在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中。
WindowManagerService類的成員函數removeWindowLocked在刪除參數win所描述的一個窗口之前,首先檢查是否需要對該窗口設置一個退出動畫。只要滿足以下四個條件,那么就需要對參數win所描述的一個窗口設置退出動畫:
1. 參數win所描述的一個窗口具有繪圖表面,即它的成員變量mSurface的值不等於null;
2. 系統屏幕當前沒有被凍結,即WindowManagerService類的成員變量mDisplayFrozen的值等於false;
3. 系統屏幕當前是點亮的,即WindowManagerService類的成員變量mPolicy所指向的一個PhoneWindowManager對象的成員函數isScreenOn的返回值為true;
4. 參數win所描述的一個窗口當前是可見的,即它的成員函數isWinVisibleLw的返回值等於true。
對參數win所描述的一個窗口設置退出動畫是通過調用WindowManagerService類的成員函數applyAnimationLocked來實現的。注意,如果參數win描述的是一個啟動窗口,那么退出動畫的類型就為WindowManagerPolicy.TRANSIT_PREVIEW_DONE,否則的話,退出動畫的類型就為WindowManagerPolicy.TRANSIT_EXIT。
一旦參數win所描述的一個窗口正處於退出動畫或者其它動畫狀態,即它的成員變量mExiting的值等於true或者成員函數isAnimating的返回值等於true,那么WindowManagerService服務就要等它的動畫顯示完成之后,再刪除它,這是通過將它的成員變量mExiting和mRemoveOnExit的值設置為true來完成的。由於這時候還需要顯示參數win所描述的一個窗口的退出動畫或者其它動畫,因此,WindowManagerService類的成員函數removeWindowLocked在返回之前,還需要執行以下操作:
1. 調用WindowManagerService類的成員函數updateFocusedWindowLocked來重新計算系統當前需要獲得焦點的窗口;
2. 調用WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLocked來重新布局和刷新系統的UI;
3. 如果參數win所描述的一個與Activity組件相關的窗口,即它的成員變量mAppToken的值不等於null,那么就會調用這個成員變量所指向的一個AppWindowToken對象的成員函數updateReportedVisibilityLocked來向ActivityManagerService服務報告該Activity組件的可見性。
如果不需要對參數win所描述的一個窗口設置退出動畫,那么WindowManagerService類的成員函數removeWindowLocked就會直接調用成員函數removeWindowInnerLocked來刪除該窗口,並且在刪除了該窗口之后,調用成員函數updateFocusedWindowLocked來重新計算系統當前需要獲得焦點的窗口以及重新布局和刷新系統的UI。
接下來,我們就繼續分析WindowManagerService類的成員函數removeWindowLocked的實現,以便可以了解Activity組件的啟動窗口的結束過程。
Step 13. WindowManagerService.removeWindowLocked
- public class WindowManagerService extends IWindowManager.Stub
- implements Watchdog.Monitor {
- ......
- private void removeWindowInnerLocked(Session session, WindowState win) {
- win.mRemoved = true;
- if (mInputMethodTarget == win) {
- moveInputMethodWindowsIfNeededLocked(false);
- }
- ......
- mPolicy.removeWindowLw(win);
- win.removeLocked();
- mWindowMap.remove(win.mClient.asBinder());
- mWindows.remove(win);
- mWindowsChanged = true;
- ......
- if (mInputMethodWindow == win) {
- mInputMethodWindow = null;
- } else if (win.mAttrs.type == TYPE_INPUT_METHOD_DIALOG) {
- mInputMethodDialogs.remove(win);
- }
- final WindowToken token = win.mToken;
- final AppWindowToken atoken = win.mAppToken;
- token.windows.remove(win);
- if (atoken != null) {
- atoken.allAppWindows.remove(win);
- }
- ......
- if (token.windows.size() == 0) {
- if (!token.explicit) {
- mTokenMap.remove(token.token);
- mTokenList.remove(token);
- } else if (atoken != null) {
- atoken.firstWindowDrawn = false;
- }
- }
- if (atoken != null) {
- if (atoken.startingWindow == win) {
- atoken.startingWindow = null;
- } else if (atoken.allAppWindows.size() == 0 && atoken.startingData != null) {
- // If this is the last window and we had requested a starting
- // transition window, well there is no point now.
- atoken.startingData = null;
- } else if (atoken.allAppWindows.size() == 1 && atoken.startingView != null) {
- // If this is the last window except for a starting transition
- // window, we need to get rid of the starting transition.
- ......
- Message m = mH.obtainMessage(H.REMOVE_STARTING, atoken);
- mH.sendMessage(m);
- }
- }
- if (win.mAttrs.type == TYPE_WALLPAPER) {
- ......
- adjustWallpaperWindowsLocked();
- } else if ((win.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
- adjustWallpaperWindowsLocked();
- }
- if (!mInLayout) {
- assignLayersLocked();
- mLayoutNeeded = true;
- performLayoutAndPlaceSurfacesLocked();
- if (win.mAppToken != null) {
- win.mAppToken.updateReportedVisibilityLocked();
- }
- }
- ......
- }
- ......
- }
這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
由於參數win所描述的一個窗口馬上就要被刪除了,因此,WindowManagerService類的成員函數removeWindowLocked首先就將它的成員變量mRemoved的值設置為true。此外,如果參數win所描述的窗口是系統輸入法的目標窗口,那么還需要調用WindowManagerService類的成員函數moveInputMethodWindowsIfNeededLocked來重新移動動系統輸入法窗口到其它可能需要輸入法的窗口的上面去。
執行完成以上兩個操作之后,WindowManagerService類的成員函數removeWindowLocked接下來就可以對參數win所描述的一個窗口進行清理了,包括:
1. 調用WindowManagerService類的成員變量mPolicy的成員函數removeWindowLw來通知窗口管理策略類PhoneWindowManager,參數win所描述的一個窗口被刪除了;
2. 調用參數win所指向的一個WindowState對象的成員函數removeLocked來執行自身的清理工作;
3. 將參數win所指向的一個WindowState對象從WindowManagerService類的成員變量mWindowMap和mWindows中刪除,即將參數win所描述的一個窗口從窗口堆棧中刪除。
執行完成以上三個清理工作之后,窗口堆棧就發生變化了,因此,就需要將WindowManagerService類的成員變量mWindowsChanged的值設置為true。
接下來,WindowManagerService類的成員函數removeWindowLocked還會檢查前面被刪除的窗口是否是一個輸入法窗口或者一個輸入法對話框。如果是一個輸入法窗口,那么就會將WindowManagerService類的成員變量mInputMethodWindow的值設置為true;如果是一個輸入法對話框,那么就會它從WindowManagerService類的成員變量mInputMethodDialogs所描述的一個輸入法對話框列表中刪除。
WindowManagerService類的成員函數removeWindowLocked的任務還沒有完成,它還需要繼續從參數win所描述的一個窗口從它的窗口令牌的窗口列表中刪除。參數win所描述的一個窗口的窗口令牌保存在它的成員變量mToken中,這個成員變量指向的是一個WindowToken對象。這個WindowToken對象有一個成員變量windows,它指向的是一個ArrayList中。這個ArrayList即為參數win所描述的一個窗口從它的窗口令牌的窗口列表,因此,將參數win所描述的一個窗口從這個窗口列表中刪除即可。
如果參數win描述的一個是與Activity組件有關的窗口,那么它的成員變量mAppToken就會指向一個AppWindowToken對象。這個AppWindowToken對象的成員變量allAppWindows所指向的一個ArrayList也會保存有參數win所描述的窗口。因此,這時候也需要將參數win所描述的一個窗口從這個ArrayList中刪除。
參數win所描述的一個窗口被刪除了以后,與它所對應的窗口令牌的窗口數量就會減少1。如果一個窗口令牌的窗口數量減少1之后變成0,那么就需要考慮將這個窗口令牌從WindowManagerService服務的窗口令牌列表中刪除了,即從WindowManagerService類的成員變量mTokenMap和mTokenList中刪除,前提是這個窗口令牌不是顯式地被增加到WindowManagerService服務中去的,即用來描述這個窗口令牌的一個WindowToken對象的成員變量explicit的值等於false。
另一方面,如果參數win描述的一個是與Activity組件有關的窗口,並且當它被刪除之后,與該Activity組件有關的窗口的數量變為0,那么就需要將用來描述該Activity組件的一個AppWindowToken對象的成員變量firstWindowDrawn的值設置為false,以表示該Activity組件的第一個窗口還沒有被顯示出來,事實上也是表示目前沒有窗口與該Activity組件對應。
當參數win描述的一個是與Activity組件有關的窗口的時候,WindowManagerService類的成員函數removeWindowLocked還需要檢查該Activity組件是否設置有啟動窗口。如果該Activity組件設置有啟動窗口的話,那么就需要對它的相應成員變量進行清理。這些檢查以及清理工作包括:
1. 如果參數win所描述的窗口即為一個Activity組件的窗口,即它的值等於用來描述與它的宿主Activity組件的一個AppWindowToken對象的成員變量startingWindow的值,那么就需要將AppWindowToken對象的成員變量startingWindow的值設置為null,以便可以表示它所描述的Activity組件的啟動窗口已經結束了;
2. 如果刪除了參數win所描述的窗口之后,它的宿主Activity組件的窗品數量為0,但是該Activity組件又正在准備顯示啟動窗口,即用來描述該Activity組件的一個AppWindowToken對象的成員變量startingData的值不等於null,那么就說明這個啟動窗口接下來也沒有必要顯示了,因此,就需要將該AppWindowToken對象的成員變量startingData的值設置為null;
3. 如果刪除了參數win所描述的窗口之后,它的宿主Activity組件的窗品數量為1,並且用來描述該Activity組件的一個AppWindowToken對象的成員變量startingView的值不等於null,那么就說明該Activity組件剩下的最后一個窗口即為它的啟動窗口,這時候就需要請求WindowManagerService服務結束掉這個啟動窗口,因為已經沒有必要顯示了。
當一個Activity組件剩下的窗口只有一個,並且用來描述該Activity組件的一個AppWindowToken對象的成員變量startingView的值不等於null時,我們是如何知道這個剩下的窗口就是該Activity組件的啟動窗口的呢?從前面第一個部分的內容可以知道,當一個Activity組件的啟動窗口被創建出來之后,它的頂層視圖就會保存在用來描述該Activity組件的一個AppWindowToken對象的成員變量startingView中。因此,如果Activity組件滿足上述兩個條件,我們就可以判斷出它所剩下的一個窗口即為它的啟動窗口。注意,在這種情況下,WindowManagerService類的成員函數removeWindowLocked不是馬上刪除這個啟動窗口的,而是通過向WindowManagerService服務所運行在的線程發送一個類型為REMOVE_STARTING的消息,等到該消息被處理時再來刪除這個啟動窗口。
清理了窗口win的宿主Activity組件的啟動窗口相關的數據之后,WindowManagerService類的成員函數removeWindowLocked又繼續檢查窗口win是否是一個壁紙窗口或者一個顯示壁紙的窗口。如果是的話,那么就需要調用WindowManagerService類的成員函數adjustWallpaperWindowsLocked來重新調整系統中的壁紙窗口在窗口堆棧中的位置,即將它們移動到下一個可能需要顯示壁紙窗口的其它窗口的下面去。
WindowManagerService類的成員函數removeWindowLocked的最后一個任務是檢查WindowManagerService服務當前是否正處於重新布局窗口的狀態,即判斷WindowManagerService類的成員變量mInLayout的值是否等於true。如果不等於true的話,那么就需要調用WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLocked來重新布局窗口,實際上就是刷新系統的UI。
注意,WindowManagerService類的成員函數removeWindowLocked在重新布局系統中的窗口之前,還需要調用另外一個成員函數assignLayersLocked來重新計算系統中的所有窗口的Z軸位置了。此外,WindowManagerService類的成員函數removeWindowLocked在重新布局了系統中的窗口之后,如果發現前面被刪除的窗口win是一個與Activity組件相關的窗口,即它的成員變量mAppToken的值不等於null,那么還會調用這個成員變量所指向的一個AppWindowToken對象的成員函數updateReportedVisibilityLocked來向ActivityManagerService服務報告該Activity組件的可見性。
這一步執行完成之后,一個的Activity組件的啟動窗口結束掉了。至此,我們就分析完成Activity組件的啟動窗口的啟動過程和結束過程了。事實上,一個Activity組件在啟動的過程中,除了可能需要顯示啟動窗口之外,還需要與系統當前激活的Activity組件執行一個切換操作,然后才可以將自己的窗口顯示出來。在接下來的一篇文章中,我們就將繼續分析Activity組件的切換過程,敬請關注!
老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關注!