Bootstrap

WMS 显示窗口动画的原理分析(一)


一个窗口在打开、关闭过程中,可能会被设置三种动画,分别是窗口本身进入、退出(self transformation),从被附加窗口传递过来的动画(attached transformation),宿主activity组件传递过来到切换动画(app transformation),这三个transformation组合在一起形成一个变换矩阵,应用在窗口原始形状之上,完成动画过程。


1.普通动画的设置过程
窗口在打开过程中 ,通过调用WindowState::performShowLocked来实现的
private final class WindowState implements WindowManagerPolicy.WindowState {
......
boolean performShowLocked() {......
if (mReadyToShow && isReadyForDisplay()) {
......
if (!showSurfaceRobustlyLocked(this)) {
return false;
}
......
applyEnterAnimationLocked(this);
......
}
return true;
}
......
}
......
}
performShowLocked首先调用WindowManagerService.java中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);
}
......
}
如果win所指向的mEnterAnimationPending为true,那么说明此窗口正在等待显示,也就是正处于不可见到可见的过程中,applyEnterAnimationLocked会将状态类型设置为WindowManagerPolicy.TRANSIT_ENTER的动画,否则设置为WindowManagerPolicy.TRANSITSHOW的动画。
确定好动画类型后,将调用applyAnimationLocked来为窗口创建一个动画。
接下来先分析关闭动画。
当应用程序进程请求WindowManagerService服务刷新一个窗口的时候,会调用WindowManagerService的relayoutWindow。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;
}
......
}
......
}
......
......
}
......
}
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来实现的。
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;
}
if (a != null) {
......
// 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) { //可以为参数win 所描述的窗口提供一个类型为transit的动画
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);
}
}
......
win.setAnimation(a);
win.mAnimationIsEntrance = isEntrance; //表明设置的动画是属于进入类型还是退出
}
}
......
return win.mAnimation != null;
}
......
}
参数win描述的是要设置动画的窗口,参数transit描述的是要设置的动画的类型,而参数isEntrance描述的是要设置的动画是进入类型还是退出类型的。
如果win所指向的一个WindowState对象的成员变量mLocalAnimating的值等于true,那么就说明它描述的窗口已经被设置过动画了,并且这个动画正在显示的过程中。如果mAnimationIsEntrance的值等于参数isEntrance的值,那么就说明该窗口正在显示的动画就是所要求的动画,直接返回。
如果要给窗口设置一个新的动画,这时候还需要继续判断屏幕当前是否处于非冻结和点亮的状态。只有这种情况才真正需要给参数win所描述的窗口设置一个动画,否则的话,设置也是无法显示的。当WindowManagerService类的成员变量mDisplayFrozen的时候,就说明屏幕不是被冻结的,而当WindowManagerService类的成员变量mPolicy所指向的一个PhoneWindowManager对象的成员函数isScreenOn的返回值等于true的时候,就说明屏幕是点亮的。在满足两个条件的情况下,开始创建动画。
WindowManagerService类的成员函数applyAnimationLocked首先是检查WindowManagerService类的成员变量mPolicy所指向的一个PhoneWindowManager对象是否可以为参数win所描述的窗口提供一个类型为transit的动画。如果可以的话,那么调用这个PhoneWindowManager对象的成员函数selectAnimationLw的返回值anim就不等于0,这时候WindowManagerService类的成员函数applyAnimationLocked就可以调用AnimationUtils类的静态成员函数loadAnimation来根据该返回值anim来创建一个动画,并且保存在变量a中。如果不能为参数win所描述的窗口提供一个类型为transit的动画的话,那么WindowManagerService类的成员变量applyAnimationLocked就需要根据该窗口的布局参数来创建这个动画了。这个创建过程分为两步执行:
1.将参数transit的值转化为一个对应的动画样式名称;
2.调用WindowManagerService类的成员函数loadAnimation来指定的窗口布局参数中创建前面第一步所指定样式名称的动画,并且保存在变量a中。其中,指定的窗口布局参数是由WindowState对象win的成员变量mAttrs所指向的一个WindowManager.LayoutParams对象来描述的。
最后,如果变量a的值不等于null,即前面成功的为参数win所描述的窗口创建了一个动画,那么接下来就会将该动画设置给参数所描述的窗口。实际上就是将变量所指向的一个Animation对象保存在参数win所指向的一个WindowState对象的成员变量mAnimation中。同时,WindowManagerService类的成员函数applyAnimationLocked还会将参数isEntrance的值保存在参数win所指向的一个WindowState对象的成员变量mAnimationIsEntrance,以表明前面给它设置的动画是属于进入类型还是退出类型的。


2.切换动画的设置过程
如果一个窗口属于一个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) { // 成功创建一个动画(保存在animation)
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;
}
......
}
参数wtoken描述的是要切换的activity组件,参数ip描述的是要用来创建切换动画的布局参数,参数transit描述的是要创建的切换动画的类型,参数visible描述的是要切换的activiy组件接下来是否是可见的。
首先判断wtoken.hidden 的值是否不等于 visible,如果不等于,说明要切换的activity组件当前的可见性已经就是要设置的可见性了,这个时候就不需要再为它设置切换动画。如果等于visible,那么还会继续检查参数transit描述的是否是一个有效的动画类型,即它的值是否不等于WindowManagerPolicy.TRANSIT_UNSET。如果参数transit描述的是一个有效的动画类型的话,那么WindowManagerService类的成员函数setTokenVisibilityLocked接下来就会执行以下三个操作:
1.判断要切换的activity组件当前是否被设置了一个哑动画,即wtoken.animation 是否等于Animation对象 sDummyAnimation。如果是的话,将animation的值设置为null,接下来重新为它设置一个新的Animation对象。一个需要参与切换的activity组件会设置可见性的时候,是会被设置一个哑动画的。
2.调用WindowManagerService类的四个参数版本的成员函数applyAnimationLocked根据参数lp、transit和visible的值来为要切换的Activity组件创建一个动画。
3.如果第2步可以成功的为要切换的activity组件创建一个动画的话,那么这个动画就会保存在wtoken所指向的一个AppWindowToken对象的成员变量animation中,这时候将delayed = runningAppAnimation = true。runningAppAnimation的值等于true意味着与参数wtoken所描述的activity组件所对应的窗口接下来要执行一个切换动画。这种情况下,就不需要为这些窗口单独设置一个进入或者退出类型的动画,否则需要单独设置一个进入或者退出类型的动画:1)如果visible的值等于true,即wtoken所描述的activity组件被要求设置成可见;如果isVisibleNow的返回值为false,那么窗口当前是不可见的。那么需要给该窗口设置一个类型为WindowManagerPolicy.TRANSIT_ENTER的动画。2)如果一个窗口当前是可见的,即visVisibleNow的返回值等于true,但是参数wtoken所描述的activity组件被要求设置成不可见的,即参数visible的值等于false,那么就需要给该窗口设置一个类型为WindowManagerPolicy.TRANSIT_EXIT的动画。
如果前面成功的为参数wtoken所描述的activity组件创建了一个切换动画,即AppWindowToken对象的成员变量animation的值不等于null,那么delayed就会等于true,表示要执行一个切换动画,同时也表明该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;
}
......
}
1.如果参数lp所描述的布局参数表明它是用来描述一个兼容窗口的,即它所指向的一个WindowManager.LayoutParams对象的成员变量flags的FLAG_COMPATIBLE_WINDOW位不等于0,那么创建的切换动画就固定为FadeInOutAnimation。
2.如果WindowManagerService类的成员变量mNextAppTransitionPackage的值不等于null,那么就说明Package名称为mNextAppTransitionPackage的Activity组件指定了一个自定义的切换动画,其中,指定的进入动画的类型由WindowManagerService类的成员变量mNextAppTransitionEnter来描述,退出则由mNextAppTransitionExit描述。
3.切换动画创建成功后,就会调用参数wtoken所指向的一个AppWindowToken对象的成员函数setAnimation来保存在其成员变量animation中。

;