Android窗口管理服务WindowManagerService显示窗口动画的原理分析
在前一文中,我们分析了Activity组件的切换过程。从这个过程可以知道,所有参与切换操作的窗口都会被设置切换动画。事实上,一个窗口在打开(关闭)的过程中,除了可能会设置切换动画之外,它本身也可能会设置有进入(退出)动画。再进一步地,如果一个窗口是附加在另外一个窗口之上的,那么被附加窗口所设置的动画也会同时传递给该窗口。本文就详细分析WindowManagerService服务显示窗口动画的原理。
老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!
在Android系统中,窗口动画的本质就是对原始窗口施加一个变换(Transformation)。在线性数学中,对物体的形状进行变换是通过乘以一个矩阵(Matrix)来实现,目的就是对物体进行偏移、旋转、缩放、切变、反射和投影等。因此,给窗口设置动画实际上就给窗口设置一个变换矩阵(Transformation Matrix)。
如前所述,一个窗口在打开(关闭)的过程,可能会被设置三个动画,它们分别是窗口本身所设置的进入(退出)动画(Self Transformation)、从被附加窗口传递过来的动画(Attached Transformation),以及宿主Activity组件传递过来的切换动画(App Transformation)。这三个Transformation组合在一起形成一个变换矩阵,以60fps的速度应用在窗口的原始形状之上,完成窗口的动画过程,如图1所示。

图1 窗口的动画显示过程
从上面的分析可以知道,窗口的变换矩阵是应用在窗口的原始位置和大小之上的,因此,在显示窗口的动画之前,除了要给窗口设置变换矩阵之外,还要计算好窗口的原始位置和大小,以及布局和绘制好窗口的UI。在前面Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析和Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制—raw)过程分析这两篇文章中,我们已经分析过窗口的位置和大小计算过程以及窗口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 { ...... // 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,欢迎关注!
- 1楼dr87370101小时前
- 又是很准时的在凌晨更新!mark一下,慢慢看