Android6.0 WMS(十) WMS窗口動畫從設置到顯示框架

       在前一文中,我們分析了Activity組件的切換過程。從這個過程可以知道,所有參與切換操作的窗口都會被設置切換動畫。事實上,一個窗口在打開(關閉)的過程中,除了可能會設置切換動畫之外,它本身也可能會設置有進入(退出)動畫。再進一步地,如果一個窗口是附加在另外一個窗口之上的,那麼被附加窗口所設置的動畫也會同時傳遞給該窗口。

在之前WMS的第六篇博客窗口管理,主要從VSync信號作爲切入點分析窗口動畫的流程。這裏我們就詳細分析WindowManagerService服務顯示窗口動畫的原理。

        在Android系統中,窗口動畫的本質就是對原始窗口施加一個變換(Transformation)。在線性數學中,對物體的形狀進行變換是通過乘以一個矩陣(Matrix)來實現,目的就是對物體進行偏移、旋轉、縮放、切變、反射和投影等。因此,給窗口設置動畫實際上就給窗口設置一個變換矩陣(Transformation Matrix)。

        如前所述,一個窗口在打開(關閉)的過程,可能會被設置三個動畫,它們分別是窗口本身所設置的進入(退出)動畫(Self Transformation)、從被附加窗口傳遞過來的動畫(Attached Transformation),以及宿主Activity組件傳遞過來的切換動畫(App Transformation)。這三個Transformation組合在一起形成一個變換矩陣,以60fps的速度應用在窗口的原始形狀之上,完成窗口的動畫過程。

        窗口的變換矩陣是應用在窗口的原始位置和大小之上的,因此,在顯示窗口的動畫之前,除了要給窗口設置變換矩陣之外,還要計算好窗口的原始位置和大小,以及佈局和繪製好窗口的UI。我們在之前博客已經分析過窗口的位置和大小計算過程以及窗口UI的佈局和繪製過程了,本文主要關注窗口動畫的設置、合成和顯示過程。這三個過程通過以下四個部分的內容來描述:
       1. 窗口動畫的設置過程
       2. 窗口動畫的顯示框架
       3. 窗口動畫的推進過程
       4. 窗口動畫的合成過程
       其中,窗口動畫的設置過程包括上述三個動畫的設置過程,窗口動畫的推進過程是指定動畫的一步一步地遷移的過程,窗口動畫的合成過程是指上述三個動畫組合成一個變換矩陣的過程,後兩個過程包含在了窗口動畫的顯示框架中。

一. 窗口動畫的設置過程


        窗口被設置的動畫雖然可以達到三個,但是這三個動畫可以歸結爲兩類,一類是普通動畫,例如,窗口在打開過程中被設置的進入動畫和在關閉過程中被設置的退出動畫,另一類是切換動畫。其中,Self Transformation和Attached Transformation都是屬於普通動畫,而App Transformation屬於切換動畫。接下來我們就分別分析這兩種類型的動畫的設置過程。

1.1 普通動畫的設置

窗口在打開的過程中,是通過調用WindowState類的成員函數performShowLocked來實現的,WindowStateAnimator類的成員函數performShowLocked調用了applyEnterAnimationLocked函數來給當前正在處理的窗口設置一個進入動畫,如下所示:
  1. void applyEnterAnimationLocked() {
  2. final int transit;
  3. if (mEnterAnimationPending) {
  4. mEnterAnimationPending = false;
  5. transit = WindowManagerPolicy.TRANSIT_ENTER;
  6. } else {
  7. transit = WindowManagerPolicy.TRANSIT_SHOW;
  8. }
  9. applyAnimationLocked(transit, true);
  10. if (mService.mAccessibilityController != null
  11. && mWin.getDisplayId() == Display.DEFAULT_DISPLAY) {
  12. mService.mAccessibilityController.onWindowTransitionLocked(mWin, transit);
  13. }
  14. }
       如果mEnterAnimationPending的值等於true,那麼就說明它所描述的窗口正在等待顯示,也就是正處於不可見到可見狀態的過程中,那麼函數applyEnterAnimationLocked就會對該窗口設置一個類型爲WindowManagerPolicy.TRANSIT_ENTER的動畫,否則的話,就會對該窗口設置一個類型爲WindowManagerPolicy.TRANSIT_SHOW的動畫。
        確定好窗口的動畫類型之後,就調用另外一個成員函數applyAnimationLocked來爲窗口創建一個動畫了。接下來我們先分析窗口在關閉的過程中所設置的動畫類型,然後再來分析applyAnimationLocked函數的實現。
        當應用程序進程請求WindowManagerService服務刷新一個窗口的時候,會調用到WindowManagerService類的成員函數relayoutWindow。WindowManagerService類的成員函數relayoutWindow在執行的過程中,如果發現需要將一個窗口從可見狀態設置爲不可見狀態時,也就是發現需要關閉一個窗口時,就會對該窗口設置一個退出動出,如下所示:
  1. if (viewVisibility == View.VISIBLE &&
  2. (win.mAppToken == null || !win.mAppToken.clientHidden)) {
  3. ……
  4. } else {
  5. winAnimator.mEnterAnimationPending = false;
  6. winAnimator.mEnteringAnimation = false;
  7. if (winAnimator.mSurfaceControl != null) {
  8. if (!win.mExiting) {
  9. surfaceChanged = true;
  10. int transit = WindowManagerPolicy.TRANSIT_EXIT;//設置一個退出動畫
  11. if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
  12. transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
  13. }
  14. if (win.isWinVisibleLw() &&
  15. winAnimator.applyAnimationLocked(transit, false)) {
  16. focusMayChange = isDefaultDisplay;
  17. win.mExiting = true;
  18. }
WindowState對象win描述的便是要刷新的窗口。當參數viewVisibility的值不等於View.VISIBLE時,就說明要將WindowState對象win所描述的窗口設置爲不可見。另一方面,如果WindowState對象win的成員變量mAppToken的值不等於null,並且這個成員變量所指向的一個AppWindowToken對象的成員變量clientHidden的值等於true,那麼就說明WindowState對象win所描述的窗口是一個與Activity組件相關的窗口,並且該Activity組件是處於不可見狀態的。在這種情況下,也需要將WindowState對象win所描述的窗口設置爲不可見。
        一旦WindowState對象win所描述的窗口要設置爲不可見,就需要考慮給它設置一個退出動畫,不過有四個前提條件:
        1. 該窗口有一個繪圖表面,即WindowState對象win的成員變量mSurface的值不等於null;
        2. 該窗口的繪圖表面不是處於等待銷燬的狀態,即WindowState對象win的成員變量mSurfacePendingDestroy的值不等於true;
        3. 該窗口不是處於正在關閉的狀態,即WindowState對象win的成員變量mExiting的值不等於true;
        4. 該窗口當前正在處於可見的狀態,即WindowState對象win的成員isWinVisibleLw的返回值等於true。
        在滿足上述四個條件的情況下,就說明WindowState對象win所描述的窗口的狀態要由可見變爲不可見,因此,就需要給它設置一個退出動畫,即一個類型爲WindowManagerPolicy.TRANSIT_EXIT的動畫,這同樣是通過調用WindowManagerService類的成員函數applyAnimationLocked來實現的。
        從上面的分析就可以知道,無論是窗口在打開時所需要的進入動畫,還是窗口在關閉時所需要的退出動畫,都是通過調用applyAnimationLocked來設置的,它的實現如下所示:
  1. boolean applyAnimationLocked(int transit, boolean isEntrance) {
  2. if ((mLocalAnimating && mAnimationIsEntrance == isEntrance)
  3. || mKeyguardGoingAwayAnimation) {
  4. if (mAnimation != null && mKeyguardGoingAwayAnimation
  5. && transit == WindowManagerPolicy.TRANSIT_PREVIEW_DONE) {
  6. applyFadeoutDuringKeyguardExitAnimation();
  7. }
  8. return true;
  9. }
  10. if (mService.okToDisplay()) {
  11. int anim = mPolicy.selectAnimationLw(mWin, transit);
  12. int attr = -1;
  13. Animation a = null;
  14. if (anim != 0) {
  15. a = anim != -1 ? AnimationUtils.loadAnimation(mContext, anim) : null;
  16. } else {
  17. switch (transit) {
  18. case WindowManagerPolicy.TRANSIT_ENTER:
  19. attr = com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
  20. break;
  21. case WindowManagerPolicy.TRANSIT_EXIT:
  22. attr = com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;
  23. break;
  24. case WindowManagerPolicy.TRANSIT_SHOW:
  25. attr = com.android.internal.R.styleable.WindowAnimation_windowShowAnimation;
  26. break;
  27. case WindowManagerPolicy.TRANSIT_HIDE:
  28. attr = com.android.internal.R.styleable.WindowAnimation_windowHideAnimation;
  29. break;
  30. }
  31. if (attr >= 0) {
  32. a = mService.mAppTransition.loadAnimationAttr(mWin.mAttrs, attr);
  33. }
  34. }
  35. if (a != null) {
  36. if (WindowManagerService.DEBUG_ANIM) {
  37. RuntimeException e = null;
  38. if (!WindowManagerService.HIDE_STACK_CRAWLS) {
  39. e = new RuntimeException();
  40. e.fillInStackTrace();
  41. }
  42. Slog.v(TAG, “Loaded animation “ + a + ” for “ + this, e);
  43. }
  44. setAnimation(a);
  45. mAnimationIsEntrance = isEntrance;
  46. }
  47. } else {
  48. clearAnimation();
  49. }
  50. return mAnimation != null;
  51. }
參數transit描述的是要設置的動畫的類型,而參數isEntrance描述的是要設置的動畫是進入類型還是退出類型的。
        如果成員變量mLocalAnimating的值等於true,那麼就說明它所描述的窗口已經被設置過動畫了,並且這個動畫正在顯示的過程中。在這種情況下,如果這個mAnimationIsEntrance的值等於參數isEntrance的值,那麼就說明該窗口正在顯示的動畫就是所要求設置的動畫,這時候就不需要給窗口重新設置一個動畫了,因此就直接返回了。
        我們假設設置一個新的動畫,這時候還需要繼續判斷屏幕當前是否是處於非凍結和點亮的狀態的。只有在屏幕不是被凍結並且是點亮的情況下,applyAnimationLocked才真正需要設置一個動畫,否則的話,設置了也是無法顯示的。當WindowManagerService類的成員變量mDisplayFrozen的時候,就說明屏幕不是被凍結的,而當WindowManagerService類的成員變量mPolicy所指向的一個PhoneWindowManager對象的成員函數isScreenOn的返回值等於true的時候,就說明屏幕是點亮的。在滿足上述兩個條件的情況下(也就是okToDisplay),applyAnimationLocked就開始給參數win所描述的窗口創建動畫了。 
        applyAnimationLocked首先是檢查WindowManagerService類的成員變量mPolicy所指向的一個PhoneWindowManager對象是否可以爲參數win所描述的窗口提供一個類型爲transit的動畫。如果可以的話,那麼調用這個PhoneWindowManager對象的成員函數selectAnimationLw的返回值anim就不等於0,這時候applyAnimationLocked就可以調用AnimationUtils類的靜態成員函數loadAnimation來根據該返回值anim來創建一個動畫,並且保存在變量a中。
        如果WindowManagerService類的成員變量mPolicy所指向的一個PhoneWindowManager對象不可以爲參數win所描述的窗口提供一個類型爲transit的動畫的話,那麼applyAnimationLocked就需要根據該窗口的佈局參數來創建這個動畫了。這個創建過程分爲兩步執行:
        1. 將參數transit的值轉化爲一個對應的動畫樣式名稱;
        2. 調用WindowManagerService類的mAppTransitiond的成員函數loadAnimation來在指定的窗口布局參數中創建前面第1步所指定樣式名稱的動畫,並且保存在變量a中,其中,指定的窗口布局參數是由WindowState對象win的成員變量mAttrs所指向的一個WindowManager.LayoutParams對象來描述的。
        最後,如果變量a的值不等於null,即前面成功地爲參數win所描述的窗口創建了一個動畫,那麼接下來就會將該動畫設置給參數win所描述的窗口。這是通過setAnimation函數來實現的,最終保存在mAnimation變量中。同時,還會將參數isEntrance的值保存在參數成員變量mAnimationIsEntrance,以表明前面給它所設置的動畫是屬於進入類型還是退出類型的。

1.2 切換動畫的設置過程

如果一個窗口屬於一個Activity組件窗口,那麼當該Activity組件被切換的時候,就會被設置一個切換動畫,這是通過調用WindowManagerService類的成員函數setTokenVisibilityLocked來實現的,如下所示:
  1. boolean setTokenVisibilityLocked(AppWindowToken wtoken, WindowManager.LayoutParams lp,
  2. boolean visible, int transit, boolean performLayout, boolean isVoiceInteraction) {
  3. boolean delayed = false;
  4. if (wtoken.clientHidden == visible) {
  5. wtoken.clientHidden = !visible;
  6. wtoken.sendAppVisibilityToClients();
  7. }
  8. wtoken.willBeHidden = false;
  9. if (wtoken.hidden == visible || (wtoken.hidden && wtoken.mIsExiting)) {
  10. boolean changed = false;
  11. boolean runningAppAnimation = false;
  12. if (transit != AppTransition.TRANSIT_UNSET) {
  13. if (wtoken.mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
  14. wtoken.mAppAnimator.animation = null;
  15. }
  16. if (applyAnimationLocked(wtoken, lp, transit, visible, isVoiceInteraction)) {
  17. delayed = runningAppAnimation = true;
  18. }
  19. WindowState window = wtoken.findMainWindow();
  20. if (window != null && mAccessibilityController != null
  21. && window.getDisplayId() == Display.DEFAULT_DISPLAY) {
  22. mAccessibilityController.onAppWindowTransitionLocked(window, transit);
  23. }
  24. changed = true;
  25. }
  26. final int windowsCount = wtoken.allAppWindows.size();
  27. for (int i = 0; i < windowsCount; i++) {
  28. WindowState win = wtoken.allAppWindows.get(i);
  29. if (win == wtoken.startingWindow) {
  30. continue;
  31. }
  32. if (visible) {
  33. if (!win.isVisibleNow()) {
  34. if (!runningAppAnimation) {
  35. win.mWinAnimator.applyAnimationLocked(
  36. WindowManagerPolicy.TRANSIT_ENTER, true);
  37. ……
  38. }
  39. ……
  40. }
  41. } else if (win.isVisibleNow()) {
  42. if (!runningAppAnimation) {
  43. win.mWinAnimator.applyAnimationLocked(
  44. WindowManagerPolicy.TRANSIT_EXIT, false);
  45. ……
  46. }
  47. ……
  48. }
  49. }
  50. ……
  51. }
  52. if (wtoken.mAppAnimator.animation != null) {
  53. delayed = true;
  54. }
  55. for (int i = wtoken.allAppWindows.size() - 1; i >= 0 && !delayed; i–) {
  56. if (wtoken.allAppWindows.get(i).mWinAnimator.isWindowAnimating()) {
  57. delayed = true;
  58. }
  59. }
  60. return delayed;
  61. }
參數wtoken描述的是要切換的Activity組件,參數lp描述的是要用來創建切換動畫的佈局參數,參數transit描述的是要創建的切換動畫的類型,而參數visible描述的是要切換的Activity組件接下來是否是可見的。
        WindowManagerService類的成員函數setTokenVisibilityLocked首先判斷要切換的Activity組件當前的可見性是否已經就是要設置的可見性,即參數wtoken所指向的一個AppWindowToken對象的成員變量hidden的值是否不等於參數visible的值。如果不等於的話,就說明要切換的Activity組件當前的可見性已經就是要設置的可見性了,這時候WindowManagerService類的成員函數setTokenVisibilityLocked就不用再爲它設置切換動畫了。
        我們假設要切換的Activity組件當前的可見性不是要求設置的可見性,即參數wtoken所指向的一個AppWindowToken對象的成員變量hidden的值等於參數visible的值,那麼WindowManagerService類的成員函數setTokenVisibilityLocked還會繼續檢查參數transit描述的是否是一個有效的動畫類型,即它的值是否不等於WindowManagerPolicy.TRANSIT_UNSET。如果參數transit描述的是一個有效的動畫類型的話,那麼WindowManagerService類的成員函數setTokenVisibilityLocked接下來就會執行以下三個操作:
        1. 判斷要切換的Activity組件當前是否被設置了一個啞動畫,即參數wtoken所指向的一個AppWindowToken對象的成員變量animation是否與WindowManagerService類的成員變量sDummyAnimation指向了同一個Animation對象。如果是的話,那麼就會將wtoken所指向的一個AppWindowToken對象的成員變量animation的值設置爲null,因爲接下來要重新爲它設置一個新的Animation對象。一個需要參與切換的Activity組件會設置可見性的時候,是會被設置一個啞動畫的。
        2. 調用WindowManagerService類的四個參數版本的成員函數applyAnimationLocked根據參數lp、transit和visible的值來爲要切換的Activity組件創建一個動畫。
        3. 如果第2步可以成功地爲要切換的Activity組件創建一個動畫的話,那麼這個動畫就會保存在wtoken所指向的一個AppWindowToken對象的成員變量animation中,這時候就會將變量delayed和runningAppAnimation的值均設置爲true。
        變量runningAppAnimation的值等於true意味着與參數wtoken所描述的Activity組件所對應的窗口接下來要執行一個切換動畫。在這種情況下,WindowManagerService類的成員函數setTokenVisibilityLocked就不需要爲這些窗口單獨設置一個進入或者退出類型的動畫,否則的話,WindowManagerService類的成員函數setTokenVisibilityLocked就會根據這些窗口的當前可見性狀態以及參數wtoken所描述的Activity組件被要求設置的可見性來單獨設置一個進入或者退出類型的動畫:

        1. 如果一個窗口當前是不可見的,即用來描述它的一個WindowState對象的成員函數isVisibleNow的返回值等於false,但是參數wtoken所描述的Activity組件被要求設置成可見的,即參數visible的值等於true,那麼就需要給該窗口設置一個類型爲WindowManagerPolicy.TRANSIT_ENTER的動畫;
        2. 如果一個窗口當前是可見的,即用來描述它的一個WindowState對象的成員函數isVisibleNow的返回值等於true,但是參數wtoken所描述的Activity組件被要求設置成不可見的,即參數visible的值等於false,那麼就需要給該窗口設置一個類型爲WindowManagerPolicy.TRANSIT_EXIT的動畫。
        給與參數wtoken所描述的Activity組件所對應的窗口設置動畫是通過調用WindowStateAnimator的成員函數applyAnimationLocked來實現的,這個成員函數在前面已經分析過了。另外,與參數wtoken所描述的Activity組件所對應的窗口是保存在參數wtoken所指向的一個AppWindowToken對象的成員變量allAppWindows所描述的一個ArrayList中的,因此,通過遍歷這個ArrayList,就可以爲與參數wtoken所描述的Activity組件所對應的每一個窗口設置一個動畫。
        最後,如果前面成功地爲參數wtoken所描述的Activity組件創建了一個切換動畫,即該參數所描述的一個AppWindowToken對象的成員變量animation的值不等於null,那麼WindowManagerService類的成員函數setTokenVisibilityLocked的返回值delayed就會等於true,表示參數wtoken所描述的Activity組件要執行一個動換動畫,同時也表明該Activity組件的窗口要延遲到切換動畫顯示結束後,才真正顯示出來。
        接下來,我們繼續分析WindowManagerService類的四個參數版本的成員函數applyAnimationLocked的實現,以便可以瞭解Activity組件的切換動畫的創建過程,如下所示:

  1. private boolean applyAnimationLocked(AppWindowToken atoken,
  2. WindowManager.LayoutParams lp, int transit, boolean enter, boolean isVoiceInteraction) {
  3. if (okToDisplay()) {
  4. DisplayInfo displayInfo = getDefaultDisplayInfoLocked();
  5. final int width = displayInfo.appWidth;
  6. final int height = displayInfo.appHeight;
  7. WindowState win = atoken.findMainWindow();
  8. Rect containingFrame = new Rect(0, 0, width, height);
  9. Rect contentInsets = new Rect();
  10. Rect appFrame = new Rect(0, 0, width, height);
  11. if (win != null && win.isFullscreen(width, height)) {
  12. containingFrame.set(win.mContainingFrame);
  13. contentInsets.set(win.mContentInsets);
  14. appFrame.set(win.mFrame);
  15. }
  16. if (atoken.mLaunchTaskBehind) {
  17. enter = false;
  18. }
  19. Animation a = mAppTransition.loadAnimation(lp, transit, enter, width, height,
  20. mCurConfiguration.orientation, containingFrame, contentInsets, appFrame,
  21. isVoiceInteraction);
  22. if (a != null) {
  23. if (DEBUG_ANIM) {
  24. RuntimeException e = null;
  25. if (!HIDE_STACK_CRAWLS) {
  26. e = new RuntimeException();
  27. e.fillInStackTrace();
  28. }
  29. Slog.v(TAG, “Loaded animation “ + a + ” for “ + atoken, e);
  30. }
  31. atoken.mAppAnimator.setAnimation(a, width, height,
  32. mAppTransition.canSkipFirstFrame());
  33. }
  34. } else {
  35. atoken.mAppAnimator.clearAnimation();
  36. }
  37. return atoken.mAppAnimator.animation != null;
  38. }
這個函數基本和前面WindowStateAnimator的的applyAnimationLocked函數類似,不過最後是設置在AppWindowToken的mAppAnimator的animation變量中。


二、窗口動畫的顯示框架


 窗口動畫是在WindowManagerService服務刷新系統UI的時候顯示的。WindowManagerService服務刷新系統UI是通過調用WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLockedInner來實現的,這個函數的框架已經和老羅那篇博客分析的有點不一樣了,接下來我們就主要分析與窗口動畫的顯示框架相關的邏輯,如下所示:
  1. private final void performLayoutAndPlaceSurfacesLockedInner(boolean recoveringMemory) {
  2. ……
  3. SurfaceControl.openTransaction();
  4. try {
  5. ……
  6. for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
  7. final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx);
  8. boolean updateAllDrawn = false;
  9. WindowList windows = displayContent.getWindowList();
  10. DisplayInfo displayInfo = displayContent.getDisplayInfo();
  11. final int displayId = displayContent.getDisplayId();
  12. final int dw = displayInfo.logicalWidth;
  13. final int dh = displayInfo.logicalHeight;
  14. final int innerDw = displayInfo.appWidth;
  15. final int innerDh = displayInfo.appHeight;
  16. final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
  17. // Reset for each display.
  18. mInnerFields.mDisplayHasContent = false;
  19. mInnerFields.mPreferredRefreshRate = 0;
  20. mInnerFields.mPreferredModeId = 0;
  21. int repeats = 0;
  22. do {
  23. repeats++;
  24. if (repeats > 6) {
  25. ……
  26. break;
  27. }
  28. ……
  29. //計算各個窗口的大小
  30. if (repeats < 4) {
  31. performLayoutLockedInner(displayContent, repeats == 1,
  32. false /*updateInputWindows*/);
  33. } else {
  34. Slog.w(TAG, “Layout repeat skipped after too many iterations”);
  35. }
  36. ……
  37. } while (displayContent.pendingLayoutChanges != 0);//直到pendingLayoutChanges爲0
  38. ……
  39. // Moved from updateWindowsAndWallpaperLocked().
  40. if (w.mHasSurface) {
  41. // Take care of the window being ready to display.
  42. final boolean committed =
  43. winAnimator.commitFinishDrawingLocked();//這裏面調用performShowLocked,然後顯示窗口
  44. …….
  45. winAnimator.setSurfaceBoundariesLocked(recoveringMemory);//設置窗口size,位置等
  46. }
  47. ……
  48. }
  49. ……
  50. if (updateAllDrawn) {
  51. updateAllDrawnLocked(displayContent);//更新APPWindowToken的allDrawn是否爲true
  52. }
  53. }
  54. if (focusDisplayed) {
  55. mH.sendEmptyMessage(H.REPORT_LOSING_FOCUS);
  56. }
  57. mDisplayManagerInternal.performTraversalInTransactionFromWindowManager();//設置SurfaceControl的旋轉角度、大小等
  58. } catch (RuntimeException e) {
  59. Slog.wtf(TAG, “Unhandled exception in Window Manager”, e);
  60. } finally {
  61. SurfaceControl.closeTransaction();
  62. }
  63. ……
  64. enableScreenIfNeededLocked();
  65. scheduleAnimationLocked();//設置動畫使能(當下次VSync信號過來,開始動畫流程)
  66. }
我們先來看WindowStateAnimator的commitFinishDrawingLocked函數,將WindowStateAnimator的mDrawState的狀態設置成READY_TO_SHOW,當其窗口的APPWindowToken的allDrawn爲true會調用performShowLocked(當然這裏還不是),當窗口是TYPE_APPLICATION_STARTING時會調用performShowLocked,之前分析Activity啓動窗口的博客http://blog.csdn.net/kc58236582/article/details/54016765分析過)。
  1. boolean commitFinishDrawingLocked() {
  2. if (mDrawState != COMMIT_DRAW_PENDING && mDrawState != READY_TO_SHOW) {
  3. return false;
  4. }
  5. if (DEBUG_SURFACE_TRACE || DEBUG_ANIM) {
  6. Slog.i(TAG, “commitFinishDrawingLocked: mDrawState=READY_TO_SHOW “ + mSurfaceControl);
  7. }
  8. mDrawState = READY_TO_SHOW;
  9. final AppWindowToken atoken = mWin.mAppToken;
  10. if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
  11. return performShowLocked();
  12. }
  13. return false;
  14. }
接下來在performLayoutAndPlaceSurfacesLockedInner中還會調用updateAllDrawnLocked函數,當每個APPWindowToken的numDrawnWindows大於等於numInteresting時,DisplayContent的layoutNeeded爲true,APPWindowToken的allDrawn爲true。這個時候當DisplayContent的layoutNeeded爲true,會重新調用刷新函數,然後再次調用commitFinishDrawingLocked函數,這個時候APPWindowToken的allDrawn爲true,就會調用performShowLocked函數了。
  1. private void updateAllDrawnLocked(DisplayContent displayContent) {
  2. // See if any windows have been drawn, so they (and others
  3. // associated with them) can now be shown.
  4. ArrayList<TaskStack> stacks = displayContent.getStacks();
  5. for (int stackNdx = stacks.size() - 1; stackNdx >= 0; –stackNdx) {
  6. final ArrayList<Task> tasks = stacks.get(stackNdx).getTasks();
  7. for (int taskNdx = tasks.size() - 1; taskNdx >= 0; –taskNdx) {
  8. final AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
  9. for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; –tokenNdx) {
  10. final AppWindowToken wtoken = tokens.get(tokenNdx);
  11. if (!wtoken.allDrawn) {
  12. int numInteresting = wtoken.numInterestingWindows;
  13. if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) {
  14. wtoken.allDrawn = true;//allDrawn設爲true
  15. displayContent.layoutNeeded = true;//需要佈局
  16. mH.obtainMessage(H.NOTIFY_ACTIVITY_DRAWN, wtoken.token).sendToTarget();
  17. }
  18. }
  19. }
  20. }
  21. }
  22. }
下面我們再來看看APPWindowToken的numDrawnWindows 和numInterestingWindows的處理。也是在performLayoutAndPlaceSurfacesLockedInner函數中,在遍歷每個window的時候有如下代碼:
  1. final AppWindowToken atoken = w.mAppToken;
  2. if (atoken != null
  3. && (!atoken.allDrawn || atoken.mAppAnimator.freezingScreen)) {
  4. if (atoken.lastTransactionSequence != mTransactionSequence) {
  5. atoken.lastTransactionSequence = mTransactionSequence;
  6. atoken.numInterestingWindows = atoken.numDrawnWindows = 0;
  7. atoken.startingDisplayed = false;
  8. }
  9. if ((w.isOnScreenIgnoringKeyguard()
  10. || winAnimator.mAttrType == TYPE_BASE_APPLICATION)
  11. && !w.mExiting && !w.mDestroying) {
  12. if (w != atoken.startingWindow) {
  13. if (!atoken.mAppAnimator.freezingScreen || !w.mAppFreezing) {
  14. atoken.numInterestingWindows++;//不是startingWindow等,numInterestingWindows加1
  15. if (w.isDrawnLw()) {
  16. atoken.numDrawnWindows++;//已經繪製過的窗口numDrawnWindows加1
  17. updateAllDrawn = true;
  18. }
  19. }
  20. } else if (w.isDrawnLw()) {
  21. atoken.startingDisplayed = true;
  22. }
  23. }
  24. }
我們再來看WindowState的isDrawnLw如何看窗口是否已經繪製過了,代碼如下。首先必要要有Surface,然後其WindowStateAnimator的mDrawState爲READY_TO_SHOW或者是HAS_DRAWN狀態。
  1. public boolean isDrawnLw() {
  2. return mHasSurface && !mDestroying &&
  3. (mWinAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW
  4. || mWinAnimator.mDrawState == WindowStateAnimator.HAS_DRAWN);
  5. }
我們再來看看commitFinishDrawingLocked函數,主要當mDrawState是COMMIT_DRAW_PENDING 和READY_TO_SHOW纔會將狀態置爲READY_TO_SHOW。
  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. }
而COMMIT_DRAW_PENDING狀態只有在下面函數中設置。
  1. boolean finishDrawingLocked() {
  2. final boolean startingWindow =
  3. mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
  4. if (mDrawState == DRAW_PENDING) {
  5. mDrawState = COMMIT_DRAW_PENDING;
  6. return true;
  7. }
  8. return false;
  9. }
接下來我們就要研究下是誰調用了WindowStateAnimator的finishDrawingLocked函數。


ViewRootImpl的performDraw函數會調用mWindowSession.finishDrawing(mWindow),然後Session會調用WMS的finishDrawingWindow函數,如下,這個函數又會調用WindowStateAnimator的finishDrawingLocked函數
  1. public void finishDrawingWindow(Session session, IWindow client) {
  2. final long origId = Binder.clearCallingIdentity();
  3. try {
  4. synchronized (mWindowMap) {
  5. WindowState win = windowForClientLocked(session, client, false);
  6. if (win != null && win.mWinAnimator.finishDrawingLocked()) {
  7. if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
  8. getDefaultDisplayContentLocked().pendingLayoutChanges |=
  9. WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
  10. }
  11. final DisplayContent displayContent = win.getDisplayContent();
  12. if (displayContent != null) {
  13. displayContent.layoutNeeded = true;
  14. }
  15. requestTraversalLocked();
  16. }
  17. }
  18. } finally {
  19. Binder.restoreCallingIdentity(origId);
  20. }
  21. }
finishDrawingLocked函數中當mDrawState的狀態是DRAW_PENDING是把狀態改成COMMIT_DRAW_PENDING。
  1. boolean finishDrawingLocked() {
  2. final boolean startingWindow =
  3. mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
  4. if (mDrawState == DRAW_PENDING) {
  5. mDrawState = COMMIT_DRAW_PENDING;
  6. return true;
  7. }
  8. return false;
  9. }
而在createSurfaceLocked函數中創建SurfaceControl的時候會把這個狀態設置成DRAW_PENDING。
  1. SurfaceControl createSurfaceLocked() {
  2. final WindowState w = mWin;
  3. if (mSurfaceControl == null) {
  4. mDrawState = DRAW_PENDING;
這樣這個邏輯就通了,當應用繪製完成會在ViewRootImpl中調用Session的finishDrawing,然後一路到WindowStateAnimator的finishDrawingLocked將mDrawState狀態變成DRAW_PENDING,然後刷新系統時調用commitFinishDrawingLocked函數,這個時候把狀態變成READY_TO_SHOW,最後調用updateAllDrawnLocked函數,把相關的APPWindowToken的allDrawn設置爲true,並且再次刷新系統,再次調用commitFinishDrawingLocked函數時,這個時候APPWindowToken的allDrawn爲true。就會調用performShowLocked函數了。

我們下面再看看,哪裏會再次界面刷新呢,我們來看performLayoutAndPlaceSurfacesLockedLoop函數,當調用完performLayoutAndPlaceSurfacesLockedInner函數後,當調用函數needsLayout函數還需要刷新佈局時,會調用requestTraversalLocked函數請求再次刷新。
  1. private final void performLayoutAndPlaceSurfacesLockedLoop() {
  2. ……
  3. try {
  4. performLayoutAndPlaceSurfacesLockedInner(recoveringMemory);
  5. mInLayout = false;
  6. if (needsLayout()) {
  7. if (++mLayoutRepeatCount < 6) {
  8. requestTraversalLocked();
  9. } else {
  10. Slog.e(TAG, “Performed 6 layouts in a row. Skipping”);
  11. mLayoutRepeatCount = 0;
  12. }
  13. } else {
  14. mLayoutRepeatCount = 0;
  15. }
  16. ……
  17. } catch (RuntimeException e) {
  18. mInLayout = false;
  19. Slog.wtf(TAG, “Unhandled exception while laying out windows”, e);
  20. }
  21. }
我們先看看needsLayout函數,就是遍歷各個DisplayContent,看看layoutNeeded是否有true。聯想到我們前面在updateAllDrawnLocked函數會把這個layoutNeeded置爲 true。
  1. private boolean needsLayout() {
  2. final int numDisplays = mDisplayContents.size();
  3. for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
  4. final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx);
  5. if (displayContent.layoutNeeded) {
  6. return true;
  7. }
  8. }
  9. return false;
  10. }
然後就會調用requestTraversalLocked函數,再次刷新佈局。這個函數就是發送消息。
  1. void requestTraversalLocked() {
  2. if (!mTraversalScheduled) {
  3. mTraversalScheduled = true;
  4. mH.sendEmptyMessage(H.DO_TRAVERSAL);
  5. }
  6. }
這個消息會再次調用performLayoutAndPlaceSurfacesLocked函數來刷新佈局。
  1. case DO_TRAVERSAL: {
  2. synchronized(mWindowMap) {
  3. mTraversalScheduled = false;
  4. performLayoutAndPlaceSurfacesLocked();
  5. }
  6. } break;

三、動畫顯示

最後我們再來回顧下,當我們是普通應用的啓動或者就是正常顯示,我們就是走上面流程,最後到commitFinishDrawingLocked函數。這個函數,如果是應用全部繪製結束還是TYPE_APPLICATION_STARTING類型的啓動窗口,最後調用performShowLocked函數來顯示動畫。這個函數我們後面再分析。
  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. }
Activity切換我們在http://blog.csdn.net/kc58236582/article/details/54092667博客中分析,是通過在在WMS的performLayoutAndPlaceSurfacesLocked調用如下代碼。
  1. if (mAppTransition.isReady()) {
  2. defaultDisplay.pendingLayoutChanges |= handleAppTransitionReadyLocked(defaultWindows);
  3. if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats(“after handleAppTransitionReadyLocked”,
  4. defaultDisplay.pendingLayoutChanges);
  5. }
然後在handleAppTransitionReadyLocked函數中會調用setTokenVisibilityLocked函數,設置切換動畫,最後再調用AppWindowAnimator的showAllWindowsLocked函數來調用了WindowStateAnimator的performShowLocked函數,來顯示動畫。
  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. }
最後殊途同歸都到了WindowStateAnimator的performShowLocked函數。這個函數主要先調用applyEnterAnimationLocked函數設置普通應用的進去或者退出動畫,然後將mDrawState的狀態設置爲HAS_DRAWN,最後調用WMS的scheduleAnimationLocked函數(這個函數在http://blog.csdn.net/kc58236582/article/details/53835998博客中分析過),這樣當有VSync信號過來,就可以調用WindowAnimator的animateLocked函數播放動畫了。
  1. boolean performShowLocked() {
  2. ……
  3. if (mDrawState == READY_TO_SHOW && mWin.isReadyForDisplayIgnoringKeyguard()) {
  4. mService.enableScreenIfNeededLocked();
  5. applyEnterAnimationLocked();
  6. ……
  7. mDrawState = HAS_DRAWN;
  8. mService.scheduleAnimationLocked();
  9. ……
  10. return true;
  11. }
  12. return false;
  13. }

下篇博客我們再繼續分析下動畫的播放過程。




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