Android窗口管理服務WindowManagerService顯示窗口動畫的原理分析

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

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

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


圖1 窗口的動畫顯示過程

       從上面的分析可以知道,窗口的變換矩陣是應用在窗口的原始位置和大小之上的,因此,在顯示窗口的動畫之前,除了要給窗口設置變換矩陣之外,還要計算好窗口的原始位置和大小,以及佈局和繪製好窗口的UI。在前面Android窗口管理服務WindowManagerService計算Activity窗口大小的過程分析和Android應用程序窗口(Activity)的測量(Measure)、佈局(Layout)和繪製(Draw)過程分析這兩篇文章中,我們已經分析過窗口的位置和大小計算過程以及窗口UI的佈局和繪製過程了,本文主要關注窗口動畫的設置、合成和顯示過程。這三個過程通過以下四個部分的內容來描述:

      1. 窗口動畫的設置過程

      2. 窗口動畫的顯示框架

      3. 窗口動畫的推進過程

      4. 窗口動畫的合成過程

      其中,窗口動畫的設置過程包括上述三個動畫的設置過程,窗口動畫的推進過程是指定動畫的一步一步地遷移的過程,窗口動畫的合成過程是指上述三個動畫組合成一個變換矩陣的過程,後兩個過程包含在了窗口動畫的顯示框架中。

       一. 窗口動畫的設置過程

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

       1. 普通動畫的設置過程

       從前面Android窗口管理服務WindowManagerService顯示Activity組件的啓動窗口(Starting Window)的過程分析一文可以知道,窗口在打開的過程中,是通過調用WindowState類的成員函數performShowLocked來實現的,如下所示:


public class WindowManagerService extends IWindowManager.Stub 
        implements Watchdog.Monitor { 
    ...... 
                                              
    private final class WindowState implements WindowManagerPolicy.WindowState { 
        ...... 
                                              
        boolean performShowLocked() { 
            ...... 
                                              
            if (mReadyToShow && isReadyForDisplay()) { 
                ...... 
                                              
                if (!showSurfaceRobustlyLocked(this)) { 
                    return false; 
                } 
                ...... 
                                              
                applyEnterAnimationLocked(this); 
                                              
                ......
            } 
            return true; 
        } 
                                              
        ...... 
    } 
                                              
    ...... 
}

      這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。


      WindowState類的成員函數performShowLocked首先是調用WindowManagerService類的成員函數showSurfaceRobustlyLocked來通知SurfaceFlinger服務將當前正在處理的窗口設置爲可見,接着再調用WindowManagerService類的成員函數applyEnterAnimationLocked來給當前正在處理的窗口設置一個進入動畫,如下所示:


public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor {
    ......
    private void applyEnterAnimationLocked(WindowState win) {
        int transit = WindowManagerPolicy.TRANSIT_SHOW;
        if (win.mEnterAnimationPending) {
            win.mEnterAnimationPending = false;
            transit = WindowManagerPolicy.TRANSIT_ENTER;
        }
        applyAnimationLocked(win, transit, true);
    }
                                         
    ......
}

       這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。

       如果參數win所指向的一個WindowState對象的成員變量mEnterAnimationPending的值等於true,那麼就說明它所描述的窗口正在等待顯示,也就是正處於不可見到可見狀態的過程中,那麼WindowManagerService類的成員函數applyEnterAnimationLocked就會對該窗口設置一個類型爲WindowManagerPolicy.TRANSIT_ENTER的動畫,否則的話,就會對該窗口設置一個類型爲WindowManagerPolicy.TRANSIT_SHOW的動畫。

       確定好窗口的動畫類型之後,WindowManagerService類的成員函數applyEnterAnimationLocked就調用另外一個成員函數applyAnimationLocked來爲窗口創建一個動畫了。接下來我們先分析窗口在關閉的過程中所設置的動畫類型,然後再來分析WindowManagerService類的成員函數applyAnimationLocked的實現。

       從前面Android窗口管理服務WindowManagerService計算Activity窗口大小的過程分析一文可以知道,當應用程序進程請求WindowManagerService服務刷新一個窗口的時候,會調用到WindowManagerService類的成員函數relayoutWindow。WindowManagerService類的成員函數relayoutWindow在執行的過程中,如果發現需要將一個窗口從可見狀態設置爲不可見狀態時,也就是發現需要關閉一個窗口時,就會對該窗口設置一個退出動出,如下所示:


public class WindowManagerService extends IWindowManager.Stub 
        implements Watchdog.Monitor { 
    ...... 
                                        
    public int relayoutWindow(Session session, IWindow client, 
            WindowManager.LayoutParams attrs, int requestedWidth, 
            int requestedHeight, int viewVisibility, boolean insetsPending, 
            Rect outFrame, Rect outContentInsets, Rect outVisibleInsets, 
            Configuration outConfig, Surface outSurface) { 
        ...... 
                                         
        synchronized(mWindowMap) { 
            WindowState win = windowForClientLocked(session, client, false);
            ......
            if (viewVisibility == View.VISIBLE &&
                    (win.mAppToken == null || !win.mAppToken.clientHidden)) {
                ......
            } else {
                ......
                if (win.mSurface != null) {
                    ......
                    // If we are not currently running the exit animation, we
                    // need to see about starting one.
                    if (!win.mExiting || win.mSurfacePendingDestroy) {
                        // Try starting an animation; if there isn't one, we
                        // can destroy the surface right away.
                        int transit = WindowManagerPolicy.TRANSIT_EXIT;
                        ......
                        if (!win.mSurfacePendingDestroy && win.isWinVisibleLw() &&
                              applyAnimationLocked(win, transit, false)) {
                            ......
                            win.mExiting = true;
                        }
                        ......
                    }
                    ......
                }
                ......
            }
            ......
        }
        ......
    }
    ......
}

       這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。


       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來實現的。

       從上面的分析就可以知道,無論是窗口在打開時所需要的進入動畫,還是窗口在關閉時所需要的退出動畫,都是通過調用WindowManagerService類的成員函數applyAnimationLocked來設置的,它的實現如下所示:


public class WindowManagerService extends IWindowManager.Stub 
        implements Watchdog.Monitor { 
    ...... 
                                      
    private boolean applyAnimationLocked(WindowState win,
            int transit, boolean isEntrance) {
        if (win.mLocalAnimating && win.mAnimationIsEntrance == isEntrance) {
            // If we are trying to apply an animation, but already running
            // an animation of the same type, then just leave that one alone.
            return true;
        }
        // Only apply an animation if the display isn't frozen.  If it is
        // frozen, there is no reason to animate and it can cause strange
        // artifacts when we unfreeze the display if some different animation
        // is running.
        if (!mDisplayFrozen && mPolicy.isScreenOn()) {
            int anim = mPolicy.selectAnimationLw(win, transit);
            int attr = -1;
            Animation a = null;
            if (anim != 0) {
                a = AnimationUtils.loadAnimation(mContext, anim);
            } else {
                switch (transit) {
                    case WindowManagerPolicy.TRANSIT_ENTER:
                        attr = com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_EXIT:
                        attr = com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_SHOW:
                        attr = com.android.internal.R.styleable.WindowAnimation_windowShowAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_HIDE:
                        attr = com.android.internal.R.styleable.WindowAnimation_windowHideAnimation;
                        break;
                }
                if (attr >= 0) {
                    a = loadAnimation(win.mAttrs, attr);
                }
            }
            ......
            if (a != null) {
                ......
                win.setAnimation(a);
                win.mAnimationIsEntrance = isEntrance;
            }
        }
        ......
        return win.mAnimation != null;
    }
    ......
}


       這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。

       參數win描述的是要設置動畫的窗口,參數transit描述的是要設置的動畫的類型,而參數isEntrance描述的是要設置的動畫是進入類型還是退出類型的。

       如果參數win所指向的一個WindowState對象的成員變量mLocalAnimating的值等於true,那麼就說明它所描述的窗口已經被設置過動畫了,並且這個動畫正在顯示的過程中。在這種情況下,如果這個WindowState對象的成員變量mAnimationIsEntrance的值等於參數isEntrance的值,那麼就說明該窗口正在顯示的動畫就是所要求設置的動畫,這時候就不需要給窗口重新設置一個動畫了,因此,WindowManagerService類的成員函數applyAnimationLocked就直接返回了。

       我們假設需要給參數win所描述的窗口設置一個新的動畫,這時候還需要繼續判斷屏幕當前是否是處於非凍結和點亮的狀態的。只有在屏幕不是被凍結並且是點亮的情況下,WindowManagerService類的成員函數applyAnimationLocked才真正需要給參數win所描述的窗口設置一個動畫,否則的話,設置了也是無法顯示的。當WindowManagerService類的成員變量mDisplayFrozen的時候,就說明屏幕不是被凍結的,而當WindowManagerService類的成員變量mPolicy所指向的一個PhoneWindowManager對象的成員函數isScreenOn的返回值等於true的時候,就說明屏幕是點亮的。在滿足上述兩個條件的情況下,WindowManagerService類的成員函數applyAnimationLocked就開始給參數win所描述的窗口創建動畫了。

       WindowManagerService類的成員函數applyAnimationLocked首先是檢查WindowManagerService類的成員變量mPolicy所指向的一個PhoneWindowManager對象是否可以爲參數win所描述的窗口提供一個類型爲transit的動畫。如果可以的話,那麼調用這個PhoneWindowManager對象的成員函數selectAnimationLw的返回值anim就不等於0,這時候WindowManagerService類的成員函數applyAnimationLocked就可以調用AnimationUtils類的靜態成員函數loadAnimation來根據該返回值anim來創建一個動畫,並且保存在變量a中。

       如果WindowManagerService類的成員變量mPolicy所指向的一個PhoneWindowManager對象不可以爲參數win所描述的窗口提供一個類型爲transit的動畫的話,那麼WindowManagerService類的成員函數applyAnimationLocked就需要根據該窗口的佈局參數來創建這個動畫了。這個創建過程分爲兩步執行:

       1. 將參數transit的值轉化爲一個對應的動畫樣式名稱;

       2. 調用WindowManagerService類的成員函數loadAnimation來在指定的窗口布局參數中創建前面第1步所指定樣式名稱的動畫,並且保存在變量a中,其中,指定的窗口布局參數是由WindowState對象win的成員變量mAttrs所指向的一個WindowManager.LayoutParams對象來描述的。

       最後,如果變量a的值不等於null,即前面成功地爲參數win所描述的窗口創建了一個動畫,那麼接下來就會將該動畫設置給參數win所描述的窗口。這是通過參數win所指向的一個WindowState對象的成員函數setAnimation來實現的,實際上就是將變量所指向的一個Animation對象保存在參數win所指向的一個WindowState對象的成員變量mAnimation中。同時,WindowManagerService類的成員函數applyAnimationLocked還會將參數isEntrance的值保存在參數win所指向的一個WindowState對象的成員變量mAnimationIsEntrance,以表明前面給它所設置的動畫是屬於進入類型還是退出類型的。

       2. 切換動畫的設置過程

       從前面Android窗口管理服務WindowManagerService切換Activity窗口(App Transition)的過程分析一文可以知道,如果一個窗口屬於一個Activity組件窗口,那麼當該Activity組件被切換的時候,就會被設置一個切換動畫,這是通過調用WindowManagerService類的成員函數setTokenVisibilityLocked來實現的,如下所示:


public class WindowManagerService extends IWindowManager.Stub 
        implements Watchdog.Monitor { 
    ...... 
                                  
    boolean setTokenVisibilityLocked(AppWindowToken wtoken, WindowManager.LayoutParams lp,
            boolean visible, int transit, boolean performLayout) {
        boolean delayed = false;
        ......
        if (wtoken.hidden == visible) {
            final int N = wtoken.allAppWindows.size();
            ......
            boolean runningAppAnimation = false;
            if (transit != WindowManagerPolicy.TRANSIT_UNSET) {
                if (wtoken.animation == sDummyAnimation) {
                    wtoken.animation = null;
                }
                applyAnimationLocked(wtoken, lp, transit, visible);
                ......
                if (wtoken.animation != null) {
                    delayed = runningAppAnimation = true;
                }
            }
            for (int i=0; i<N; i++) {
                WindowState win = wtoken.allAppWindows.get(i);
                ......
                if (visible) {
                    if (!win.isVisibleNow()) {
                        if (!runningAppAnimation) {
                            applyAnimationLocked(win,
                                    WindowManagerPolicy.TRANSIT_ENTER, true);
                        }
                        ......
                    }
                } else if (win.isVisibleNow()) {
                    if (!runningAppAnimation) {
                        applyAnimationLocked(win,
                                WindowManagerPolicy.TRANSIT_EXIT, false);
                    }
                    ......
                }
            }
            ......
        }
        if (wtoken.animation != null) {
            delayed = true;
        }
        return delayed;
    }
    ......
}

       這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。


       參數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對象。從前面Android窗口管理服務WindowManagerService切換Activity窗口(App Transition)的過程分析一文可以知道,一個需要參與切換的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組件所對應的窗口設置動畫是通過調用WindowManagerService類的三個參數版本的成員函數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組件的切換動畫的創建過程,如下所示:


public class WindowManagerService extends IWindowManager.Stub 
        implements Watchdog.Monitor { 
    ...... 
                              
    private boolean applyAnimationLocked(AppWindowToken wtoken,
            WindowManager.LayoutParams lp, int transit, boolean enter) {
        // Only apply an animation if the display isn't frozen.  If it is
        // frozen, there is no reason to animate and it can cause strange
        // artifacts when we unfreeze the display if some different animation
        // is running.
        if (!mDisplayFrozen && mPolicy.isScreenOn()) {
            Animation a;
            if (lp != null && (lp.flags & FLAG_COMPATIBLE_WINDOW) != 0) {
                a = new FadeInOutAnimation(enter);
                ......
            } else if (mNextAppTransitionPackage != null) {
                a = loadAnimation(mNextAppTransitionPackage, enter ?
                        mNextAppTransitionEnter : mNextAppTransitionExit);
            } else {
                int animAttr = 0;
                switch (transit) {
                    case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN:
                        animAttr = enter
                                ? com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation
                                : com.android.internal.R.styleable.WindowAnimation_activityOpenExitAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE:
                        animAttr = enter
                                ? com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation
                                : com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_TASK_OPEN:
                        animAttr = enter
                                ? com.android.internal.R.styleable.WindowAnimation_taskOpenEnterAnimation
                                : com.android.internal.R.styleable.WindowAnimation_taskOpenExitAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_TASK_CLOSE:
                        animAttr = enter
                                ? com.android.internal.R.styleable.WindowAnimation_taskCloseEnterAnimation
                                : com.android.internal.R.styleable.WindowAnimation_taskCloseExitAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT:
                        animAttr = enter
                                ? com.android.internal.R.styleable.WindowAnimation_taskToFrontEnterAnimation
                                : com.android.internal.R.styleable.WindowAnimation_taskToFrontExitAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_TASK_TO_BACK:
                        animAttr = enter
                                ? com.android.internal.R.styleable.WindowAnimation_taskToBackEnterAnimation
                                : com.android.internal.R.styleable.WindowAnimation_taskToBackExitAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN:
                        animAttr = enter
                                ? com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation
                                : com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE:
                        animAttr = enter
                                ? com.android.internal.R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
                                : com.android.internal.R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN:
                        animAttr = enter
                                ? com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
                                : com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE:
                        animAttr = enter
                                ? com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation
                                : com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation;
                        break;
                }
                a = animAttr != 0 ? loadAnimation(lp, animAttr) : null;
                ......
            }
            if (a != null) {
                ......
                wtoken.setAnimation(a);
            }
        }
                                     
        ......
        return wtoken.animation != null;
    }
    ......
}

       這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。

       WindowManagerService類的四個參數版本的成員函數applyAnimationLocked和三個參數版本的成員函數applyAnimationLocked的實現是類似的,不過它是爲Activity組件創建動畫,並且:

       1. 如果參數lp所描述的佈局參數表明它是用來描述一個兼容窗口的,即它所指向的一個WindowManager.LayoutParams對象的成員變量flags的FLAG_COMPATIBLE_WINDOW位不等於0,那麼創建的切換動畫就固定爲FadeInOutAnimation。

       2. 如果WindowManagerService類的成員變量mNextAppTransitionPackage的值不等於null,那麼就說明Package名稱爲mNextAppTransitionPackage的Activity組件指定了一個自定義的切換動畫,其中,指定的進入動畫的類型由WindowManagerService類的成員變量mNextAppTransitionEnter來描述,指定的退出動畫的類型由WindowManagerService類的成員變量mNextAppTransitionExit來描述。在這種情況下,WindowManagerService服務就會使用這個指定的切換動畫,而不是使用默認的切換動畫。一般來說,一個個Activity組件在調用成員函數startActivity來通知ActivityManagerService服務啓動另外一個Activity之外,可以馬上調用另外一個成員函數overridePendingTransition來指定自定久的切換動畫。

       3. 切換動畫的類型主要分三類:第一類是和Activity相關的;第二類是和Task相關的;第三類是和Wallpaper相關的。這一點可以參考前面Android窗口管理服務WindowManagerService切換Activity窗口(App Transition)的過程分析一文。

       切換動畫創建成功之後,就會調用參數wtoken所指向的一個AppWindowToken對象的成員函數setAnimation來保存在其成員變量animation中,這樣就表示它所描述的Activity組件被設置了一個切換動畫。

       二. 窗口動畫的顯示框架

       窗口動畫是在WindowManagerService服務刷新系統UI的時候顯示的。從前面前面Android窗口管理服務WindowManagerService切換Activity窗口(App Transition)的過程分析一文可以知道,WindowManagerService服務刷新系統UI是通過調用WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLockedInner來實現的,接下來我們就主要分析與窗口動畫的顯示框架相關的邏輯,如下所示:


public class WindowManagerService extends IWindowManager.Stub   
        implements Watchdog.Monitor {   
    ......   
                          
    private final void performLayoutAndPlaceSurfacesLockedInner(   
            boolean recoveringMemory) {   
        ......   
                          
        Surface.openTransaction();   
        ......   
                          
        try {   
            ......   
            int repeats = 0;   
            int changes = 0;   
                                      
            do {   
                repeats++;   
                if (repeats > 6) {   
                    ......   
                    break;   
                }   
                          
                // 1. 計算各個窗口的大小 
                // FIRST LOOP: Perform a layout, if needed.   
                if (repeats < 4) {   
                    changes = performLayoutLockedInner();   
                    if (changes != 0) {   
                        continue;   
                    }   
                } else {   
                    Slog.w(TAG, "Layout repeat skipped after too many iterations");   
                    changes = 0;   
                }   
                        
                // 2. 推進各個Activity組件的切換動畫 
                // Update animations of all applications, including those 
                // associated with exiting/removed apps 
                boolean tokensAnimating = false;
                final int NAT = mAppTokens.size();
                for (i=0; i<NAT; i++) {
                    if (mAppTokens.get(i).stepAnimationLocked(currentTime, dw, dh)) {
                        tokensAnimating = true;
                    }
                }
                final int NEAT = mExitingAppTokens.size();
                for (i=0; i<NEAT; i++) {
                    if (mExitingAppTokens.get(i).stepAnimationLocked(currentTime, dw, dh)) {
                        tokensAnimating = true;
                    }
                } 
                          
                // 3. 推進各個窗口的動畫 
                // SECOND LOOP: Execute animations and update visibility of windows.   
                ...... 
                animating = tokensAnimating;
                ......
                mPolicy.beginAnimationLw(dw, dh);
                final int N = mWindows.size();
                for (i=N-1; i>=0; i--) {
                    WindowState w = mWindows.get(i);
                    ......
                    if (w.mSurface != null) {
                        // Execute animation.
                        ......
                        if (w.stepAnimationLocked(currentTime, dw, dh)) {
                            animating = true;
                            ......
                        }
                        ......
                    }
                    ......
                    mPolicy.animatingWindowLw(w, attrs);
                }
                        
                ...... 
                changes |= mPolicy.finishAnimationLw();
                ......
                                          
            } while (changes != 0);   
                          
            // 4. 更新各個窗口的繪圖表面 
            // THIRD LOOP: Update the surfaces of all windows.   
            ......   
            final int N = mWindows.size();
            for (i=N-1; i>=0; i--) {
                WindowState w = mWindows.get(i);
                ......
                if (w.mSurface != null) {
                    ......
                                          
                    //計算實際要顯示的大小和位置
                    w.computeShownFrameLocked();
                    ......
                    //設置大小和位置等
                    ......
                    if (w.mAttachedHidden || !w.isReadyForDisplay()) {
                        ......
                    } else if (w.mLastLayer != w.mAnimLayer
                            || w.mLastAlpha != w.mShownAlpha
                            || w.mLastDsDx != w.mDsDx
                            || w.mLastDtDx != w.mDtDx
                            || w.mLastDsDy != w.mDsDy
                            || w.mLastDtDy != w.mDtDy
                            || w.mLastHScale != w.mHScale
                            || w.mLastVScale != w.mVScale
                            || w.mLastHidden) {
                        ......
                        //設置Z軸位置、Alpha通道和變換矩陣
                        ......
                        if (w.mLastHidden && !w.mDrawPending
                                && !w.mCommitDrawPending
                                && !w.mReadyToShow) {
                            ......
                            if (showSurfaceRobustlyLocked(w)) {
                                w.mHasDrawn = true;
                                w.mLastHidden = false;
                            }
                                                
                            ......
                       }
                       ......
                    }
                    ......
                }
                ......
            }
            ......
        } catch (RuntimeException e) {   
            ......   
        }   
                          
        ......   
                          
        Surface.closeTransaction();   
                          
        ......   
                        
        // 5. 檢查是否需要再一次刷新系統UI 
        if (needRelayout) { 
            requestAnimationLocked(0); 
        } else if (animating) { 
            requestAnimationLocked(currentTime+(1000/60)-SystemClock.uptimeMillis()); 
        } 
                         
        ...... 
    }   
                          
    ......   
}


       這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。


       WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLockedInner按照以下步驟來顯示窗口動畫:

       第一步是調用WindowManagerService類的成員函數performLayoutLockedInner來計算各個窗口的大小以及位置。只有知道了窗口的大小以及位置之後,我們才能它應用一個變換矩陣,然後得到窗口下一步要顯示的大小以及位置。這一步可以參考前面Android窗口管理服務WindowManagerService計算Activity窗口大小的過程分析一文。

       第二步是推進各個Activity組件的切換動畫,也就是計算每一個Activity組件下一步所要執行的動畫。注意,只有那些正在參與切換操作的Activity組件才設置有動畫,而只有設置有切換動畫的Activity組件才需要計算它們下一步所要執行的動畫。從前面第一部分的內容可以知道,如果一個Activity組件參與了切換操作,那麼用來描述它的一個AppWindowToken對象的成員變量animation就會指向一個Animation對象,用來描述一個切換動畫。

       用來描述系統當前的Activity組件的AppWindowToken對象有一部分保存在WindowManagerService類的成員變量mAppTokens所描述的一個ArrayList中,另外一部分保存在WindowManagerService類的成員變量mExitingAppTokens所描述的一個ArrayList中。其中,保存在成員變量mAppTokens中的AppWindowToken對象有可能描述的就是正在打開的Activity組件,而保存在成員變量mExitingAppTokens中的AppWindowToken對象描述的就是正在關閉的Activity組件,這兩類Activity組件都是參與了切換操作的,因此,我們需要計算它們下一步所要執行的動畫,這是通過調用用來描述它們的AppWindowToken對象的成員函數stepAnimationLocked來實現的。注意,對於那些不是正在打開或者正在關閉的Activity組件,調用用來描述它們的AppWindowToken對象的成員函數stepAnimationLocked不會有任何效果,因爲這些AppWindowToken對象的成員變量animation的值等於null。

       在這一步中,如果有任何一個正在打開或者正在關閉的Activity組件的動畫還沒有執行完成,那麼調用用來描述它的AppWindowToken對象的成員函數stepAnimationLocked的返回值就會等於true,這時候變量tokensAnimating的值也會等於true,表示Activity組件的切換動畫還在進行中。後面我們再詳細分析AppWindowToken類的成員函數stepAnimationLocked的實現。

       第三步是推進各個Activity組件的動畫,也就是計算每一個窗口下一步所要執行的動畫。注意,只有那些被設置有動畫的窗口才需要計算它們下一下所要執行的動畫。從前面第一部分的內容可以知道,如果一個窗口設置有動畫,那麼用來描述它的一個WindowState對象的成員變量mAnimation就會指向一個Animation對象,用來描述一個窗口動畫。

       用來描述系統當前的窗口的WindowState對象保存在WindowManagerService類的成員變量mWindows所描述的一個ArrayList中,只要調用這些WindowState對象的成員函數stepAnimationLocked,就可以計算它們所描述的窗口下一步所要執行的動畫。同樣,對於那些沒有設置動畫的窗口來說,調用用來描述它們的WindowState對象的成員函數stepAnimationLocked是不會有任何效果的,因爲這些WindowState對象的成員變量mAnimation的值等於null。

       注意,對於那些還沒有創建繪圖表面的窗口來說,即使它們設置有動畫,在這一步裏面,也是不需要計算它們下一步所要執行的動畫的,這是因爲一個沒有繪圖表面的窗口是無法對它應用動畫的。

       此外,在計算所有窗口下一步要執行的動畫之前以及之後,會通知系統的窗口管理策略類窗口下一次要執行的動畫就要開始計算了以及已經計算結束了,同時,每計算完成一個窗口下一次要執行的動畫之後,也會通知系統的窗口管理策略類該窗口下一次要執行的動畫已經計算完成了,這分別是通過調用WindowManagerService類的成員變量mPolicy所指向的一個PhoneWindowManager對象的成員函數beginAnimationLw、finishAnimationLw和animatingWindowLw來實現的。

       我們知道,在Android系統中,有兩個特殊的系統窗口,即狀態欄窗口和鎖屏窗口。如果當前激活的窗口是一個全屏窗口的時候,那麼系統的狀態欄窗口就會被隱藏。同時,在顯示鎖屏窗口的時候,一般系統中的所有其它窗口都會被它擋在後面。但是有些被設置了FLAG_SHOW_WHEN_LOCKED標誌位的窗口,能夠顯示在鎖屏窗口的上面。還有些被設置了FLAG_DISMISS_KEYGUARD位的窗口,它們能夠解散系統當前正在顯示的鎖屏窗口。這些全屏窗口、能夠顯示在鎖屏窗口上面的窗口以及能夠解散鎖屏窗口的窗口,會影響到系統的狀態欄窗口和鎖屏窗口是否需要顯示的問題,而這是需要由窗口管理策略類來決定的。因此,在計算所有窗口下一步要執行的動畫的前後,以及計算每一個窗口下一次要執行的動畫的過程中,都要通知一下窗口管理策略類,以便它可以決定是否需要顯示系統的狀態欄窗口和鎖屏窗口。

       窗口管理策略類按照以下方法來決定是否需要顯示系統的狀態欄窗口和鎖屏窗口:

       1. 在計算所有窗口下一步要執行的動畫之前,假設狀態欄窗口是不可見的,而鎖屏窗口是可見的、不可解散的;

       2. 在計算一個窗口下一步要執行的動畫的過程中,檢查該窗口是否是可見的以及全屏顯示的。如果該窗口是可見的,但是不是全屏的,那麼就意味着狀態欄窗口是可見的。同時,如果該窗口是可見的,並且也是全屏的,那麼就會繼續檢查該窗口是否被設置了FLAG_SHOW_WHEN_LOCKED和FLAG_DISMISS_KEYGUARD標誌位。如果這兩個標誌位分別被設置了,那麼就意味着鎖屏窗口是不可見的和需要解散的。

       3. 在計算完成所有窗口下一步要執行的動畫之後,根據第2步收集到的信息來決定是否要顯示狀態欄窗口以及鎖屏窗口。如果系統當前存在狀態欄窗口,並且第2步收集到的信息表明它是需要顯示的,那麼就會將它的狀態設置爲可見的;另一方面,如果系統當前存在狀態欄窗口,並且第2步收集到的信息表明系統當前有一個全屏的窗口,那麼狀態欄窗口無論如何都是需要隱藏的。如果系統當前存在鎖屏窗口,並且第2步收集到的信息表明它是不需要顯示的或者需要解散的,那麼就會將它的狀態設置不可見,否則的話,就會將它的狀態設置爲可見。

       在計算完成所有窗口下一步要執行的動畫之後,如果狀態欄窗口或者鎖屏窗口的狀態由可見變爲不可見,或者由不可見變爲可見,那麼PhoneWindowManager類的成員函數finishAnimationLw的返回值就會不等於0,這意味着WindowManagerService服務需要重新佈局系統中各個窗口,即重新計算各個窗口的大小、位置以及下一步要執行的動畫等操作,因爲狀態欄窗口或者鎖屏窗口的可見性變化引發窗口堆棧發生變化。這就是上述的第一步、第二步和第三步要放在一個while循環來執行的原因。但是,這個while循環不能無限地執行下去,否則的話,系統的UI就永遠刷新不出來。這個while循環最多允許執行7次,並且各個窗口的大小和位置的重複計算次數最多爲4次。

       第四步是更新各個窗口的繪圖表面。既然是更新各個窗口的繪圖表面,那麼就意味着只有具有繪圖表面的窗口才需要更新。一個窗口如果具有繪圖表面,那麼用來描述它的一個WindowState對象的成員變量mSurface的值就不等於null。對於具有繪圖表面的窗口,這一步主要是執行以下操作:

       1. 調用用來描述它的WindowState對象的成員函數computeShownFrameLocked來計算它實際要顯示的大小和位置,這是需要考慮它的原始大小和位置,以及它所被設置的變換矩陣。這個變換矩陣是通過組合它的動畫來得到的,其中包括窗口本身所設置的及其所附加在的窗口所設置的動畫,還有窗口所屬的Activity組件所設置的切換動畫。後面我們再分析WindowState類的成員函數computeShownFrameLocked的實現。

       2. 將第1步計算得到的窗口實際要顯示的大小以及位置設置到SurfaceFlinger服務中去。這一點可以參考前面Android窗口管理服務WindowManagerService計算窗口Z軸位置的過程分析一文。

       3. 如果窗口當前已經就準備就緒顯示,即用來描述它的WindowState對象的成員函數isReadyForDisplay的返回值等於true,並且它所附加在的窗口是可見的,即用來描述它的WindowState對象的成員變量mAttachedHidden的值等於false,那麼只要滿足以下條件之一,就需要考慮通知SurfaceFlinger服務將該窗口的狀態設置爲可見的:

       A. 該窗口的Z軸位置發生了變化,即用來描述該窗口的WindowState對象的成員變量mLastLayer和mAnimLayer的值不相等;

       B. 該窗口的Alpha通道值發生了變化,即用來描述該窗口的WindowState對象的成員變量mLastAlpha和mShownAlpha的值不相等;

       C. 該窗口的變換矩陣發生了變化,即用來描述該窗口的WindowState對象的成員變量mLastDsDx、mLastDtDx、mLastDsDy和mLastDtDy的值不等於mDsDx、mDtDx、mDsDy和mDtDy的值;

       D. 該窗口在寬度和高度上所設置的縮放因子發生了變化,即用來描述該窗口的WindowState對象的成員變量mLastHScale和mLastVScale的值不等於mHScale和mVScale的值;

       E. 該窗口在上一次系統UI刷新時是不可見的,即用來描述該窗口的WindowState對象的成員變量mLastHidden的值等於true。

       如果一個具有繪圖表面的窗口滿足上述條件,那麼這一步會將該窗口的最新Z軸位置、Alpha通道值和變換矩陣設置到SurfaceFlinger服務中去,這一點可以參考前面Android窗口管理服務WindowManagerService計算窗口Z軸位置的過程分析一文。

       此外,如果一個具有繪圖表面的窗口滿足的是上述的條件E,並且還滿足以下條件:

       F. 它的UI已經繪製完成,即用來描述它的WindowState對象的成員變量mDrawPending和mCommitDrawPending值等於false;

       G. 它不是處於等待同一個窗口令牌的其它窗口的完成UI繪製的狀態,即用來描述它的WindowState對象的成員變量mReadyToShow的值等於false。

       那麼這一步還會調用WindowManagerService類的成員函數showSurfaceRobustlyLocked來通知SurfaceFlinger服務將該窗口的狀態設置爲可見的。如果能夠成功地通知SurfaceFlinger服務將該窗口的狀態設置爲可見,那麼還會分別將用來描述該窗口的WindowState對象的成員變量mHasDrawn和mLastHidden的值設置爲true和false,表示該窗口的UI已經繪製完成,並且狀態已經設置爲可見。

       注意,上述四步對窗口的操作,例如設置窗口的大小、位置、Alpha通道值和變換矩陣等,都是在一個事務中執行的。這個事務從調用Surface類的靜態成員函數openTransaction時開始,一直到調用Surface類的靜態成員函數closeTransaction時結束。當事務結束的時候,前面所設置的窗口的狀態纔會批量地同步到SurfaceFlinger服務中去,這樣就可以避免每修改一個窗口的一個狀態,就觸發SurfaceFlinger服務刷新一次系統的UI,造成屏幕閃爍。

       第五步是檢查是否需要再一次刷新系統UI。在三種情況下,是需要再一次刷新系統UI的。第一種情況是發現此時Activity組件的切換動畫已經顯示完成了;第二種情況是發現前面的操作會導致壁紙窗口的目標窗口被銷燬了;第三種情況是發現此時還有窗口的動畫未結束。

       由於在Activity組件的切換過程中,很多操作都會被延遲執行,例如,窗口的Z軸位置的計算,因此,當出現第一種情況下,就需要重新執行這些延遲操作,主要就是重建窗口堆棧,以及計算每一個窗口的Z軸位置。在第二種情況下,由於當壁紙窗口的目標窗品被銷燬之後,因此,就需要重新調整壁紙窗口在窗口堆棧中的位置。這兩種情況都會導致變量needRelayout的值就等於true,表示需要馬上重新刷新系統UI,這是通過以0爲參數來調用WindowManagerService類的成員函數requestAnimationLocked來實現的。

       在第三種情況中,變量animating的值會等於true,表示有窗口的動畫還未結束,有可能是窗口本身的動畫尚未結束,也有可能是Activity組件的切換動畫尚未結束。在WindowManagerService服務中,窗口動畫是以60幀每秒(fps)的速度來顯示的,因此,這一步就會以約等於1000/60的參數來調用WindowManagerService類的成員函數requestAnimationLocked,表示要在1/60秒後再次刷新系統的UI,這樣就可以把動畫效果展現出來。

       三.  窗口動畫的推進過程

       從前面第一部分的內容可以知道,窗口動畫有可能是是來自窗口本身所設置的動畫,也有可能是來自於其宿主Activity組件所設置的切換動畫,接下來我們就分別分析這兩種類型的動畫的推進過程。

       1. 窗口動畫的推進過程

       從前面第二部分的內容可以知道,窗口動畫的推進過程是由WindowState類的成員函數stepAnimationLocked的實現的,如下所示:


public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor {
    ......
    private final class WindowState implements WindowManagerPolicy.WindowState {
        ......
        // Currently running animation.
        boolean mAnimating;
        boolean mLocalAnimating;
        Animation mAnimation;
        ......
        boolean mHasLocalTransformation;
        final Transformation mTransformation = new Transformation();
        ......
        // This must be called while inside a transaction.  Returns true if
        // there is more animation to run.
        boolean stepAnimationLocked(long currentTime, int dw, int dh) {
            if (!mDisplayFrozen && mPolicy.isScreenOn()) {
                // We will run animations as long as the display isn't frozen.
                if (!mDrawPending && !mCommitDrawPending && mAnimation != null) {
                    ......
                    mHasLocalTransformation = true;
                    if (!mLocalAnimating) {
                        ......
                        mAnimation.initialize(mFrame.width(), mFrame.height(), dw, dh);
                        mAnimation.setStartTime(currentTime);
                        mLocalAnimating = true;
                        mAnimating = true;
                    }
                    mTransformation.clear();
                    final boolean more = mAnimation.getTransformation(
                        currentTime, mTransformation);
                    ......
                    if (more) {
                        // we're not done!
                        return true;
                    }
                    ......
                }
                ......
            }
            ......
            mAnimating = false;
            mLocalAnimating = false;
            mAnimation = null;
            ......
            mHasLocalTransformation = false;
            ......
            mTransformation.clear();
            ......
            return false;
        }
        ......
    }
    ......
}

       這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。

       WindowState類有幾個成員變量是用來描述窗口的動畫狀態的,其中:

--mAnimating,表示窗口是否處於正在顯示動畫的過程中。

--mLocalAnimating,表示窗口的動畫是否已經初始化過了。一個動畫只有經過初始化之後,才能開始執行。

--mAnimation,表示窗口的動畫對象。

--mHasLocalTransformation,表示窗口的動畫是否是一個本地動畫,即這個動畫是否是來自窗口本身的。有時候一個窗口雖然正在顯示動畫,但是這個動畫有可能是其宿主Activity組件的切換動畫。在這種情況下,mHasLocalTransformation的值就會等於false。

--mTransformation,表示一個變換矩陣,是根據窗口動畫的當前推進狀態來計算得到的,用來改變窗口的大小以及位置。

       窗口動畫的推進只有以下四個條件均滿足的情況下才會執行:

       (1). 屏幕沒有被凍結,即WindowManagerService類的成員變量mDisplayFrozen的值等於false;

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

       (3). 窗口的UI已經繪製完成並且已經提交,即WindowState類的成員變量mDrawPending和mCommitDrawPending的值均等於false;

       (4). 窗口已經被設置過動畫,即WindowState類的成員變量mAnimation的值不等於null。

       一旦滿足上述四個條件,那麼WindowState類的成員函數stepAnimationLocked就會執行以下操作:

       (1). 將成員變量mHasLocalTransformation的值設置爲true,表明窗口當前具有一個本地動畫。

       (2). 檢查成員變量mLocalAnimating的值是否等於false。如果等於false的話,那麼就說明窗口的動畫還沒有經過初始化,這時候就會對該動畫進行初始化,這是通過調用成員變量mAnimation所指向的一個Animation對象的成員函數initialize和setStartTime來實現的。窗口動畫初始化完成之後,還需要將成員變量mLocalAnimating和mAnimating的值均設置爲true,表明窗口動畫已經初始化過了,並且窗口當前正在執行動畫的過程中。

       (3). 將成員變量mTransformation所描述的變換矩陣的數據清空。

       (4). 調用成員變量mAnimation所指向的一個Animation對象的成員函數getTransformation來計算窗口動畫下一步所對應的變換矩陣,並且將這個變換矩陣的數據保存在成員變量mTransformation。

       (5). 如果窗口的動畫尚未結束顯示,那麼調用成員變量mAnimation所指向的一個Animation對象的成員函數getTransformation得到的返回值more就會等於true,這時候窗口動畫的向前推進操作就完成了。

       (6). 如果窗口的動畫已經結束顯示,那麼調用成員變量mAnimation所指向的一個Animation對象的成員函數getTransformation得到的返回值more就會等於false,這時候就需要執行一些清理工作。這些清理工作包括將成員變量mAnimating、mLocalAnimating和mHasLocalTransformation的值設置爲false,以及將成員變量mAnimation的值設置爲null,還有將成員變量mTransformation所描述的變換矩陣的數據清空。

       最後,如果窗口的動畫尚未結束顯示,那麼WindowState類的成員函數stepAnimationLocked會返回一個true值給調用者,否則的話,就會返回一個false值給調用者。

       2. Activity組件切換動畫的推進過程

       從前面第二部分的內容可以知道,Activity組件切換動畫的推進過程是由AppWindowToken類的成員函數stepAnimationLocked來實現的,如下所示:


public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor {
    ......
    class AppWindowToken extends WindowToken {
        ......
        boolean animating;
        Animation animation;
        boolean hasTransformation;
        final Transformation transformation = new Transformation();
        ......
        // This must be called while inside a transaction.
        boolean stepAnimationLocked(long currentTime, int dw, int dh) {
            if (!mDisplayFrozen && mPolicy.isScreenOn()) {
                // We will run animations as long as the display isn't frozen.
                if (animation == sDummyAnimation) {
                    // This guy is going to animate, but not yet.  For now count
                    // it as not animating for purposes of scheduling transactions;
                    // when it is really time to animate, this will be set to
                    // a real animation and the next call will execute normally.
                    return false;
                }
                if ((allDrawn || animating || startingDisplayed) && animation != null) {
                    if (!animating) {
                        ......
                        animation.initialize(dw, dh, dw, dh);
                        animation.setStartTime(currentTime);
                        animating = true;
                    }
                    transformation.clear();
                    final boolean more = animation.getTransformation(
                        currentTime, transformation);
                    ......
                    if (more) {
                        // we're done!
                        hasTransformation = true;
                        return true;
                    }
                    ......
                    animation = null;
                }
            }
            ......
            hasTransformation = false;
            ......
            animating = false;
            ......
            transformation.clear();
            ......
            return false;
        }
        ......
    }
    ......
}

       這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。

       AppWindowToken類有幾個成員變量是用來描述Activity組件的切換動畫狀態的,其中:

--animating,表示Activity組件的切換動畫是否已經初始化過了。

--animation,表示Activity組件的切換動畫對象。

--mHasTransformation,表示Activity組件是否具有切換動畫。

--transformation,表示一個變換矩陣,是根據Activity組件切換動畫的當前推進狀態來計算得到的,用來改變Activity組件窗口的大小以及位置。

       Activity組件切換動畫的推進只有以下五個條件均滿足的情況下才會執行:

       (1). 屏幕沒有被凍結,即WindowManagerService類的成員變量mDisplayFrozen的值等於false。

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

       (3). Activity組件所設置的切換動畫不是一個啞動畫,即AppWindowToken類的成員變量animation與WindowManagerService類的靜態成員變量sDummyAnimation不是指向同一個Animation對象。從前面Android窗口管理服務WindowManagerService切換Activity窗口(App Transition)的過程分析一文可以知道,一個Activity組件在進行切換之前,用來描述它的一個AppWindowToken對象的成員變量animation的值會被設置爲sDummyAnimation,用來表示它準備要執行一個切換動畫,但是這個切換動畫還沒有設置。

       (4). Activity組件的所有窗口的UI都已經繪製完成,或者Activity組件的切換動畫已經初始化過了,或者Activity組件的啓動窗口已經結束顯示,即AppWindowToken類的成員變量allDrawn、animating和startingDisplayed中的其中一個的值等於true。

       (5). Activity組件已經被設置過切換動畫,即AppWindowToken類的成員變量animation的值不等於null。

       一旦滿足上述五個條件,那麼AppWindowToken類的成員函數stepAnimationLocked就會執行以下操作:

       (1). 檢查成員變量animating的值是否等於false。如果等於false的話,那麼就說明Activity組件的切換動畫還沒有經過初始化,這時候就會對該動畫進行初始化,這是通過調用成員變量animation所指向的一個Animation對象的成員函數initialize和setStartTime來實現的。窗口動畫初始化完成之後,還需要將成員變量animating的值設置爲true,表明窗口動畫已經初始化過了。

       (2). 將成員變量transformation所描述的變換矩陣的數據清空。

       (3). 調用成員變量animation所指向的一個Animation對象的成員函數getTransformation來計算Activity組件切換動畫下一步所對應的變換矩陣,並且將這個變換矩陣的數據保存在成員變量transformation。

       (4). 如果Activity組件切換動畫尚未結束顯示,那麼調用成員變量animation所指向的一個Animation對象的成員函數getTransformation得到的返回值more就會等於true,這時候Activity組件切換動畫的向前推進操作就完成了。

       (6). 如果Activity組件切換動畫已經結束顯示,那麼調用成員變量animation所指向的一個Animation對象的成員函數getTransformation得到的返回值more就會等於false,這時候就需要執行一些清理工作。這些清理工作包括將成員變量animating和hasTransformation的值設置爲false,以及將成員變量animation的值設置爲null,還有將成員變量transformation所描述的變換矩陣的數據清空。

       最後,如果窗口的動畫尚未結束顯示,那麼AppWindowToken類的成員函數stepAnimationLocked就會將成員變量hasTransformation的值設置爲true,並且返回一個true值給調用者,否則的話,就會返回一個false值給調用者。

       四. 窗口動畫的合成過程

       從前面第二部分的內容可以知道,WindowState類的成員函數computeShownFrameLocked負責合成窗口的動畫,包括窗口本身所設置的進入(退出)動畫、從被附加窗口傳遞過來的動畫,以及宿主Activity組件傳遞過來的切換動畫。窗口的這三個動畫合成之後,就可以得到一個變換矩陣。將這個變換矩陣應用到窗口的原始大小和位置上去,就可以得到窗口經過動畫變換後所得到的位置和大小。

       WindowState類的成員函數computeShownFrameLocked的實現如下所示:


public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor {
    ......
    private final class WindowState implements WindowManagerPolicy.WindowState {
        ......
        // Actual frame shown on-screen (may be modified by animation)
        final Rect mShownFrame = new Rect();
        ......
        // Current transformation being applied.
        float mDsDx=1, mDtDx=0, mDsDy=0, mDtDy=1;
        ......
        // "Real" frame that the application sees.
        final Rect mFrame = new Rect();
        ......
        void computeShownFrameLocked() {
            final boolean selfTransformation = mHasLocalTransformation;
            Transformation attachedTransformation =
                    (mAttachedWindow != null && mAttachedWindow.mHasLocalTransformation)
                    ? mAttachedWindow.mTransformation : null;
            Transformation appTransformation =
                    (mAppToken != null && mAppToken.hasTransformation)
                    ? mAppToken.transformation : null;
            // Wallpapers are animated based on the "real" window they
            // are currently targeting.
            if (mAttrs.type == TYPE_WALLPAPER && mLowerWallpaperTarget == null
                    && mWallpaperTarget != null) {
                if (mWallpaperTarget.mHasLocalTransformation &&
                        mWallpaperTarget.mAnimation != null &&
                        !mWallpaperTarget.mAnimation.getDetachWallpaper()) {
                    attachedTransformation = mWallpaperTarget.mTransformation;
                    ......
                }
                if (mWallpaperTarget.mAppToken != null &&
                        mWallpaperTarget.mAppToken.hasTransformation &&
                        mWallpaperTarget.mAppToken.animation != null &&
                        !mWallpaperTarget.mAppToken.animation.getDetachWallpaper()) {
                    appTransformation = mWallpaperTarget.mAppToken.transformation;
                    ......
                }
            }
            if (selfTransformation || attachedTransformation != null
                    || appTransformation != null) {
                // cache often used attributes locally
                final Rect frame = mFrame;
                final float tmpFloats[] = mTmpFloats;
                final Matrix tmpMatrix = mTmpMatrix;
                // Compute the desired transformation.
                tmpMatrix.setTranslate(0, 0);
                if (selfTransformation) {
                    tmpMatrix.postConcat(mTransformation.getMatrix());
                }
                tmpMatrix.postTranslate(frame.left, frame.top);
                if (attachedTransformation != null) {
                    tmpMatrix.postConcat(attachedTransformation.getMatrix());
                }
                if (appTransformation != null) {
                    tmpMatrix.postConcat(appTransformation.getMatrix());
                }
                // "convert" it into SurfaceFlinger's format
                // (a 2x2 matrix + an offset)
                // Here we must not transform the position of the surface
                // since it is already included in the transformation.
                //Slog.i(TAG, "Transform: " + matrix);
                tmpMatrix.getValues(tmpFloats);
                mDsDx = tmpFloats[Matrix.MSCALE_X];
                mDtDx = tmpFloats[Matrix.MSKEW_X];
                mDsDy = tmpFloats[Matrix.MSKEW_Y];
                mDtDy = tmpFloats[Matrix.MSCALE_Y];
                int x = (int)tmpFloats[Matrix.MTRANS_X] + mXOffset;
                int y = (int)tmpFloats[Matrix.MTRANS_Y] + mYOffset;
                int w = frame.width();
                int h = frame.height();
                mShownFrame.set(x, y, x+w, y+h);
                ......
                return;
            }
            mShownFrame.set(mFrame);
            if (mXOffset != 0 || mYOffset != 0) {
                mShownFrame.offset(mXOffset, mYOffset);
            }
            ......
            mDsDx = 1;
            mDtDx = 0;
            mDsDy = 0;
            mDtDy = 1;
        }
        ......
    }
    ......
}

       這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。


       在分析WindowState類的成員函數computeShownFrameLocked的實現之前,我們先來看幾個相關的成員變量:

--mFrame,用來描述窗口的實際位置以及大小。它們的計算過程可以參考前面Android窗口管理服務WindowManagerService計算Activity窗口大小的過程分析一文。

--mShownFrame,用來描述窗口當前所要顯示的位置以及大小。

--mDsDx、mDtDx、mDsDy、mDtDy,用來描述窗口的變換矩陣(二維)。

       WindowState類的成員函數computeShownFrameLocked的目標就根據窗口的實際位置、大小,以及窗口的動畫,來計算得到窗口當前所要顯示的位置以及大小。

       注意,在調用WindowState類的成員函數computeShownFrameLocked之前,窗口的實際位置和大小是已經計算好了的,並且窗口的動畫也是已經向前推進好了的。窗口的實際位置和大小的計算過程可以參考前面Android窗口管理服務WindowManagerService計算Activity窗口大小的過程分析一文,而窗口動畫向前推進的過程可以參考前面第三部分的內容。

       WindowState類的成員函數computeShownFrameLocked首先檢查當前正在處理的窗口有多少個動畫是需要合成的,即:

       1. 檢查成員變量mHasLocalTransformation的值是否等於true。如果等於true的話,那麼就會將變量selfTransformation的值也設置爲true,表示窗口本身設置有一個動畫。這個動畫要麼是一個打開窗口類型的動畫,要麼是一個關閉窗口類型的動畫。

       2. 檢查成員變量mAttachedWindow的值是否等於null。如果不等於null的話,那麼就說明當前正在處理的窗口是附加在另外一個窗口之上。如果這個被附加的窗口也設置有動畫,那麼成員變量mAttachedWindow所指向的一個WindowState對象的成員變量mHasLocalTransformation的值就會等於true,這時候用來描述被附加窗口當前所要執行的動畫的一個變換矩陣就由該WindowState對象的成員變量mTransformation來描述。這個變換矩陣最終會被保存在變量attachedTransformation中。

       3. 檢查成員變量mAppToken的值是否等於null。如果不等於null的話,那麼就說明當前正在處理的窗口是一個Activity組件的窗口。如果這個Activity組件設置有切換動畫,那麼成員變量mAppToken所指向的一個AppWindowToken對象的成員變量hasTransformation的值就會等於true,這時候用來描述該Activity組件當前有所要執行的切換動畫的一個變換矩陣就由該AppWindowToken對象的成員變量transformation來描述。這個變換矩陣最終會被保存在變量appTransformation中。

       WindowState類的成員函數computeShownFrameLocked接着檢查當前正在處理的窗口是否是一個壁紙窗口,即檢查成員變量mAttrs所指向的一個WindowManager.LayoutParams對象的成員變量type的值的TYPE_WALLPAPER位是否等於1。如果當前正在處理的窗口是一個壁紙窗口,並且它當前有且僅有一個目標窗口,即WindowManagerService類的成員變量mWallpaperTarget和mLowerWallpaperTarget的值分別不等於null和等於null。在這種情況下,需要對壁紙窗口的動畫進行特殊處理,即:

       1. 要把壁紙窗口所附加在的窗口的動畫設置爲壁紙窗口的目標窗口所附加在的窗口的動畫,即將變量attachedTransformation指向用來描述壁紙窗口的目標窗口所附加在的窗口當前所要執行的動畫的一個變換矩陣,前提是壁紙窗口的目標窗口設置有動畫,並且這個目標窗口在結束動畫過程後不會與壁紙窗口分離。

       2. 要把壁紙窗口的切換動畫設置爲壁紙窗口的目標窗口的切換動畫,即將變量appTransformation指向用來描述壁紙窗口的目標窗口當前所要執行的切換動畫的一個變換矩陣,前提是壁紙窗口的目標窗口設置有切換動畫,並且這個目標窗口結束動畫過程後不會與壁紙窗口分離。

       通過上面的處理之後,如果變量selfTransformation的值等於true,或者變量attachedTransformation和appTransformation的值不等於null,那麼就說明當前正在處理的窗口有動畫需要顯示,因此,接下來就要將這些動畫組合成一個總的變換矩陣。這個總的變換矩陣就是通過WindowState類的成員變量mTmpMatrix來描述的,它是通過下面的步驟來獲得的:

       1. 將該矩陣的偏移位置初始化爲(0, 0),這是通過調用變量tmpMatrix所描述的一個Matrix對象的成員函數setTranslate來實現的。

       2. 如果變量selfTransformation的值等於true,那麼就說明當前正在處理的窗口本身設置有動畫。這個動畫是通過WindowState類的成員變量mTransformation來描述的,調用這個成員變量所指向的一個Transformation對象的成員函數getMatrix就可以獲得一個對應的變換矩陣。將這個變換矩陣與變量tmpMatrix所描述的變換矩陣相乘,就可以得到一箇中間變換矩陣,這是通過調用變量tmpMatrix所指向的一個Matrix對象的成員函數postConcat來實現的。

       3. 將上面第2步得到的中間變換矩陣的偏移位置設置爲當前正在處理的窗口的實際位置,這是通過調用變量tmpMatrix所描述的一個Matrix對象的成員函數postTranslate來實現的,而當前正在處理的窗口的實際位置由變量frame所指向的一個Frame對象的成員變量left和top來描述。注意,變量frame和WindowState類的成員變量mFrame指向的是同一個Frame對象,因此它的成員變量left和top描述的是正在處理的窗口的實際位置。

       4. 如果變量attachedTransformation的值不等於null,那麼就說明當前正在處理的窗口所附加在的窗口設置有動畫。這個動畫就是通過變量attachedTransformation所指向的一個Transformation對象來描述的,調用這個Transformation對象的成員函數getMatrix就可以獲得一個對應的變換矩陣。將這個變換矩陣與變量tmpMatrix所描述的變換矩陣相乘,就可以得到一箇中間變換矩陣,這是通過調用變量tmpMatrix所指向的一個Matrix對象的成員函數postConcat來實現的。

       5. 如果變量attachedTransformation的值不等於null,那麼就說明當前正處理的窗口的宿主Activity組件設置有切換動畫。這個切換動畫就是通過變量appTransformation所指向的一個Transformation對象來描述的,調用這個Transformation對象的成員函數getMatrix就可以獲得一個對應的變換矩陣。將這個變換矩陣與變量tmpMatrix所描述的變換矩陣相乘,就可以得到一箇中間變換矩陣,這是通過調用變量tmpMatrix所指向的一個Matrix對象的成員函數postConcat來實現的。

       經過上面的5個步驟之後,最終得到的變換矩陣就保存在變量tmpMatrix中。由於這個變換矩陣是要設置到SurfaceFlinger服務中去的,因此就需要將這個變換矩陣轉換爲SurfaceFlinger服務所要求的格式。SurfaceFlinger服務所要求的變換矩陣的格式是由窗口在X軸和Y軸上的切變值以及縮放值來表示的,它們可以按照以下的步驟來獲得:

       1. 調用變量tmpMatrix所描述的一個Matrix對象的成員函數getValues來獲得一個數組,並且保存在變量tmpFloats中。

       2. 數組tmpFloats的第Matrix.MSKEW_X和Matrix.MSKEW_Y個位置的值就分別表示窗口在X軸和Y軸上的切變值,分別保存在WindowState類的成員變量mDtDx和mDsDy中。

       3. 數組tmpFloats的第Matrix.MSCALE_X和Matrix.MSCALE_Y個位置的值就分別表示窗口在X軸和Y軸上的縮放值,分別保存在WindowState類的成員變量mDsDx和mDtDy中。

       獲得了當前正在處理的窗口的變換矩陣之後,接下來還要計算當前正在處理的窗口接下來要顯示的位置以及大小。

       前面得到的數組tmpFloats中的第Matrix.MTRANS_X和Matrix.MTRANS_Y個位置的值分別表示窗口在X軸和Y軸上的偏移值,它們實際就是窗口接下來要顯示的位置。從前面Android窗口管理服務WindowManagerService對壁紙窗口(Wallpaper Window)的管理分析一文可以知道,如果當前正在處理的是一個壁紙窗口,那麼WindowState類的成員變量mXOffset和mYOffset描述的就是壁紙窗口相對其目標窗口的偏移值。對於普通的窗口來說,WindowState類的成員變量mXOffset和mYOffset的值等於0。因此,需要將保在在數組tmpFloats中的第Matrix.MTRANS_X和Matrix.MTRANS_Y個位置的值與WindowState類的成員變量mXOffset和mYOffset的值分別相加,才能得到當前正在處理接下來要顯示在的位置。

       由於前面獲得的變換矩陣已經包含了當前正在處理的窗口的大小縮放因子,因此,我們就將當前正在處理的窗口的大小設置爲它的實際大小即可。通過調用變量frame所指向的一個Frame對象的成員函數width和height可以獲得當前正在處理的窗口的實際大小。

       經過上面兩步之後,當前正在處理的窗口接下來要顯示的位置以及大小就計算完成了,其中,位置值保存在變量x和y中,而大小值保存在變量w和h,最終就可以將它們保存在WindowState類的成員變量mShownFrame所指向的一個Frame對象中。

       如果當前正在處理的窗口沒有動畫可以顯示,即變量selfTransformation的值等於false,並且變量attachedTransformation和appTransformation的值均等於null,那麼WindowState類的成員函數computeShownFrameLocked的實現就簡單了,它只要簡單地將成員變量mFrame的內容設置到成員變量mShownFrame中,並且將成員變量mDsDx、mDtDx、mDsDy和mDtDy分別設置爲1、0、0和1即可,表示當前正在處理的窗口既沒有切變變換,也沒有縮放變換。另外,如果WindowState類的成員變量mXOffset或者mYOffset的值不等於0,那麼就需要將它們作來偏移值設置到成員變量mShownFrame所描述的一個Frame對象去,以便可以正確地計算出當前正在處理的窗口的位置。

       至此,我們就分析完成窗口動畫的顯示過程了,整個WindowManagerService服務的分析也到此結束了。WindowManagerService服務可以說是整個Android應用程序框架層最爲複雜的模塊了,它與SurfaceFlinger服務一起爲整個Android系統提供了UI服務,理解它對理解Android系統有着重要的意義。不過,要理解WindowManagerService服務的實現,是必須下些功夫的,同時也希望這個系列的文章能夠幫助到大家。重新學習WindowManagerService服務請參考Android窗口管理服務WindowManagerService的簡要介紹和學習計劃一文。

老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關注!

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