Android6.0 WMS(九) WMS切換Activity窗口(App Transition)的過程分析



在Android系統中,同一時刻只有一個Activity組件是處於激活狀態的,因此,當ActivityManagerService服務激活了一個新的Activity組件時,它就需要通知WindowManagerService服務將該Activity組件的窗口顯示出來,這會涉及到將焦點和屏幕等資源從前一個激活的Activity組件切換到後一個激活的Activity組件的過程,本文就詳細分析這個過程。

         Activity窗口的切換操作是在新激活的Activity組件的啓動過程進行的。具體來說,就是在前一個激活的Activity組件進入到Paused狀態並且新激活的Activity組件進之到Resumed 狀態之後,將前一個激活的Activity組件的窗口設置爲不可見,以及將新激活的Activity組件的窗口設置爲可見。整個切換過程是需要在ActivityManagerService服務和WindowManagerService服務的協作之下進行的,如圖1所示。

圖1 Activity窗口的切換操作示意圖

 WindowManagerService服務在執行Activity窗口的切換操作的時候,會給參與切換操作的Activity組件的設置一個動畫,以便可以向用戶展現一個Activity組件切換效果,從而提高用戶體驗。事實上,一個Activity窗口在由不可見狀態切換到可見狀態的過程中,除了會被設置一個Activity組件切換動畫之外,還有被設置一個窗口進入動畫,此外,如果該Activity窗口是附加在另外一個窗口之上的,並且這個被附加的窗口正在顯示一個動畫,那麼這個動畫也同時會被設置給到該Activity窗口的顯示過程中去。本文主要是關注Activity窗口的切換操作,在接下來的一篇文章中分析窗口的動畫框架時,我們再詳細分析上述三種動畫是如何作用在窗口的顯示過程中的。

ActivityManagerService服務在啓動一個Activity組件的過程中,會調用到ActivityStack類的成員函數startActivityLocked。ActivityStack類的成員函數startActivityLocked首先會給正在啓動的Activity組件準備一個切換操作,接着再調用其它的成員函數來通知前一個激活的Activity組件進入到Paused狀態。等到前一個激活的Activity組件進入到Paused狀態之後,ActivityManagerService服務就會檢查用來運行正在啓動的Activity組件的進程啓動起來了沒有。如果這個進程還沒有啓動,那麼ActivityManagerService服務就會將該進程啓動起來,然後再調用ActivityStack類的成員函數realStartActivityLocked來將正在啓動的Activity組件加載起來,並且將它的狀態設置爲Resumed,最後通知WindowManagerService服務執行前面所準備的切換操作。

        接下來,我們就從ActivityStack類的成員函數startActivityLocked開始分析Activity窗口的切換過程,如圖2所示。

圖2 Activity窗口的切換過程


ActivityStack的startActivityLocked函數

我們先看看ActivityStack的startActivityLocked函數

  1. final void startActivityLocked(ActivityRecord r, boolean newTask,
  2. boolean doResume, boolean keepCurTransition, Bundle options) {
  3. ......
  4. // Slot the activity into the history stack and proceed
  5. if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Adding activity " + r + " to stack to task " + task,
  6. new RuntimeException("here").fillInStackTrace());
  7. task.addActivityToTop(r);
  8. task.setFrontOfTask();
  9. r.putInHistory();
  10. if (!isHomeStack() || numActivities() > 0) {
  11. ......
  12. if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
  13. mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, keepCurTransition);
  14. mNoAnimActivities.add(r);
  15. } else {
  16. mWindowManager.prepareAppTransition(newTask
  17. ? r.mLaunchTaskBehind
  18. ? AppTransition.TRANSIT_TASK_OPEN_BEHIND
  19. : AppTransition.TRANSIT_TASK_OPEN
  20. : AppTransition.TRANSIT_ACTIVITY_OPEN, keepCurTransition);
  21. mNoAnimActivities.remove(r);
  22. }
  23. mWindowManager.addAppToken(task.mActivities.indexOf(r),
  24. r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
  25. (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId,
  26. r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind);
  27. ......
  28. if (doResume) {
  29. mStackSupervisor.resumeTopActivitiesLocked(this, r, options);
  30. }
  31. }

      當Activity組件的一個Intent對象的成員函數getFlags返回的一個標誌值的Intent.FLAG_ACTIVITY_NO_ANIMATION位可能會不等於0,這意味着正在啓動的Activity組件不需要顯示切換動畫。在這種情況下,ActivityManagerSerivce服務就會通知WindowManagerService服務不需要準備一個Activity組件切換操作,這是通過以WindowManagerPolicy.TRANSIT_NONE爲參數來調用ActivityStack類的成員變量mService所指向的一個ActivityManagerService對象的成員變量mWindowManager所描述的一 個WindowManagerService對象的成員函數prepareAppTransition來實現的。

      當系統需要執行一個Activity組件切換操作,即需要WindowManagerService服務在顯示正在啓動的Activity組件的窗口時應用一個切換動畫,那麼這個動畫的類型也是有講究的。具體來說,如果參數r描述的是Activity組件是需要在在一個新的任務中啓動的,即參數newTask的值等於true,那麼切換動畫的類型就指定爲WindowManagerPolicy.TRANSIT_TASK_OPEN,否則的話,切換動畫的類型就指定爲WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN。

     無論如何,最終指定的切換動畫的類型都是通過調用ActivityStack類的成員變量mService所指向的一個ActivityManagerService對象的成員變量mWindowManager所描述的一個WindowManagerService對象的成員函數prepareAppTransition來通知WindowManagerService服務的。

        ActivityStack類的成員函數startActivityLocked通知WindowManagerService服務準備好一個Activity組件切換操作之後,如果參數doResume的值等於true,那麼它就會繼續調用另外一個成員函數resumeTopActivityLocked來繼續執行啓動參數r所描述的一個Activity組件的操作。

        接下來,我們就首先分析WindowManagerService類的成員函數prepareAppTransition的實現,以便可以瞭解WindowManagerService服務是如何準備一個Activity組件切換操作的,然後再回過頭來分析ActivityStack類的成員函數resumeTopActivityLocked是如何繼續執行啓動參數r所描述的一個Activity組件的操作的。


WMS的prepareAppTransition函數

下面我們就來分析下WMS的prepareAppTransition函數:

  1. public void prepareAppTransition(int transit, boolean alwaysKeepCurrent) {
  2. ......
  3. synchronized(mWindowMap) {
  4. if (!mAppTransition.isTransitionSet() || mAppTransition.isTransitionNone()) {
  5. mAppTransition.setAppTransition(transit);
  6. } else if (!alwaysKeepCurrent) {
  7. if (transit == AppTransition.TRANSIT_TASK_OPEN
  8. && mAppTransition.isTransitionEqual(
  9. AppTransition.TRANSIT_TASK_CLOSE)) {
  10. // Opening a new task always supersedes a close for the anim.
  11. mAppTransition.setAppTransition(transit);
  12. } else if (transit == AppTransition.TRANSIT_ACTIVITY_OPEN
  13. && mAppTransition.isTransitionEqual(
  14. AppTransition.TRANSIT_ACTIVITY_CLOSE)) {
  15. // Opening a new activity always supersedes a close for the anim.
  16. mAppTransition.setAppTransition(transit);
  17. }
  18. }
  19. if (okToDisplay() && mAppTransition.prepare()) {
  20. mSkipAppTransitionAnimation = false;
  21. }
  22. if (mAppTransition.isTransitionSet()) {
  23. mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
  24. mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, 5000);
  25. }
  26. }
  27. }

WindowManagerService類的成員變量mAppTransition描述的就是WindowManagerService服務接下來要執行一個Activity組件切換操作的類型,也就是要執行的一個Activity組件切換動畫的類型。WindowManagerService類的成員函數prepareAppTransition按照以下規則來設置WindowManagerService服務接下來要執行的Activity組件切換動畫的類型:

        1. 當 WindowManagerService類的成員變量mAppTransition的值等於WindowManagerPolicy.TRANSIT_UNSET或者WindowManagerPolicy.TRANSIT_NONE的時候,就說明WindowManagerService服務接下來沒有Activity組件切換動畫等待執行的,這時候參數transit所描述的Activity組件切換動畫就可以作爲WindowManagerService服務接下來要執行的Activity組件切換動畫。

        2. 當WindowManagerService類的成員變量mAppTransition的值等於WindowManagerPolicy.TRANSIT_TASK_CLOSE,那麼就說明WindowManagerService服務接下來要執行一個關閉Activity組件任務的切換動畫等待執行的。在這種情況下,如果參數transit所描述的是一個打開Activity組件任務的切換動畫,即它的值等於WindowManagerPolicy.TRANSIT_TASK_OPEN,那麼就需要將WindowManagerService服務接下來要執行的Activity組件切換動畫爲打開Activity組件任務類型的。這是因爲打開Activity組件任務的切換動畫的優先級高於關閉Activity組件任務的切換動畫。

        3. 當WindowManagerService類的成員變量mAppTransition的值等於WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE,那麼就說明WindowManagerService服務接下來要執行一個關閉Activity組件的切換動畫等待執行的。在這種情況下,如果參數transit所描述的是一個打開Activity組件的切換動畫,即它的值等於WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN,那麼就需要將WindowManagerService服務接下來要執行的Activity組件切換動畫爲打開Activity組件類型的。這是因爲打開Activity組件的切換動畫的優先級高於關閉Activity組件的切換動畫。

         mSkipAppTransitionAnimation表示WindowManagerService服務是否需要不執行Activity組件的切換動畫?

        最後,WindowManagerService類的成員函數prepareAppTransition還會調用成員變量mH所描述的一個H對象的成員函數sendMessageDelayed來向WindowManagerService服務所運行在的線程發送一個類型爲APP_TRANSITION_TIMEOUT的消息。這個消息將在5秒後被執行,是用來強制前面所設置的Activity組件切換動畫要在5秒之內執行完成的,否則的話,WindowManagerService服務就會認爲該切換動畫執行超時了。

  1. case APP_TRANSITION_TIMEOUT: {
  2. synchronized (mWindowMap) {
  3. if (mAppTransition.isTransitionSet() || !mOpeningApps.isEmpty()
  4. || !mClosingApps.isEmpty()) {
  5. mAppTransition.setTimeout();//設置timeout狀態
  6. performLayoutAndPlaceSurfacesLocked();//時間到了強制刷新
  7. }
  8. }
  9. break;
  10. }

setTimeout只是設置了一個狀態

  1. void setTimeout() {
  2. mAppTransitionState = APP_STATE_TIMEOUT;
  3. }

而setAppTransition是設置Activity下一個切換類型

  1. void setAppTransition(int transit) {
  2. mNextAppTransition = transit;
  3. }


 ActivityStackSupervisor的realStartActivityLocked函數

 這一步執行完成之後,WindowManagerService服務接下來要執行的Activity組件切換操作或者說切換動畫就準備完成了。注意,這時候只是準備好Activity組件切換動畫,但是這個切換動畫還不能執行,要等到前一個激活的Activity組件進入到Paused狀態並且接下來正在啓動的Activity組件進入到Resumed狀態之後才能執行。

        回到前面的Step 1中,即ActivityStack類的成員函數startActivityLocked,接下來它就會調用另外一個成員函數resumeTopActivityLocked來繼續啓動指定的Activity組件。ActivityStack類的成員函數resumeTopActivityLocked以及接下來要調用的其它成員函數就是執行以下兩個操作:

        1. 通知當前處於激活狀態的Activity組件所運行在的應用程序進程,它所運行的一個Activity組件要由Resumed狀態進入到Paused狀態了。

        2. 檢查用來運行當前正在啓動的Activity組件的應用程序進程是否已經啓動起來了。如果已經啓動起來,那麼就會直接通知該應用程序進程將正在啓動的Activity組件加載起來,否則的話,就會先將該應用程序進程啓動起來,然後再通知它將正在啓動的Activity組件加載起來。

        在第2步中,通知相應的應用程序進程將正在啓動的Activity組件加載起來是通過調用ActivityStackSupervisor類的成員函數realStartActivityLocked來實現的,接下來我們就繼續分析這個成員函數的實現,以便可以繼續瞭解Activity組件的切換操作的執行過程。

  1. final boolean realStartActivityLocked(ActivityRecord r,
  2. ProcessRecord app, boolean andResume, boolean checkConfig)
  3. throws RemoteException {
  4. if (andResume) {
  5. r.startFreezingScreenLocked(app, 0);
  6. mWindowManager.setAppVisibility(r.appToken, true);
  7. // schedule launch ticks to collect information about slow apps.
  8. r.startLaunchTickingLocked();
  9. }
  10. try {
  11. ......
  12. app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
  13. System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
  14. new Configuration(stack.mOverrideConfig), r.compat, r.launchedFromPackage,
  15. task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,
  16. newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);
  17. ......
  18. } catch (RemoteException e) {
  19. .....
  20. }
  21. ......
  22. if (andResume) {
  23. stack.minimalResumeActivityLocked(r);
  24. } else {
  25. r.state = STOPPED;
  26. r.stopped = true;
  27. }
  28. ......
  29. return true;
  30. }

上面這個函數主要有如下步驟:

1. 通知WindowManagerService服務將參數r所描述的Activity組件的可見性設置爲true,這是通過調用ActivityStack類的成員變量mService所指向的一個ActivityManagerService對象的成員變量mWindowManager所描述的一個WindowManagerService對象的成員函數setAppVisibility來實現的。

2. 通知參數r所描述的Activity組件所運行在的應用程序進程將它加載起來,這是通過調用參數app所指向的一個ProcessRecord對象的成員變量thread所描述的一個類型爲ApplictionThread的Binder代理對象的成員函數scheduleLaunchActivity來實現的。

3. 當參數andResume的值等於true的時候,就表示在執行第2步時,參數r所描述的Activity組件所運行在的應用程序進程已經將它的狀態設置爲Resumed了,即已經調用過它的成員函數onResume了。在這種情況,ActivityManagerService服務也需要將該Activity組件的狀態設置爲Resumed了,即將r所指向的一個ActivityRecord對象的成員變量state的值設置爲ActivityState.RESUMED,並且將ActivityStack類的成員變量mResumedActivity的值設置爲r,以便表示當前激活的Activity組件爲參數r所描述的Activity組件。最後,ActivityManagerService服務還需要調用ActivityStack類的成員函數minimalResumeActivityLocked函數。而minimalResumeActivityLocked主要調用了completeResumeLocked函數。completeResumeLocked函數來通知WindowManagerService服務執行在前面Step 2所準備好的Activity組件切換操作。


        接下來,我們首先分析WindowManagerService類的成員函數setAppVisibility的實現,以便可以瞭解WindowManagerService服務是如何設置一個Activity組件的可見性的,接着再分析ActivityStack類的成員函數completeResumeLocked的實現,以便可以瞭解ActivityManagerService服務是如何通知WindowManagerService服務執行前面所準備好的一個Activity組件切換操作的。


WMS的setAppVisibility函數

  1. public void setAppVisibility(IBinder token, boolean visible) {
  2. ......
  3. AppWindowToken wtoken;
  4. synchronized(mWindowMap) {
  5. wtoken = findAppWindowToken(token);//找到APPWindowToken
  6. if (wtoken == null) {
  7. Slog.w(TAG, "Attempted to set visibility of non-existing app token: " + token);
  8. return;
  9. }
  10. mOpeningApps.remove(wtoken);
  11. mClosingApps.remove(wtoken);
  12. wtoken.waitingToShow = false;
  13. wtoken.hiddenRequested = !visible;
  14. // If we are preparing an app transition, then delay changing
  15. // the visibility of this token until we execute that transition.
  16. if (okToDisplay() && mAppTransition.isTransitionSet()) {
  17. if (!wtoken.mAppAnimator.usingTransferredAnimation &&
  18. (!wtoken.startingDisplayed || mSkipAppTransitionAnimation)) {
  19. wtoken.mAppAnimator.setDummyAnimation();
  20. }
  21. wtoken.inPendingTransaction = true;
  22. if (visible) {
  23. mOpeningApps.add(wtoken);//把要打開的應用的APPWindowToken放入mOpeningApps中
  24. wtoken.startingMoved = false;
  25. wtoken.mEnteringAnimation = true;
  26. if (wtoken.hidden) {
  27. wtoken.allDrawn = false;
  28. wtoken.deferClearAllDrawn = false;
  29. wtoken.waitingToShow = true;
  30. if (wtoken.clientHidden) {
  31. wtoken.clientHidden = false;
  32. wtoken.sendAppVisibilityToClients();
  33. }
  34. }
  35. } else {
  36. mClosingApps.add(wtoken);
  37. wtoken.mEnteringAnimation = false;
  38. }
  39. if (mAppTransition.getAppTransition() == AppTransition.TRANSIT_TASK_OPEN_BEHIND) {
  40. // We're launchingBehind, add the launching activity to mOpeningApps.
  41. final WindowState win =
  42. findFocusedWindowLocked(getDefaultDisplayContentLocked());
  43. if (win != null) {
  44. final AppWindowToken focusedToken = win.mAppToken;
  45. if (focusedToken != null) {
  46. focusedToken.hidden = true;
  47. mOpeningApps.add(focusedToken);
  48. }
  49. }
  50. }
  51. return;
  52. }
  53. .....
  54. setTokenVisibilityLocked(wtoken, null, visible, AppTransition.TRANSIT_UNSET,//設置
  55. true, wtoken.voiceInteraction);
  56. wtoken.updateReportedVisibilityLocked();
  57. .....
  58. }
  59. }

        每一個Activity組件在WindowManagerService服務內部都對應有一個AppWindowToken對象,用來描述該Activity組件的窗口在WindowManagerService服務中的狀態。因此,WindowManagerService類的成員函數setAppVisibility就先通過調用成員函數findAppWindowToken來找到與參數token所描述的一個Activity組件所對應的一個AppWindowToken對象wtoken,以便接下來可以設置它的狀態。

        注意,WindowManagerService類的成員函數setAppVisibility在修改參數token所描述的一個Activity組件的可見性的時候,需要考慮WindowManagerService服務接下來是否需要執行一個Activity組件操作,即WindowManagerService類的成員變量mNextAppTransition的值是否等於WindowManagerPolicy.TRANSIT_UNSET。如果是TRANSIT_UNSET的話,只要簡單地執行以下兩個操作即可。

        1. 調用WindowManagerService類的成員函數setTokenVisibilityLocked來將參數token所描述的Activity組件的可見性設置爲參數visible所描述的值;(這個函數還會設置動畫,當有改變的時候還會調用performLayoutAndPlaceSurfacesLocked刷新系統)

        2. 調用AppWindowToken對象wtoken的成員函數updateReportedVisibilityLocked來向ActivityManagerService服務報告參數token所描述的Activity組件的可見性。

如果不是TRANSIT_UNSET,也是就是已經設置了Transit,那麼參數token所描述的Activity組件就是正在等待執行切換操作的Activity組件,這時候修改的可見性就會複雜一些:

        我們假設WindowManagerService服務接下來需要執行一個Activity組件操作,即WindowManagerService類的成員變量mNextAppTransition的值等於WindowManagerPolicy.TRANSIT_UNSET,這時候還需要滿足okToDisplay函數即:

        1. 屏幕當前不是處於凍結狀態,即WindowManagerService類的成員變量mDisplayFrozen的值等於false;

        2. 屏幕當前是點亮的,即WindowManagerService類的成員變量mPolicy所指向的一個PhoneWindowManager對象的成員函數isScreenOn的返回值等於true;

         那麼WindowManagerService類的成員函數setAppVisibility接下來纔會開始修改參數token所描述的一個Activity組件的可見性,即:

        1. 將AppWindowToken對象wtoken的成員變量hiddenRequested的值設置爲參數visible的相反值。也就是說,如果參數token所描述的Activity組件是可見的,那麼就將AppWindowToken對象wtoken的成員變量hiddenRequested的值設置爲false,否則的話,就設置爲true。

        2. 調用AppWindowToken對象wtoken的成員函數setDummyAnimation來給參數token所描述的Activity組件設置一個啞動畫。注意,要等到執行參數token所描述的Activity組件的切換操作時,WindowManagerService服務纔會給該Activity組件設置一個合適的切換動畫。

        3. 分別將AppWindowToken對象wtoken從WindowManagerService類的成員變量mOpeningApps和mClosingApps所描述的兩個ArrayList中刪除。注意,WindowManagerService類的成員變量mOpeningApps和mClosingApps保存的分別是系統當前正在打開和關閉的Activity組件,後面會根據參數visible的值來決定參數token所描述的Activity組件是正在打開的還是正在關閉的,以便可以將它放在WindowManagerService類的成員變量mOpeningApps或者mClosingApps中。

        4. 將AppWindowToken對象wtoken的成員變量waitingToShow和waitingToHide的值都初始化爲false,表示參數token所描述的Activity組件既不是正在等待顯示的,也不是正在等待隱藏的,這兩個成員變量的值同樣是要根據參數visible的值來設置的。

        5. 將AppWindowToken對象wtoken的成員變量inPendingTransaction的值設置爲true,表示參數token所描述的Activity組件正在等待切換。

        接下來的操作取決於參數visible的值是true還是false。

        假設參數visible的值等於true,那麼就表示要將參數token所描述的Activity組件設置可見,這時候WindowManagerService類的成員函數setAppVisibility會繼續執行以下操作:

        1. 將AppWindowToken對象wtoken添加WindowManagerService類的成員變量mOpeningApps所描述的一個ArrayList中,表示參數token所描述的Activity組件是正在打開的。

        2. 將AppWindowToken對象wtoken的成員變量startingDisplayed和startingMoved的值都設置爲false,表示參數token所描述的Activity組件的啓動窗口還沒有顯示出來,以及也沒有被轉移給其它的Activity組件。

        3. 如果AppWindowToken對象wtoken的成員變量hidden的值等於true,那麼就意味着參數token所描述的Activity組件當前是不可見的。由於在這種情況下,參數token所描述的Activity組件正在等待打開,因此,該Activity組件的窗口一定是還沒有繪製出來,並且正在等待繪製以及顯示出來,這時候就需要將AppWindowToken對象wtoken的成員變量allDrawn和waitingToShow的值分別設置爲false和true。

        4.  如果AppWindowToken對象wtoken的成員變量hidden的值等於true,並且另外一個成員變量clientHidden的值也等於true,那麼就說明在應用程序進程這一側看來,參數token所描述的Activity組件是不可見的,這時候就需要讓該應用程序進程認爲參數token所描述的Activity組件是可見的,以便該應用程序進程可以將參數token所描述的Activity組件的窗口繪製出來,這樣WindowManagerService服務接下來纔可以將該Activity組件的窗口顯示出來。通知應用程序進程將參數token所描述的Activity組件設置爲true是通過調用AppWindowToken對象wtoken的成員函數sendAppVisibilityToClients來實現的,同時在通知之前,也會將AppWindowToken對象wtoken的成員變量clientHidden設置爲false。

        假設參數visible的值等於false,那麼就表示要將參數token所描述的Activity組件設置不可見,這時候WindowManagerService類的成員函數setAppVisibility會繼續執行以下操作:

        1. 將AppWindowToken對象wtoken添加WindowManagerService類的成員變量mClosingApps所描述的一個ArrayList中,表示參數token所描述的Activity組件是正在關閉的。

        2. 如果AppWindowToken對象wtoken的成員變量hidden的值等於false,那麼就意味着參數token所描述的Activity組件當前是可見的。由於在這種情況下,參數token所描述的Activity組件正在等待關閉,因此,該Activity組件的窗口接下來的狀態應該等待隱藏不見的,這時候就需要將AppWindowToken對象wtoken的成員變量waitingToHide的值設置爲true。

        這一步執行完成之後,參數token所描述的Activity組件的可見性就設置好了,回到前面的Step 3中,即ActivityStack類的成員函數realStartActivityLocked中,接下來就會通知參數token所描述的Activity組件所運行在的應用程序進程將它加載起來,並且最後調用ActivityStack類的成員函數completeResumeLocked來通知WindowManagerService服務執行在前面Step 2所準備好的Activity組件切換操作。


 ActivityStack的completeResumeLocked函數

  1. private void completeResumeLocked(ActivityRecord next) {
  2. ......
  3. mStackSupervisor.scheduleIdleTimeoutLocked(next);
  4. mStackSupervisor.reportResumedActivityLocked(next);
  5. ......
  6. }

  ActivityStack類的成員函數completeResumeLocked主要是執行以下三個操作:

        1. 調用ActivityStackSupervisor的scheduleIdleTimeoutLocked函數向AMS所運行在的線程發送一個類型爲IDLE_TIMEOUT_MSG的消息,這個消息將在IDLE_TIMEOUT毫秒後處理。這個類型爲IDLE_TIMEOUT_MSG實際是用來監控WindowManagerService服務能否在IDLE_TIMEOUT毫秒之內,完成參數next所描述的Activity組件的切換操作,並且將它的窗口顯示出來。如果能夠到的話,WindowManagerService服務就會通知ActivityManagerService服務,然後ActivityManagerService服務就會執行一些清理工作,例如,將那些已經處於Stopped狀態的Activity組件清理掉。如果不能夠做到的話,那麼ActivityStack類的成員函數completeResumeLocked也需要保證在IDLE_TIMEOUT毫秒之後,ActivityManagerService服務能夠執行上述的清理工作。

        2. 調用ActivityStackSupervisor的eportResumedActivityLocked函數。

我們來看下ActivityStackSupervisor的eportResumedActivityLocked函數。

  1. boolean reportResumedActivityLocked(ActivityRecord r) {
  2. final ActivityStack stack = r.task.stack;
  3. if (isFrontStack(stack)) {
  4. mService.updateUsageStats(r, true);
  5. }
  6. if (allResumedActivitiesComplete()) {
  7. ensureActivitiesVisibleLocked(null, 0);
  8. mWindowManager.executeAppTransition();
  9. return true;
  10. }
  11. return false;
  12. }

這個函數當Activity完成onResume之後,會調用ensureActivitiesVisibleLocked函數(檢查哪些Activity組件是需要設置爲可見的,哪些Activity組件是需要設置爲不可見的)和WMS的executeAppTransition函數(通知WindowManagerService服務執行在前面Step 2所準備好的Activity組件切換操作)。

這裏我們再看下WMS的executeAppTransition函數


WMS的executeAppTransition函數

  1. public void executeAppTransition() {
  2. ......
  3. synchronized(mWindowMap) {
  4. if (mAppTransition.isTransitionSet()) {
  5. mAppTransition.setReady();
  6. final long origId = Binder.clearCallingIdentity();
  7. try {
  8. performLayoutAndPlaceSurfacesLocked();
  9. } finally {
  10. Binder.restoreCallingIdentity(origId);
  11. }
  12. }
  13. }
  14. }

executeAppTransition函數邏輯很簡單,先看mAppTransition(下一個切換操作)是否被設置過,然後調用setReady設置狀態爲準備狀態。接着就調用performLayoutAndPlaceSurfacesLocked刷新系統。


WMS的handleAppTransitionReadyLocked函數

在WMS的performLayoutAndPlaceSurfacesLocked關於Activity切換的代碼主要如下:

  1. if (mAppTransition.isReady()) {
  2. defaultDisplay.pendingLayoutChanges |= handleAppTransitionReadyLocked(defaultWindows);
  3. if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after handleAppTransitionReadyLocked",
  4. defaultDisplay.pendingLayoutChanges);
  5. }

這裏mAPPTransition的isReady函數就是在executeAppTransition函數中將下一個Activity的操作狀態設置成準備狀態的。 我們主要看下handleAppTransitionReadyLocked函數,這個函數比較長我們分段分析:

  1. public int handleAppTransitionReadyLocked(WindowList windows) {
  2. int changes = 0;
  3. int i;
  4. int appsCount = mOpeningApps.size();
  5. boolean goodToGo = true;
  6. if (!mAppTransition.isTimeout()) {
  7. for (i = 0; i < appsCount && goodToGo; i++) {
  8. AppWindowToken wtoken = mOpeningApps.valueAt(i);
  9. if (!wtoken.allDrawn && !wtoken.startingDisplayed
  10. && !wtoken.startingMoved) {
  11. goodToGo = false;
  12. }
  13. }

系統當前所有正在打開的Activity組件都保存在WindowManagerService類的成員變量mOpeningApps所描述的一個ArrayList中,因此,這段代碼就通過遍歷這個ArrayList來檢查每一個正在打開的Activity組件的UI是否已經繪製完成,即檢查對應的AppWindowToken對象的成員變量allDraw的值是否不等於true。如果不等於true的話,就說明還沒有繪製完成。此外,還要求這些正在打開的Activity組件的啓動窗口已經顯示結束,或者已經轉移給其它的Activity組件,即要求查對應的AppWindowToken對象的成員變量startingDisplayed和startingMoved的值均等於true。只要其中的一個正在打開的Activity組件不能滿足上述條件,那麼變量goodToGo的值就會等於false,表示這時候還不能執行Activity組件操作。


下面很大篇幅是壁紙相關的我們就不分析了。

我們繼續分析剩下的,主要就是mOpeningApps和mClosingApps中要打開的app和關閉的app的動畫設置。

  1. appsCount = mOpeningApps.size();//要啓動的app的APPWindowToken
  2. for (i = 0; i < appsCount; i++) {
  3. AppWindowToken wtoken = mOpeningApps.valueAt(i);
  4. final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
  5. if (!appAnimator.usingTransferredAnimation) {
  6. appAnimator.clearThumbnail();
  7. appAnimator.animation = null;
  8. }
  9. wtoken.inPendingTransaction = false;
  10. if (!setTokenVisibilityLocked(
  11. wtoken, animLp, true, transit, false, voiceInteraction)){//設置動畫
  12. mNoAnimationNotifyOnTransitionFinished.add(wtoken.token);
  13. }
  14. wtoken.updateReportedVisibilityLocked();
  15. wtoken.waitingToShow = false;
  16. appAnimator.mAllAppWinAnimators.clear();
  17. final int windowsCount = wtoken.allAppWindows.size();
  18. for (int j = 0; j < windowsCount; j++) {
  19. appAnimator.mAllAppWinAnimators.add(wtoken.allAppWindows.get(j).mWinAnimator);
  20. }
  21. mAnimator.mAnimating |= appAnimator.showAllWindowsLocked();//這個函數會設置VSync回調,並且設置動畫
  22. mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
  23. ......
  24. }
  25. appsCount = mClosingApps.size();//要關閉的app的APPWindowToken
  26. for (i = 0; i < appsCount; i++) {
  27. AppWindowToken wtoken = mClosingApps.valueAt(i);
  28. final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
  29. appAnimator.clearThumbnail();
  30. appAnimator.animation = null;
  31. wtoken.inPendingTransaction = false;
  32. setTokenVisibilityLocked(wtoken, animLp, false, transit, false, voiceInteraction);//設置動畫
  33. wtoken.updateReportedVisibilityLocked();
  34. wtoken.allDrawn = true;
  35. wtoken.deferClearAllDrawn = false;
  36. if (wtoken.startingWindow != null && !wtoken.startingWindow.mExiting) {
  37. scheduleRemoveStartingWindowLocked(wtoken);
  38. }
  39. mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
  40. ......
  41. }
  42. ......
  43. mOpeningApps.clear();
  44. mClosingApps.clear();
  45. // This has changed the visibility of windows, so perform
  46. // a new layout to get them all up-to-date.
  47. changes |= WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT
  48. | WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
  49. ......
  50. }
  51. return changes;
  52. }

這段代碼將給所有參與了切換操作的Activity組件設置一個切換動畫,而這個動畫就來自前面所獲得的變量animLp所指向的一個WindowManager.LayoutParams對象。由於參與了切換操作的Activity組件可以劃分爲兩類,即一類是正在打開的,一類是正在打閉的,因此,我們就分別討論這兩種類型的Activity組件的切換動畫的設置過程。
       

1)對於正在打開的Activity組件,它們的切換動畫的設置過程如下所示:

        1. 找到對應的AppWindowToken對象;

        2. 將對應的AppWindowToken對象的成員變量reportedVisible的值設置爲false,表示還沒有向ActivityManagerService服務報告過正在打開的Activity組件的可見性;

        3. 將對應的AppWindowToken對象的成員變量inPendingTransaction的值設置爲false,表示正在打開的Activity組件不是處於等待執行切換操作的狀態了;

        4. 將對應的AppWindowToken對象的成員變量animation的值設置爲null,因爲接下來要重新這個成員變量的值來描述正在打開的Activity組件的切換動畫;

        5. 調用WindowManagerService類的成員函數setTokenVisibilityLocked將正在打開的Activity組件的可見性設置爲true,並且給正在打開的Activity組件設置一個切換動畫,這個切換動畫會保存在對應的AppWindowToken對象的成員變量animation中;

        6. 調用對應的AppWindowToken對象的成員函數updateReportedVisibilityLocked向ActivityManagerService服務報告正在打開的Activity組件的可見性;

        7. 將對應的AppWindowToken對象的成員變量waitingToShow的值設置爲false,表示正在打開的Activity組件的窗口不是處於等待顯示的狀態了;

        8. 調用對應的AppWindowToken對象的成員函數showAllWindowsLocked通知SurfaceFlinger服務將正在打開的Activity組件的窗口設置爲可見的。


2)對於正在關閉的Activity組件,它們的切換動畫的設置過程如下所示:

        1. 找到對應的AppWindowToken對象;

        2. 將對應的AppWindowToken對象的成員變量inPendingTransaction的值設置爲false,表示正在關閉的Activity組件不是處於等待執行切換操作的狀態了;

        3. 將對應的AppWindowToken對象的成員變量animation的值設置爲null,因爲接下來要重新這個成員變量的值來描述正在關閉的Activity組件的切換動畫;

        4. 調用WindowManagerService類的成員函數setTokenVisibilityLocked將正在關閉的Activity組件的可見性設置爲true,並且給正在關閉的Activity組件設置一個切換動畫,這個切換動畫會保存在對應的AppWindowToken對象的成員變量animation中;

        5. 調用對應的AppWindowToken對象的成員函數updateReportedVisibilityLocked向ActivityManagerService服務報告正在關閉的Activity組件的可見性;

        6. 將對應的AppWindowToken對象的成員變量waitingToHide的值設置爲false,表示正在關閉的Activity組件的窗口不是處於等待隱藏的狀態了;

        7. 將對應的AppWindowToken對象的成員變量allDrawn的值設置爲true,這樣就可以使得前面所設置的切換動畫得以執行。

        給所有參與了切換操作的Activity組件都設置了一個切換動畫之後,接下來就可以將WindowManagerService類的成員變量mOpeningApps和mClosingApps所描述的兩個ArrayList清空了。


最後我們再來看下最後返回的changes,

  1. changes |= WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT
  2. | WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;

       由於前面的操作已經導致那些正在打開的Activity組件的窗口由不可見變爲可見,即相當於是導致窗口堆棧發生了變化,這時候就需要重新計算各個窗口的大小,以便讓各個窗口對自己的UI元素進行重新佈局,這是通過將變量changes的值的PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT位設置爲1來實現的。
        從前面的分析可以知道,一旦變量changes的值不等於0,WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLockedInner開始的那個do…while循環就會重複執行,也就是會重複執行以下三個操作:

        1. 計算各個窗口的大小,以便讓各個窗口可以對自己的UI元素進行佈局。

        2. 計算各個窗口接下來要執行的動畫。

        3. 執行各個窗口的動畫。
至此,我們就分析完成了Activity組件切換操作了。Activity組件的切換操作執行完成之後,參與了切換操作的Activity組件的窗口就會獲得一個切換動畫。一個窗口的切換動畫,與其本身所設置的進入動畫,以及其父窗口所設置的動畫,一起形成了窗口的顯示過程動畫。


AppWindowAnimator的showAllWindowsLocked函數

下面我們再來看看AppWindowAnimator的showAllWindowsLocked函數

  1. boolean showAllWindowsLocked() {
  2. boolean isAnimating = false;
  3. final int NW = mAllAppWinAnimators.size();
  4. for (int i=0; i<NW; i++) {
  5. WindowStateAnimator winAnimator = mAllAppWinAnimators.get(i);
  6. winAnimator.performShowLocked();
  7. isAnimating |= winAnimator.isAnimating();
  8. }
  9. return isAnimating;
  10. }
這個函數就遍歷這個AppWindowWinAnimators下所有的WindowStateAnimator,然後調用WindowStateAnimator的performShowLocked函數,這個函數就是顯示動畫的第一步,設置VSync信號,已經設置動畫。當有VSync信號過來時,就會開始播放動畫了。



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