Android6.0 WMS(八) 顯示Activity的啓動窗口



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

Activity組件的啓動窗口是由ActivityManagerService服務來決定是否要顯示的。如果需要顯示,那麼ActivityManagerService服務就會通知WindowManagerService服務來爲正在啓動的Activity組件顯示一個啓動窗口,而WindowManagerService服務又是通過窗口管理策略類PhoneWindowManager來創建這個啓動窗口的。

注意,Activity組件的啓動窗口是由ActivityManagerService服務來控制是否顯示的,也就是說,Android應用程序是無法決定是否要要Activity組件顯示啓動窗口的。接下來,我們就分別分析Activity組件的啓動窗口的顯示和結束過程。

一. Activity組件的啓動窗口的顯示過程

        從前面Android應用程序啓動過程源代碼分析一文可以知道,Activity組件在啓動的過程中,會調用ActivityStack類的成員函數startActivityLocked。注意,在調用ActivityStack類的成員函數startActivityLocked的時候,Actvitiy組件還處於啓動的過程,即它的窗口尚未顯示出來,不過這時候ActivityManagerService服務會檢查是否需要爲正在啓動的Activity組件顯示一個啓動窗口。如果需要的話,那麼ActivityManagerService服務就會請求WindowManagerService服務爲正在啓動的Activity組件設置一個啓動窗口。這個過程如圖所示。

圖 Activity組件的啓動窗口的顯示過程

這個過程可以分爲6個步驟,接下來我們就詳細分析每一個步驟。

1.1 ActivityStack的startActivityLocked

  1. final void startActivityLocked(ActivityRecord r, boolean newTask,
  2. boolean doResume, boolean keepCurTransition, Bundle options) {
  3. ......
  4. if (!isHomeStack() || numActivities() > 0) {
  5. // We want to show the starting preview window if we are
  6. // switching to a new task, or the next activity's process is
  7. // not currently running.
  8. boolean showStartingIcon = newTask;
  9. ProcessRecord proc = r.app;
  10. if (proc == null) {
  11. proc = mService.mProcessNames.get(r.processName, r.info.applicationInfo.uid);
  12. }
  13. .....
  14. if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
  15. mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, keepCurTransition);//設置app變化相關
  16. mNoAnimActivities.add(r);
  17. } else {
  18. mWindowManager.prepareAppTransition(newTask
  19. ? r.mLaunchTaskBehind
  20. ? AppTransition.TRANSIT_TASK_OPEN_BEHIND
  21. : AppTransition.TRANSIT_TASK_OPEN
  22. : AppTransition.TRANSIT_ACTIVITY_OPEN, keepCurTransition);
  23. mNoAnimActivities.remove(r);
  24. }
  25. mWindowManager.addAppToken(task.mActivities.indexOf(r),//調用WMS增加AppToken
  26. r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
  27. (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId,
  28. r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind);
  29. boolean doShow = true;
  30. if (newTask) {
  31. if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
  32. resetTaskIfNeededLocked(r, r);
  33. doShow = topRunningNonDelayedActivityLocked(null) == r;
  34. }
  35. } else if (options != null && new ActivityOptions(options).getAnimationType()
  36. == ActivityOptions.ANIM_SCENE_TRANSITION) {
  37. doShow = false;
  38. }
  39. if (r.mLaunchTaskBehind) {
  40. mWindowManager.setAppVisibility(r.appToken, true);
  41. ensureActivitiesVisibleLocked(null, 0);
  42. } else if (SHOW_APP_STARTING_PREVIEW && doShow) {
  43. ActivityRecord prev = mResumedActivity;
  44. if (prev != null) {
  45. // We don't want to reuse the previous starting preview if:
  46. // (1) The current activity is in a different task.
  47. if (prev.task != r.task) {
  48. prev = null;
  49. }
  50. // (2) The current activity is already displayed.
  51. else if (prev.nowVisible) {
  52. prev = null;
  53. }
  54. }
  55. mWindowManager.setAppStartingWindow(
  56. r.appToken, r.packageName, r.theme,
  57. mService.compatibilityInfoForPackageLocked(
  58. r.info.applicationInfo), r.nonLocalizedLabel,
  59. r.labelRes, r.icon, r.logo, r.windowFlags,
  60. prev != null ? prev.appToken : null, showStartingIcon);
  61. r.mStartingWindowShown = true;
  62. }
  63. } else {
  64. mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
  65. r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
  66. (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId,
  67. r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind);
  68. ActivityOptions.abort(options);
  69. options = null;
  70. }
  71. ......
  72. if (doResume) {
  73. mStackSupervisor.resumeTopActivitiesLocked(this, r, options);
  74. }
  75. }

 上面這個函數先調用了WMS的addAppToken函數增加一個AppToken。

ActivityStack類的靜態成員變量SHOW_APP_STARTING_PREVIEW是用描述系統是否可以爲正在啓動的Activity組件顯示啓動窗口,只有在它的值等於true,以及正在啓動的Activity組件的窗口接下來是要顯示出來的情況下,即變量doShow的值等於true,ActivityManagerService服務纔會請求WindowManagerService服務爲正在啓動的Activity組件設置啓動窗口。

1.2 WindowManagerService.setAppStartingWindow

setAppStartingWindow函數比較長我們來分段解析:

  1. AppWindowToken wtoken = findAppWindowToken(token);
  2. if (wtoken == null) {
  3. Slog.w(TAG, "Attempted to set icon of non-existing app token: " + token);
  4. return;
  5. }
  6. // If the display is frozen, we won't do anything until the
  7. // actual window is displayed so there is no reason to put in
  8. // the starting window.
  9. if (!okToDisplay()) {
  10. return;
  11. }
  12. if (wtoken.startingData != null) {
  13. return;
  14. }

參數token描述的是要設置啓動窗口的Activity組件,而參數transferFrom描述的是要將啓動窗口轉移給Activity組件token的Activity組件。這兩個Activity組件是運行在同一個任務中的,並且參數token描述的Activity組件Activity組件是正在啓動的Activity組件,而參數transferFrom描述的Activity組件是系統當前激活的Activity組件。
        這段代碼首先調用WindowManagerService類的成員函數findAppWindowToken來獲得與參數token對應的一個類型爲AppWindowToken的窗口令牌wtoken。如果這個AppWindowToken對象的成員變量startingData的值不等於null,那麼就說明參數token所描述的Activity組件已經設置過啓動窗口了,因此,WindowManagerService類的成員函數setAppStartingWindow就不用往下處理了。

函數okToDisplay如下:

  1. boolean okToDisplay() {
  2. return !mDisplayFrozen && mDisplayEnabled && mPolicy.isScreenOn();
  3. }

        這段代碼還會檢查系統屏幕當前是否處於凍結狀態,即WindowManagerService類的成員變量mDisplayFrozen的值是否等於true,或者系統屏幕當前是否處於黑屏狀態,即indowManagerService類的成員變量mPolicy所指向的一個PhoneWindowManager對象的成員函數isScreenOn的返回值是否等於false。以及mDisplayEnabled是否被允許顯示。如果是處於上述三種狀態的話,那麼WindowManagerService類的成員函數setAppStartingWindow就不用往下處理的。因爲在這幾種種狀態下,爲token所描述的Activity組件設置的啓動窗口是無法顯示的。

繼續分析:

  1. if (transferFrom != null) {
  2. AppWindowToken ttoken = findAppWindowToken(transferFrom);
  3. if (ttoken != null) {
  4. WindowState startingWindow = ttoken.startingWindow;
  5. if (startingWindow != null) {//有啓動窗口
  6. // In this case, the starting icon has already been displayed, so start
  7. // letting windows get shown immediately without any more transitions.
  8. mSkipAppTransitionAnimation = true;
  9. if (DEBUG_STARTING_WINDOW) Slog.v(TAG,
  10. "Moving existing starting " + startingWindow + " from " + ttoken
  11. + " to " + wtoken);
  12. final long origId = Binder.clearCallingIdentity();
  13. // Transfer the starting window over to the new token.
  14. wtoken.startingData = ttoken.startingData;
  15. wtoken.startingView = ttoken.startingView;
  16. wtoken.startingDisplayed = ttoken.startingDisplayed;
  17. ttoken.startingDisplayed = false;
  18. wtoken.startingWindow = startingWindow;
  19. wtoken.reportedVisible = ttoken.reportedVisible;
  20. ttoken.startingData = null;
  21. ttoken.startingView = null;
  22. ttoken.startingWindow = null;
  23. ttoken.startingMoved = true;
  24. startingWindow.mToken = wtoken;
  25. startingWindow.mRootToken = wtoken;
  26. startingWindow.mAppToken = wtoken;
  27. startingWindow.getWindowList().remove(startingWindow);
  28. mWindowsChanged = true;
  29. ttoken.windows.remove(startingWindow);
  30. ttoken.allAppWindows.remove(startingWindow);
  31. addWindowToListInOrderLocked(startingWindow, true);
  32. if (ttoken.allDrawn) {
  33. wtoken.allDrawn = true;
  34. wtoken.deferClearAllDrawn = ttoken.deferClearAllDrawn;
  35. }
  36. if (ttoken.firstWindowDrawn) {
  37. wtoken.firstWindowDrawn = true;
  38. }
  39. if (!ttoken.hidden) {
  40. wtoken.hidden = false;
  41. wtoken.hiddenRequested = false;
  42. wtoken.willBeHidden = false;
  43. }
  44. if (wtoken.clientHidden != ttoken.clientHidden) {
  45. wtoken.clientHidden = ttoken.clientHidden;
  46. wtoken.sendAppVisibilityToClients();
  47. }
  48. ttoken.mAppAnimator.transferCurrentAnimation(
  49. wtoken.mAppAnimator, startingWindow.mWinAnimator);
  50. updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
  51. true /*updateInputWindows*/);
  52. getDefaultDisplayContentLocked().layoutNeeded = true;
  53. performLayoutAndPlaceSurfacesLocked();//刷新系統
  54. Binder.restoreCallingIdentity(origId);
  55. return;//結束
  56. } else if (ttoken.startingData != null) {//沒有啓動窗口,但有啓動數據
  57. wtoken.startingData = ttoken.startingData;
  58. ttoken.startingData = null;
  59. ttoken.startingMoved = true;
  60. Message m = mH.obtainMessage(H.ADD_STARTING, wtoken);
  61. mH.sendMessageAtFrontOfQueue(m);
  62. return;//結束
  63. }
  64. final AppWindowAnimator tAppAnimator = ttoken.mAppAnimator;
  65. final AppWindowAnimator wAppAnimator = wtoken.mAppAnimator;
  66. if (tAppAnimator.thumbnail != null) {
  67. if (wAppAnimator.thumbnail != null) {
  68. wAppAnimator.thumbnail.destroy();
  69. }
  70. wAppAnimator.thumbnail = tAppAnimator.thumbnail;
  71. wAppAnimator.thumbnailX = tAppAnimator.thumbnailX;
  72. wAppAnimator.thumbnailY = tAppAnimator.thumbnailY;
  73. wAppAnimator.thumbnailLayer = tAppAnimator.thumbnailLayer;
  74. wAppAnimator.thumbnailAnimation = tAppAnimator.thumbnailAnimation;
  75. tAppAnimator.thumbnail = null;
  76. }
  77. }
  78. }
  79. if (!createIfNeeded) {
  80. return;
  81. }
  82. if (theme != 0) {
  83. AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
  84. com.android.internal.R.styleable.Window, mCurrentUserId);
  85. if (ent == null) {
  86. return;
  87. }
  88. final boolean windowIsTranslucentDefined = ent.array.hasValue(
  89. com.android.internal.R.styleable.Window_windowIsTranslucent);
  90. final boolean windowIsTranslucent = ent.array.getBoolean(
  91. com.android.internal.R.styleable.Window_windowIsTranslucent, false);
  92. final boolean windowSwipeToDismiss = ent.array.getBoolean(
  93. com.android.internal.R.styleable.Window_windowSwipeToDismiss, false);
  94. if (windowIsTranslucent || (!windowIsTranslucentDefined && windowSwipeToDismiss)) {
  95. return;
  96. }
  97. if (ent.array.getBoolean(
  98. com.android.internal.R.styleable.Window_windowIsFloating, false)) {
  99. return;
  100. }
  101. if (ent.array.getBoolean(
  102. com.android.internal.R.styleable.Window_windowShowWallpaper, false)) {
  103. if (mWallpaperTarget == null) {
  104. windowFlags |= FLAG_SHOW_WALLPAPER;
  105. } else {
  106. return;
  107. }
  108. }
  109. }
  110. wtoken.startingData = new StartingData(pkg, theme, compatInfo, nonLocalizedLabel,
  111. labelRes, icon, logo, windowFlags);
  112. Message m = mH.obtainMessage(H.ADD_STARTING, wtoken);
  113. mH.sendMessageAtFrontOfQueue(m);
  114. }

如果參數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組件創建一個新的啓動窗口。

       但是如果啓動窗口和啓動數據都沒有,我們就要新建StartingData(啓動數據),然後發送ADD_STARTING消息。

我們再來看這個消息的處理:

1.3 ADD_STARTING消息處理

ADD_STARTING消息處理肯定是時啓動窗口沒有,所以需要窗口啓動窗口的。

  1. case ADD_STARTING: {
  2. final AppWindowToken wtoken = (AppWindowToken)msg.obj;
  3. final StartingData sd = wtoken.startingData;
  4. if (sd == null) {
  5. // Animation has been canceled... do nothing.
  6. return;
  7. }
  8. View view = null;
  9. try {
  10. view = mPolicy.addStartingWindow(
  11. wtoken.token, sd.pkg, sd.theme, sd.compatInfo,
  12. sd.nonLocalizedLabel, sd.labelRes, sd.icon, sd.logo, sd.windowFlags);
  13. } catch (Exception e) {
  14. Slog.w(TAG, "Exception when adding starting window", e);
  15. }
  16. if (view != null) {
  17. boolean abort = false;
  18. synchronized(mWindowMap) {
  19. if (wtoken.removed || wtoken.startingData == null) {
  20. // If the window was successfully added, then
  21. // we need to remove it.
  22. if (wtoken.startingWindow != null) {
  23. wtoken.startingWindow = null;
  24. wtoken.startingData = null;
  25. abort = true;
  26. }
  27. } else {
  28. wtoken.startingView = view;
  29. }
  30. }
  31. if (abort) {
  32. try {
  33. mPolicy.removeStartingWindow(wtoken.token, view);
  34. } catch (Exception e) {
  35. Slog.w(TAG, "Exception when removing starting window", e);
  36. }
  37. }
  38. }
  39. } break;

參數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服務中去的。

1.4 PhoneWindowManager的addStartingWindow函數

我們再來看看PhoneWindowManager的addStartingWindow函數

  1. public View addStartingWindow(IBinder appToken, String packageName, int theme,
  2. CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
  3. int icon, int logo, int windowFlags) {
  4. if (!SHOW_STARTING_ANIMATIONS) {
  5. return null;
  6. }
  7. if (packageName == null) {
  8. return null;
  9. }
  10. WindowManager wm = null;
  11. View view = null;
  12. try {
  13. Context context = mContext;
  14. if (theme != context.getThemeResId() || labelRes != 0) {
  15. try {
  16. context = context.createPackageContext(packageName, 0);
  17. context.setTheme(theme);
  18. } catch (PackageManager.NameNotFoundException e) {
  19. // Ignore
  20. }
  21. }
  22. PhoneWindow win = new PhoneWindow(context);
  23. win.setIsStartingWindow(true);
  24. final TypedArray ta = win.getWindowStyle();
  25. if (ta.getBoolean(
  26. com.android.internal.R.styleable.Window_windowDisablePreview, false)
  27. || ta.getBoolean(
  28. com.android.internal.R.styleable.Window_windowShowWallpaper,false)) {
  29. return null;
  30. }
  31. Resources r = context.getResources();
  32. win.setTitle(r.getText(labelRes, nonLocalizedLabel));
  33. win.setType(
  34. WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);//注意這個,type是TYPE_APPLICATION_STARTING
  35. synchronized (mWindowManagerFuncs.getWindowManagerLock()) {
  36. // Assumes it's safe to show starting windows of launched apps while
  37. // the keyguard is being hidden. This is okay because starting windows never show
  38. // secret information.
  39. if (mKeyguardHidden) {
  40. windowFlags |= FLAG_SHOW_WHEN_LOCKED;
  41. }
  42. }
  43. win.setFlags(
  44. windowFlags|
  45. WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
  46. WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
  47. WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
  48. windowFlags|
  49. WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
  50. WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
  51. WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
  52. win.setDefaultIcon(icon);
  53. win.setDefaultLogo(logo);
  54. win.setLayout(WindowManager.LayoutParams.MATCH_PARENT,
  55. WindowManager.LayoutParams.MATCH_PARENT);
  56. final WindowManager.LayoutParams params = win.getAttributes();
  57. params.token = appToken;
  58. params.packageName = packageName;
  59. params.windowAnimations = win.getWindowStyle().getResourceId(
  60. com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
  61. params.privateFlags |=
  62. WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
  63. params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
  64. if (!compatInfo.supportsScreen()) {
  65. params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
  66. }
  67. params.setTitle("Starting " + packageName);
  68. wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
  69. view = win.getDecorView();
  70. if (win.isFloating()) {
  71. return null;
  72. }
  73. wm.addView(view, params);
  74. return view.getParent() != null ? view : null;

PhoneWindowManager這個函數就是創建了一個窗口,然後設置type(我們注意上面的type是TYPE_APPLICATION_STARTING代表應用剛啓動) flag layout,最後把這個窗口通過WMS的addWindow函數(這個我們之前的博客分析過)加入WMS中管理。


二. Activity組件的啓動窗口的結束過程

  Activity組件啓動完成之後,它的窗口就會顯示出來,這時候WindowManagerService服務就需要將它的啓動窗口結束掉。我們知道,在WindowManagerService服務中,每一個窗口都對應有一個WindowState對象。每當WindowManagerService服務需要顯示一個窗口的時候,就會調用一個對應的WindowState對象的成員函數performShowLocked。WindowState類的成員函數performShowLocked在執行的過程中,就會檢查當前正在處理的WindowState對象所描述的窗口是否設置有啓動窗口。如果有的話,那麼就會將它結束掉。這個過程如圖所示。

在commitFinishDrawingLocked函數中,而當mAttrs.type == TYPE_APPLICATION_STARTING就會調用performShowLocked函數,前面當我們啓動一個應用的啓動窗口會在PhoneWindowManager的addStartingWindow函數中新建一個窗口,並且type設置爲這個類型。

  1. boolean commitFinishDrawingLocked() {
  2. if (mDrawState != COMMIT_DRAW_PENDING && mDrawState != READY_TO_SHOW) {
  3. return false;
  4. }
  5. mDrawState = READY_TO_SHOW;
  6. final AppWindowToken atoken = mWin.mAppToken;
  7. if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
  8. return performShowLocked();
  9. }
  10. return false;
  11. }

然後commitFinishDrawingLocked又在performLayoutAndPlaceSurfacesLockedInner函數中調用(這個就是WMS刷新的核心函數)。


2.1 WindowStateAnimator的performShowLocked函數

  1. boolean performShowLocked() {
  2. if (mWin.isHiddenFromUserLocked()) {
  3. if (DEBUG_VISIBILITY) Slog.w(TAG, "hiding " + mWin + ", belonging to " + mWin.mOwnerUid);
  4. mWin.hideLw(false);
  5. return false;
  6. }
  7. if (mDrawState == READY_TO_SHOW && mWin.isReadyForDisplayIgnoringKeyguard()) {
  8. if (SHOW_TRANSACTIONS || DEBUG_ORIENTATION)
  9. WindowManagerService.logSurface(mWin, "SHOW (performShowLocked)", null);
  10. mService.enableScreenIfNeededLocked();
  11. applyEnterAnimationLocked();
  12. // Force the show in the next prepareSurfaceLocked() call.
  13. mLastAlpha = -1;
  14. if (DEBUG_SURFACE_TRACE || DEBUG_ANIM)
  15. Slog.v(TAG, "performShowLocked: mDrawState=HAS_DRAWN in " + this);
  16. mDrawState = HAS_DRAWN;
  17. mService.scheduleAnimationLocked();
  18. int i = mWin.mChildWindows.size();
  19. while (i > 0) {
  20. i--;
  21. WindowState c = mWin.mChildWindows.get(i);
  22. if (c.mAttachedHidden) {
  23. c.mAttachedHidden = false;
  24. if (c.mWinAnimator.mSurfaceControl != null) {
  25. c.mWinAnimator.performShowLocked();
  26. // It hadn't been shown, which means layout not
  27. // performed on it, so now we want to make sure to
  28. // do a layout. If called from within the transaction
  29. // loop, this will cause it to restart with a new
  30. // layout.
  31. final DisplayContent displayContent = c.getDisplayContent();
  32. if (displayContent != null) {
  33. displayContent.layoutNeeded = true;
  34. }
  35. }
  36. }
  37. }
  38. if (mWin.mAttrs.type != TYPE_APPLICATION_STARTING
  39. && mWin.mAppToken != null) {
  40. mWin.mAppToken.firstWindowDrawn = true;
  41. if (mWin.mAppToken.startingData != null) {
  42. // If this initial window is animating, stop it -- we
  43. // will do an animation to reveal it from behind the
  44. // starting window, so there is no need for it to also
  45. // be doing its own stuff.
  46. clearAnimation();
  47. mService.mFinishedStarting.add(mWin.mAppToken);
  48. mService.mH.sendEmptyMessage(H.FINISHED_STARTING);
  49. }
  50. mWin.mAppToken.updateReportedVisibilityLocked();
  51. }
  52. return true;
  53. }
  54. return false;
  55. }

對當前正在正在處理的窗口執行以下操作:

        1. 將對應的WindowState對象的成員變量mLastAlpha的值設置爲-1,以便以後在顯示窗口之前,都可以更新窗口的Alpha通道值。

        2. 將對應的WindowState對象的成員變量mHasDrawn的值設置爲true,以便可以表示窗口的UI繪製出來了。

        3. 確保屏幕對WindowManagerService服務是可用的,這是通過調用WindowManagerService類的成員函數enableScreenIfNeededLocked來實現的。系統在啓動完成之前,屏幕是用來顯示開機動畫的,這時候屏幕是被開機動畫佔用的。等到系統啓動完成之後,屏幕就應該是被WindowManagerService服務佔用的,這時候就需要停止顯示開機動畫。WindowManagerService類的成員函數enableScreenIfNeededLocked就是確保開機動畫已經停止顯示。

        4. 給窗口設置一個進入動畫或者顯示動畫,這是通過調用WindowManagerService類的成員函數applyEnterAnimationLocked來實現的。默認是設置爲顯示動畫,但是如果窗口之前是不可見的,那麼就會設置爲進入動畫。

        5. 子窗口處理

    由於當前正在處理的窗口可能有子窗口,因此就需要在顯示完成當前正在處理的窗口之後,繼續將它的子窗口顯示出來。如果一個窗口具有子窗口,那麼這些子窗口就會保存在一個對應的WindowState對象的成員變量mChildWindows所描述的一個ArrayList中。注意,只有那些父窗口上一次是不可見的,並且具有繪圖表面的子窗口才需要顯示。顯示子窗口是通過遞歸調用WindowState類的成員函數performShowLocked來實現的。

        6. 啓動窗口處理

       最後,如果當前正在處理的窗口是一個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就會同時將該動畫結束掉,就會調用clearAnimation即將當前正在處理的WindowState對象的成員變量mAnimation的值設置爲null,但是會將另外一個成員變量mAnimating的值設置爲true,以便可以在其它地方對當前正在處理的窗口的動畫進行清理。

  1. public void clearAnimation() {
  2. if (mAnimation != null) {
  3. mAnimating = true;
  4. mLocalAnimating = false;
  5. mAnimation.cancel();
  6. mAnimation = null;
  7. mKeyguardGoingAwayAnimation = false;
  8. }
  9. }

        還有一個地方需要注意的是,如果當前正在處理的窗口是一個Acitivy組件相關的窗口,那麼WindowState類的成員函數performShowLocked還會調用當前正在處理的WindowState對象的成員變量mAppToken所指向的一個AppWindowToken對象的成員函數updateReportedVisibilityLocked來向ActivityManagerService服務報告該Acitivy組件的可見性。

        接下來,我們就繼續分析在WindowManagerService類內部定義的H類的成員函數sendEmptyMessage的實現,以便可以瞭解Acitivy組件的啓動窗口的結束過程。


2.2 FINISHED_STARTING消息處理

消息的處理是在WMS中:

  1. case FINISHED_STARTING: {
  2. IBinder token = null;
  3. View view = null;
  4. while (true) {
  5. synchronized (mWindowMap) {
  6. final int N = mFinishedStarting.size();
  7. if (N <= 0) {
  8. break;
  9. }
  10. AppWindowToken wtoken = mFinishedStarting.remove(N-1);
  11. if (wtoken.startingWindow == null) {
  12. continue;
  13. }
  14. view = wtoken.startingView;
  15. token = wtoken.token;
  16. wtoken.startingData = null;
  17. wtoken.startingView = null;
  18. wtoken.startingWindow = null;
  19. wtoken.startingDisplayed = false;
  20. }
  21. try {
  22. mPolicy.removeStartingWindow(token, view);
  23. } catch (Exception e) {
  24. Slog.w(TAG, "Exception when removing starting window", e);
  25. }
  26. }
  27. } break;

之前在performShowLocked中會把啓動窗口加入mFinishedStarting隊列,這裏先檢查下mFinishedStarting隊列是否爲空,如果爲空直接退出。不爲空一個一個去除其中的窗口。將啓動窗口的各項數據清空,然後調用PhoneWindowManager的removeStartingWindow函數。


2.3 PhoneWindowManager的removeStartingWindow函數

PhoneWindowManager的removeStartingWindow函數直接到WindowManagerImpl的removeView中了。

  1. public void removeStartingWindow(IBinder appToken, View window) {
  2. if (window != null) {
  3. WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
  4. wm.removeView(window);
  5. }
  6. }

WindowManagerImpl的removeView函數直接調用了WindowManagerGlobal的removeView函數了

  1. @Override
  2. public void removeView(View view) {
  3. mGlobal.removeView(view, false);
  4. }
WindowManagerGlobal的removeView函數調用了removeViewLocked函數。
  1. public void removeView(View view, boolean immediate) {
  2. if (view == null) {
  3. throw new IllegalArgumentException("view must not be null");
  4. }
  5. synchronized (mLock) {
  6. int index = findViewLocked(view, true);
  7. View curView = mRoots.get(index).getView();
  8. removeViewLocked(index, immediate);
  9. if (curView == view) {
  10. return;
  11. }
  12. throw new IllegalStateException("Calling with view " + view
  13. + " but the ViewAncestor is attached to " + curView);
  14. }
  15. }

這個函數主要是調用了ViewRootImpl的die函數。

  1. private void removeViewLocked(int index, boolean immediate) {
  2. ViewRootImpl root = mRoots.get(index);
  3. View view = root.getView();
  4. if (view != null) {
  5. InputMethodManager imm = InputMethodManager.getInstance();
  6. if (imm != null) {
  7. imm.windowDismissed(mViews.get(index).getWindowToken());
  8. }
  9. }
  10. boolean deferred = root.die(immediate);
  11. if (view != null) {
  12. view.assignParent(null);
  13. if (deferred) {
  14. mDyingViews.add(view);
  15. }
  16. }
  17. }


2.4 ViewRootImpl的die函數

這裏傳入的參數immediate是false,所以直接發送了MSG_DIE信號。

  1. boolean die(boolean immediate) {
  2. // Make sure we do execute immediately if we are in the middle of a traversal or the damage
  3. // done by dispatchDetachedFromWindow will cause havoc on return.
  4. if (immediate && !mIsInTraversal) {
  5. doDie();
  6. return false;
  7. }
  8. if (!mIsDrawing) {
  9. destroyHardwareRenderer();
  10. } else {
  11. Log.e(TAG, "Attempting to destroy the window while drawing!\n" +
  12. " window=" + this + ", title=" + mWindowAttributes.getTitle());
  13. }
  14. mHandler.sendEmptyMessage(MSG_DIE);
  15. return true;
  16. }

MSG_DIE信號的處理直接是調用了doDie函數。

  1. case MSG_DIE:
  2. doDie();
  3. break;

我們來看下ViewRootImpl的doDie函數

  1. void doDie() {
  2. synchronized (this) {
  3. if (mRemoved) {
  4. return;
  5. }
  6. mRemoved = true;
  7. if (mAdded) {
  8. dispatchDetachedFromWindow();
  9. }
  10. if (mAdded && !mFirst) {
  11. destroyHardwareRenderer();
  12. if (mView != null) {
  13. int viewVisibility = mView.getVisibility();
  14. boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
  15. if (mWindowAttributesChanged || viewVisibilityChanged) {
  16. // If layout params have been changed, first give them
  17. // to the window manager to make sure it has the correct
  18. // animation info.
  19. try {
  20. if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
  21. & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
  22. mWindowSession.finishDrawing(mWindow);
  23. }
  24. } catch (RemoteException e) {
  25. }
  26. }
  27. mSurface.release();
  28. }
  29. }
  30. mAdded = false;
  31. }
  32. WindowManagerGlobal.getInstance().doRemoveView(this);
  33. }

我們先看WindowManagerGlobal的doRemoveView函數只是去除一些變量而已

  1. void doRemoveView(ViewRootImpl root) {
  2. synchronized (mLock) {
  3. final int index = mRoots.indexOf(root);
  4. if (index >= 0) {
  5. mRoots.remove(index);
  6. mParams.remove(index);
  7. final View view = mViews.remove(index);
  8. mDyingViews.remove(view);
  9. }
  10. }
  11. if (HardwareRenderer.sTrimForeground && HardwareRenderer.isAvailable()) {
  12. doTrimForeground();
  13. }
  14. }

 ViewRootImpl類有一個成員變量mAdded,當它的值等於true的時候,就表示當前正在處理的ViewRoot對象有一個關聯的View對象,因此,這時候就可以調用另外一個成員函數dispatchDetachedFromWindow來刪除這個View對象。由於刪除了這個View對象之後,當前正在處理的ViewRootImpl對象就不再關聯有View對象了,因此,ViewRootImpl類的成員函數doDie在調用另外一個成員函數dispatchDetachedFromWindow之前,還會將成員變量mAdded的值設置爲false。

  1. void dispatchDetachedFromWindow() {
  2. ......
  3. try {
  4. mWindowSession.remove(mWindow);
  5. } catch (RemoteException e) {
  6. }
  7. ......
  8. }

每一個與UI相關的應用程序進程,都與WindowManagerService服務建立有一個連接,這個連接是通過一個實現了IWindowSession接口的Binder代理對象來描述的,並且這個Binder代理對象就保存在ViewRoot類的靜態成員變量sWindowSession中,它引用了運行在WindowManagerService服務這一側的一個類型爲Session的Binder本地對象。

        注意,由於當前進程即爲WindowManagerService服務所運行在的進程,因此,這時候ViewRootImpl類的靜態成員變量sWindowSession保存的其實不是一個實現了IWindowSession接口的Binder代理對象,而是一個實現了IWindowSession接口的類型爲Session的Binder本地對象。這是因爲Binder驅動發現Client和Service是位於同一個進程的時候,就會將Service的本地接口直接返回給Client,而不會將Service的代理對象返回給Client,這樣就可以避免在同一進程中執行Binder進程間調用也會經過Binder驅動來中轉。

進程中的每一個窗口都有一個對應的W對象,這個W對象就保存在ViewRootImpl類的成員變量mWindow中。有了這個W對象之後,ViewRootImpl類的成員函數dispatchDetachedFromWindow就可以調用靜態成員變量sWindowSession所描述的一個Session對象的成員函數remove來通知WindowManagerService服務刪除一個對應的WindowState對象。從前面的調用過程可以知道,這個WindowState對象描述的是一個Activity組件的啓動窗口,因此,WindowManagerService服務刪除了這個WindowState對象之後,就相當於是將一個Activity組件的啓動窗口結束掉了。

        接下來,我們就繼續分析Session類的成員函數remove的實現,以便可以瞭解Activity組件的啓動窗口的結束過程。

  1. final class Session extends IWindowSession.Stub
  2. implements IBinder.DeathRecipient {
  3. ......
  4. public void remove(IWindow window) {
  5. mService.removeWindow(this, window);
  6. }


2.5 WMS的removeWindow函數

最後到了WMS的removeWindow中。找到其相關的windowState,然後調用removeWindowLocked函數。

  1. public void removeWindow(Session session, IWindow client) {
  2. synchronized(mWindowMap) {
  3. WindowState win = windowForClientLocked(session, client, false);
  4. if (win == null) {
  5. return;
  6. }
  7. removeWindowLocked(win);
  8. }
  9. }

removeWindowLocked函數

  1. void removeWindowLocked(WindowState win) {
  2. final boolean startingWindow = win.mAttrs.type == TYPE_APPLICATION_STARTING;
  3. final long origId = Binder.clearCallingIdentity();
  4. win.disposeInputChannel();
  5. boolean wasVisible = false;
  6. if (win.mHasSurface && okToDisplay()) {
  7. // If we are not currently running the exit animation, we
  8. // need to see about starting one.
  9. wasVisible = win.isWinVisibleLw();
  10. if (wasVisible) {
  11. final int transit = (!startingWindow)
  12. ? WindowManagerPolicy.TRANSIT_EXIT
  13. : WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
  14. // Try starting an animation.
  15. if (win.mWinAnimator.applyAnimationLocked(transit, false)) {
  16. win.mExiting = true;
  17. }
  18. if (mAccessibilityController != null
  19. && win.getDisplayId() == Display.DEFAULT_DISPLAY) {
  20. mAccessibilityController.onWindowTransitionLocked(win, transit);
  21. }
  22. }
  23. final AppWindowToken appToken = win.mAppToken;
  24. final boolean isAnimating = win.mWinAnimator.isAnimating();
  25. final boolean lastWinStartingNotAnimating = startingWindow && appToken!= null
  26. && appToken.allAppWindows.size() == 1 && !isAnimating;
  27. if (!lastWinStartingNotAnimating && (win.mExiting || isAnimating)) {
  28. // The exit animation is running... wait for it!
  29. win.mExiting = true;
  30. win.mRemoveOnExit = true;
  31. final DisplayContent displayContent = win.getDisplayContent();
  32. if (displayContent != null) {
  33. displayContent.layoutNeeded = true;
  34. }
  35. final boolean focusChanged = updateFocusedWindowLocked(
  36. UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/);
  37. performLayoutAndPlaceSurfacesLocked();
  38. if (appToken != null) {
  39. appToken.updateReportedVisibilityLocked();
  40. }
  41. if (focusChanged) {
  42. mInputMonitor.updateInputWindowsLw(false /*force*/);
  43. }
  44. Binder.restoreCallingIdentity(origId);
  45. return;
  46. }
  47. }
  48. removeWindowInnerLocked(win);
  49. ......
  50. updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
  51. ......
  52. }

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組件的啓動窗口的結束過程。


最後我們來看下WMS的removeWindowInnerLocked函數

  1. void removeWindowInnerLocked(WindowState win, boolean performLayout) {
  2. if (win.mRemoved) {
  3. // Nothing to do.
  4. return;
  5. }
  6. for (int i=win.mChildWindows.size()-1; i>=0; i--) {//處理子窗口
  7. WindowState cwin = win.mChildWindows.get(i);
  8. Slog.w(TAG, "Force-removing child win " + cwin + " from container "
  9. + win);
  10. removeWindowInnerLocked(cwin);
  11. }
  12. win.mRemoved = true;//設置變量
  13. if (mInputMethodTarget == win) {
  14. moveInputMethodWindowsIfNeededLocked(false);
  15. }
  16. if (false) {
  17. RuntimeException e = new RuntimeException("here");
  18. e.fillInStackTrace();
  19. Slog.w(TAG, "Removing window " + win, e);
  20. }
  21. mPolicy.removeWindowLw(win);
  22. win.removeLocked();
  23. mWindowMap.remove(win.mClient.asBinder());
  24. if (win.mAppOp != AppOpsManager.OP_NONE) {
  25. mAppOps.finishOp(win.mAppOp, win.getOwningUid(), win.getOwningPackage());
  26. }
  27. mPendingRemove.remove(win);
  28. mResizingWindows.remove(win);
  29. mWindowsChanged = true;
  30. if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Final remove of window: " + win);
  31. if (mInputMethodWindow == win) {
  32. mInputMethodWindow = null;
  33. } else if (win.mAttrs.type == TYPE_INPUT_METHOD_DIALOG) {
  34. mInputMethodDialogs.remove(win);
  35. }
  36. final WindowToken token = win.mToken;
  37. final AppWindowToken atoken = win.mAppToken;
  38. if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Removing " + win + " from " + token);
  39. token.windows.remove(win);
  40. if (atoken != null) {
  41. atoken.allAppWindows.remove(win);
  42. }
  43. if (localLOGV) Slog.v(
  44. TAG, "**** Removing window " + win + ": count="
  45. + token.windows.size());
  46. if (token.windows.size() == 0) {
  47. if (!token.explicit) {
  48. mTokenMap.remove(token.token);
  49. } else if (atoken != null) {
  50. atoken.firstWindowDrawn = false;
  51. }
  52. }
  53. if (atoken != null) {
  54. if (atoken.startingWindow == win) {
  55. if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Notify removed startingWindow " + win);
  56. scheduleRemoveStartingWindowLocked(atoken);
  57. } else
  58. if (atoken.allAppWindows.size() == 0 && atoken.startingData != null) {
  59. // If this is the last window and we had requested a starting
  60. // transition window, well there is no point now.
  61. if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Nulling last startingWindow");
  62. atoken.startingData = null;
  63. } else if (atoken.allAppWindows.size() == 1 && atoken.startingView != null) {
  64. // If this is the last window except for a starting transition
  65. // window, we need to get rid of the starting transition.
  66. scheduleRemoveStartingWindowLocked(atoken);
  67. }
  68. }
  69. if (win.mAttrs.type == TYPE_WALLPAPER) {
  70. mLastWallpaperTimeoutTime = 0;
  71. getDefaultDisplayContentLocked().pendingLayoutChanges |=
  72. WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
  73. } else if ((win.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
  74. getDefaultDisplayContentLocked().pendingLayoutChanges |=
  75. WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
  76. }
  77. final WindowList windows = win.getWindowList();
  78. if (windows != null) {
  79. windows.remove(win);
  80. if (!mInLayout) {
  81. assignLayersLocked(windows);
  82. final DisplayContent displayContent = win.getDisplayContent();
  83. if (displayContent != null) {
  84. displayContent.layoutNeeded = true;
  85. }
  86. if (performLayout) {
  87. performLayoutAndPlaceSurfacesLocked();
  88. }
  89. if (win.mAppToken != null) {
  90. win.mAppToken.updateReportedVisibilityLocked();
  91. }
  92. }
  93. }
  94. mInputMonitor.updateInputWindowsLw(true /*force*/);
  95. }

由於參數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組件的啓動窗口的啓動過程和結束過程了。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章