在前一文中,我們分析了Activity元件的切換過程。從這個過程可以知道,所有參與切換操作的視窗都會被設定切換動畫。事實上,一個視窗在打開(關閉)的過程中,除了可能會設定切換動畫之外,它本身也可能會設定有進入(退出)動畫。再進一步地,如果一個視窗是附加在另外一個視窗之上的,那麼被附加視窗所設定的動畫也會同時傳遞給該視窗。本文就詳細分析WindowManagerService服務顯示視窗動畫的原理。
《Android系統源代碼情景分析》一書正在進擊的程式員網(http://0xcc0xcd.com)中連載,點選進入!
在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,歡迎關注!