天天看點

Android視窗管理服務WindowManagerService顯示Activity元件的啟動視窗(Starting Window)的過程分析

        在Android系統中,Activity元件在啟動之後,并且在它的視窗顯示出來之前,可以顯示一個啟動視窗。這個啟動視窗可以看作是Activity元件的預覽視窗,是由WindowManagerService服務統一管理的,即由WindowManagerService服務負責啟動和結束。在本文中,我們就詳細分析WindowManagerService服務啟動和結束Activity元件的啟動視窗的過程。

《Android系統源代碼情景分析》一書正在進擊的程式員網(http://0xcc0xcd.com)中連載,點選進入!

        Activity元件的啟動視窗是由ActivityManagerService服務來決定是否要顯示的。如果需要顯示,那麼ActivityManagerService服務就會通知WindowManagerService服務來為正在啟動的Activity元件顯示一個啟動視窗,而WindowManagerService服務又是通過視窗管理政策類PhoneWindowManager來建立這個啟動視窗的。這個過程如圖1所示。

Android視窗管理服務WindowManagerService顯示Activity元件的啟動視窗(Starting Window)的過程分析

圖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所示。

Android視窗管理服務WindowManagerService顯示Activity元件的啟動視窗(Starting Window)的過程分析

圖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所示。

Android視窗管理服務WindowManagerService顯示Activity元件的啟動視窗(Starting Window)的過程分析

圖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,歡迎關注!