在前一文中,我们分析了Activity组件的切换过程。从这个过程可以知道,所有参与切换操作的窗口都会被设置切换动画。事实上,一个窗口在打开(关闭)的过程中,除了可能会设置切换动画之外,它本身也可能会设置有进入(退出)动画。再进一步地,如果一个窗口是附加在另外一个窗口之上的,那么被附加窗口所设置的动画也会同时传递给该窗口。
在之前WMS的第六篇博客窗口管理,主要从VSync信号作为切入点分析窗口动画的流程。这里我们就详细分析WindowManagerService服务显示窗口动画的原理。
在Android系统中,窗口动画的本质就是对原始窗口施加一个变换(Transformation)。在线性数学中,对物体的形状进行变换是通过乘以一个矩阵(Matrix)来实现,目的就是对物体进行偏移、旋转、缩放、切变、反射和投影等。因此,给窗口设置动画实际上就给窗口设置一个变换矩阵(Transformation Matrix)。
如前所述,一个窗口在打开(关闭)的过程,可能会被设置三个动画,它们分别是窗口本身所设置的进入(退出)动画(Self Transformation)、从被附加窗口传递过来的动画(Attached Transformation),以及宿主Activity组件传递过来的切换动画(App Transformation)。这三个Transformation组合在一起形成一个变换矩阵,以60fps的速度应用在窗口的原始形状之上,完成窗口的动画过程。
一. 窗口动画的设置过程
1.1 普通动画的设置
-
void applyEnterAnimationLocked() {
-
final
int transit;
-
if (mEnterAnimationPending) {
-
mEnterAnimationPending =
false;
-
transit = WindowManagerPolicy.TRANSIT_ENTER;
-
}
else {
-
transit = WindowManagerPolicy.TRANSIT_SHOW;
-
}
-
applyAnimationLocked(transit,
true);
-
-
if (mService.mAccessibilityController != null
-
&& mWin.getDisplayId() == Display.DEFAULT_DISPLAY) {
-
mService.mAccessibilityController.onWindowTransitionLocked(mWin, transit);
-
}
-
}
-
if (viewVisibility == View.VISIBLE &&
-
(win.mAppToken == null || !win.mAppToken.clientHidden)) {
-
……
-
}
else {
-
winAnimator.mEnterAnimationPending =
false;
-
winAnimator.mEnteringAnimation =
false;
-
if (winAnimator.mSurfaceControl != null) {
-
if (!win.mExiting) {
-
surfaceChanged =
true;
-
-
int transit = WindowManagerPolicy.TRANSIT_EXIT;
//设置一个退出动画
-
if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
-
transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
-
}
-
if (win.isWinVisibleLw() &&
-
winAnimator.applyAnimationLocked(transit,
false)) {
-
focusMayChange = isDefaultDisplay;
-
win.mExiting =
true;
-
}
-
boolean applyAnimationLocked(int transit, boolean isEntrance) {
-
if ((mLocalAnimating && mAnimationIsEntrance == isEntrance)
-
|| mKeyguardGoingAwayAnimation) {
-
if (mAnimation != null && mKeyguardGoingAwayAnimation
-
&& transit == WindowManagerPolicy.TRANSIT_PREVIEW_DONE) {
-
applyFadeoutDuringKeyguardExitAnimation();
-
}
-
return
true;
-
}
-
-
-
if (mService.okToDisplay()) {
-
int anim = mPolicy.selectAnimationLw(mWin, transit);
-
int attr =
-1;
-
Animation a = null;
-
if (anim !=
0) {
-
a = anim !=
-1 ? AnimationUtils.loadAnimation(mContext, anim) : null;
-
}
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 = mService.mAppTransition.loadAnimationAttr(mWin.mAttrs, attr);
-
}
-
}
-
-
if (a != null) {
-
if (WindowManagerService.DEBUG_ANIM) {
-
RuntimeException e = null;
-
if (!WindowManagerService.HIDE_STACK_CRAWLS) {
-
e =
new RuntimeException();
-
e.fillInStackTrace();
-
}
-
Slog.v(TAG,
“Loaded animation “ + a +
” for “ +
this, e);
-
}
-
setAnimation(a);
-
mAnimationIsEntrance = isEntrance;
-
}
-
}
else {
-
clearAnimation();
-
}
-
-
return mAnimation != null;
-
}
1.2 切换动画的设置过程
-
boolean setTokenVisibilityLocked(AppWindowToken wtoken, WindowManager.LayoutParams lp,
-
boolean visible, int transit, boolean performLayout, boolean isVoiceInteraction) {
-
boolean delayed =
false;
-
-
if (wtoken.clientHidden == visible) {
-
wtoken.clientHidden = !visible;
-
wtoken.sendAppVisibilityToClients();
-
}
-
-
wtoken.willBeHidden =
false;
-
-
if (wtoken.hidden == visible || (wtoken.hidden && wtoken.mIsExiting)) {
-
boolean changed =
false;
-
-
boolean runningAppAnimation =
false;
-
-
if (transit != AppTransition.TRANSIT_UNSET) {
-
if (wtoken.mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
-
wtoken.mAppAnimator.animation = null;
-
}
-
if (applyAnimationLocked(wtoken, lp, transit, visible, isVoiceInteraction)) {
-
delayed = runningAppAnimation =
true;
-
}
-
WindowState window = wtoken.findMainWindow();
-
-
if (window != null && mAccessibilityController != null
-
&& window.getDisplayId() == Display.DEFAULT_DISPLAY) {
-
mAccessibilityController.onAppWindowTransitionLocked(window, transit);
-
}
-
changed =
true;
-
}
-
-
final
int windowsCount = wtoken.allAppWindows.size();
-
for (
int i =
0; i < windowsCount; i++) {
-
WindowState win = wtoken.allAppWindows.get(i);
-
if (win == wtoken.startingWindow) {
-
continue;
-
}
-
-
if (visible) {
-
if (!win.isVisibleNow()) {
-
if (!runningAppAnimation) {
-
win.mWinAnimator.applyAnimationLocked(
-
WindowManagerPolicy.TRANSIT_ENTER,
true);
-
……
-
}
-
……
-
}
-
}
else
if (win.isVisibleNow()) {
-
if (!runningAppAnimation) {
-
win.mWinAnimator.applyAnimationLocked(
-
WindowManagerPolicy.TRANSIT_EXIT,
false);
-
……
-
}
-
……
-
}
-
}
-
-
……
-
}
-
-
if (wtoken.mAppAnimator.animation != null) {
-
delayed =
true;
-
}
-
-
for (
int i = wtoken.allAppWindows.size() -
1; i >=
0 && !delayed; i–) {
-
if (wtoken.allAppWindows.get(i).mWinAnimator.isWindowAnimating()) {
-
delayed =
true;
-
}
-
}
-
-
return delayed;
-
}
-
private boolean applyAnimationLocked(AppWindowToken atoken,
-
WindowManager.LayoutParams lp, int transit, boolean enter, boolean isVoiceInteraction) {
-
if (okToDisplay()) {
-
DisplayInfo displayInfo = getDefaultDisplayInfoLocked();
-
final
int width = displayInfo.appWidth;
-
final
int height = displayInfo.appHeight;
-
-
WindowState win = atoken.findMainWindow();
-
Rect containingFrame =
new Rect(
0,
0, width, height);
-
Rect contentInsets =
new Rect();
-
Rect appFrame =
new Rect(
0,
0, width, height);
-
if (win != null && win.isFullscreen(width, height)) {
-
containingFrame.
set(win.mContainingFrame);
-
contentInsets.
set(win.mContentInsets);
-
appFrame.
set(win.mFrame);
-
}
-
-
if (atoken.mLaunchTaskBehind) {
-
enter =
false;
-
}
-
Animation a = mAppTransition.loadAnimation(lp, transit, enter, width, height,
-
mCurConfiguration.orientation, containingFrame, contentInsets, appFrame,
-
isVoiceInteraction);
-
if (a != null) {
-
if (DEBUG_ANIM) {
-
RuntimeException e = null;
-
if (!HIDE_STACK_CRAWLS) {
-
e =
new RuntimeException();
-
e.fillInStackTrace();
-
}
-
Slog.v(TAG,
“Loaded animation “ + a +
” for “ + atoken, e);
-
}
-
atoken.mAppAnimator.setAnimation(a, width, height,
-
mAppTransition.canSkipFirstFrame());
-
}
-
}
else {
-
atoken.mAppAnimator.clearAnimation();
-
}
-
-
return atoken.mAppAnimator.animation != null;
-
}
二、窗口动画的显示框架
-
private final void performLayoutAndPlaceSurfacesLockedInner(boolean recoveringMemory) {
-
……
-
SurfaceControl.openTransaction();
-
try {
-
-
……
-
-
for (
int displayNdx =
0; displayNdx < numDisplays; ++displayNdx) {
-
final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx);
-
boolean updateAllDrawn =
false;
-
WindowList windows = displayContent.getWindowList();
-
DisplayInfo displayInfo = displayContent.getDisplayInfo();
-
final
int displayId = displayContent.getDisplayId();
-
final
int dw = displayInfo.logicalWidth;
-
final
int dh = displayInfo.logicalHeight;
-
final
int innerDw = displayInfo.appWidth;
-
final
int innerDh = displayInfo.appHeight;
-
final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
-
-
// Reset for each display.
-
mInnerFields.mDisplayHasContent =
false;
-
mInnerFields.mPreferredRefreshRate =
0;
-
mInnerFields.mPreferredModeId =
0;
-
-
int repeats =
0;
-
do {
-
repeats++;
-
if (repeats >
6) {
-
……
-
break;
-
}
-
-
……
-
-
//计算各个窗口的大小
-
if (repeats <
4) {
-
performLayoutLockedInner(displayContent, repeats ==
1,
-
false
/*updateInputWindows*/);
-
}
else {
-
Slog.w(TAG,
“Layout repeat skipped after too many iterations”);
-
}
-
-
……
-
}
while (displayContent.pendingLayoutChanges !=
0);
//直到pendingLayoutChanges为0
-
-
……
-
-
// Moved from updateWindowsAndWallpaperLocked().
-
if (w.mHasSurface) {
-
// Take care of the window being ready to display.
-
final boolean committed =
-
winAnimator.commitFinishDrawingLocked();
//这里面调用performShowLocked,然后显示窗口
-
…….
-
-
winAnimator.setSurfaceBoundariesLocked(recoveringMemory);
//设置窗口size,位置等
-
}
-
-
……
-
}
-
-
……
-
-
if (updateAllDrawn) {
-
updateAllDrawnLocked(displayContent);
//更新APPWindowToken的allDrawn是否为true
-
}
-
}
-
-
if (focusDisplayed) {
-
mH.sendEmptyMessage(H.REPORT_LOSING_FOCUS);
-
}
-
-
-
mDisplayManagerInternal.performTraversalInTransactionFromWindowManager();
//设置SurfaceControl的旋转角度、大小等
-
-
}
catch (RuntimeException e) {
-
Slog.wtf(TAG,
“Unhandled exception in Window Manager”, e);
-
} finally {
-
SurfaceControl.closeTransaction();
-
}
-
-
……
-
enableScreenIfNeededLocked();
-
-
scheduleAnimationLocked();
//设置动画使能(当下次VSync信号过来,开始动画流程)
-
-
}
-
boolean commitFinishDrawingLocked() {
-
if (mDrawState != COMMIT_DRAW_PENDING && mDrawState != READY_TO_SHOW) {
-
return
false;
-
}
-
if (DEBUG_SURFACE_TRACE || DEBUG_ANIM) {
-
Slog.i(TAG,
“commitFinishDrawingLocked: mDrawState=READY_TO_SHOW “ + mSurfaceControl);
-
}
-
mDrawState = READY_TO_SHOW;
-
final AppWindowToken atoken = mWin.mAppToken;
-
if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
-
return performShowLocked();
-
}
-
return
false;
-
}
接下来在performLayoutAndPlaceSurfacesLockedInner中还会调用updateAllDrawnLocked函数,当每个APPWindowToken的numDrawnWindows大于等于numInteresting时,DisplayContent的layoutNeeded为true,APPWindowToken的allDrawn为true。这个时候当DisplayContent的layoutNeeded为true,会重新调用刷新函数,然后再次调用commitFinishDrawingLocked函数,这个时候APPWindowToken的allDrawn为true,就会调用performShowLocked函数了。
-
private void updateAllDrawnLocked(DisplayContent displayContent) {
-
// See if any windows have been drawn, so they (and others
-
// associated with them) can now be shown.
-
ArrayList<TaskStack> stacks = displayContent.getStacks();
-
for (
int stackNdx = stacks.size() -
1; stackNdx >=
0; –stackNdx) {
-
final ArrayList<Task> tasks = stacks.get(stackNdx).getTasks();
-
for (
int taskNdx = tasks.size() -
1; taskNdx >=
0; –taskNdx) {
-
final AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
-
for (
int tokenNdx = tokens.size() -
1; tokenNdx >=
0; –tokenNdx) {
-
final AppWindowToken wtoken = tokens.get(tokenNdx);
-
if (!wtoken.allDrawn) {
-
int numInteresting = wtoken.numInterestingWindows;
-
if (numInteresting >
0 && wtoken.numDrawnWindows >= numInteresting) {
-
wtoken.allDrawn =
true;
//allDrawn设为true
-
displayContent.layoutNeeded =
true;
//需要布局
-
mH.obtainMessage(H.NOTIFY_ACTIVITY_DRAWN, wtoken.token).sendToTarget();
-
}
-
}
-
}
-
}
-
}
-
}
下面我们再来看看APPWindowToken的numDrawnWindows 和numInterestingWindows的处理。也是在performLayoutAndPlaceSurfacesLockedInner函数中,在遍历每个window的时候有如下代码:
-
final AppWindowToken atoken = w.mAppToken;
-
-
if (atoken != null
-
&& (!atoken.allDrawn || atoken.mAppAnimator.freezingScreen)) {
-
if (atoken.lastTransactionSequence != mTransactionSequence) {
-
atoken.lastTransactionSequence = mTransactionSequence;
-
atoken.numInterestingWindows = atoken.numDrawnWindows =
0;
-
atoken.startingDisplayed =
false;
-
}
-
if ((w.isOnScreenIgnoringKeyguard()
-
|| winAnimator.mAttrType == TYPE_BASE_APPLICATION)
-
&& !w.mExiting && !w.mDestroying) {
-
if (w != atoken.startingWindow) {
-
if (!atoken.mAppAnimator.freezingScreen || !w.mAppFreezing) {
-
atoken.numInterestingWindows++;
//不是startingWindow等,numInterestingWindows加1
-
if (w.isDrawnLw()) {
-
atoken.numDrawnWindows++;
//已经绘制过的窗口numDrawnWindows加1
-
updateAllDrawn =
true;
-
}
-
}
-
}
else
if (w.isDrawnLw()) {
-
atoken.startingDisplayed =
true;
-
}
-
}
-
}
-
public boolean isDrawnLw() {
-
return mHasSurface && !mDestroying &&
-
(mWinAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW
-
|| mWinAnimator.mDrawState == WindowStateAnimator.HAS_DRAWN);
-
}
我们再来看看commitFinishDrawingLocked函数,主要当mDrawState是COMMIT_DRAW_PENDING 和READY_TO_SHOW才会将状态置为READY_TO_SHOW。
-
boolean commitFinishDrawingLocked() {
-
if (mDrawState != COMMIT_DRAW_PENDING && mDrawState != READY_TO_SHOW) {
-
return
false;
-
}
-
-
mDrawState = READY_TO_SHOW;
-
final AppWindowToken atoken = mWin.mAppToken;
-
if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
-
return performShowLocked();
-
}
-
return
false;
-
}
而COMMIT_DRAW_PENDING状态只有在下面函数中设置。
-
boolean finishDrawingLocked() {
-
final boolean startingWindow =
-
mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-
if (mDrawState == DRAW_PENDING) {
-
mDrawState = COMMIT_DRAW_PENDING;
-
return
true;
-
}
-
return
false;
-
}
接下来我们就要研究下是谁调用了WindowStateAnimator的finishDrawingLocked函数。
-
public void finishDrawingWindow(Session session, IWindow client) {
-
final
long origId = Binder.clearCallingIdentity();
-
try {
-
synchronized (mWindowMap) {
-
WindowState win = windowForClientLocked(session, client,
false);
-
if (win != null && win.mWinAnimator.finishDrawingLocked()) {
-
if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) !=
0) {
-
getDefaultDisplayContentLocked().pendingLayoutChanges |=
-
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-
}
-
final DisplayContent displayContent = win.getDisplayContent();
-
if (displayContent != null) {
-
displayContent.layoutNeeded =
true;
-
}
-
requestTraversalLocked();
-
}
-
}
-
} finally {
-
Binder.restoreCallingIdentity(origId);
-
}
-
}
finishDrawingLocked函数中当mDrawState的状态是DRAW_PENDING是把状态改成COMMIT_DRAW_PENDING。
-
boolean finishDrawingLocked() {
-
final boolean startingWindow =
-
mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-
if (mDrawState == DRAW_PENDING) {
-
mDrawState = COMMIT_DRAW_PENDING;
-
return
true;
-
}
-
return
false;
-
}
而在createSurfaceLocked函数中创建SurfaceControl的时候会把这个状态设置成DRAW_PENDING。
-
SurfaceControl createSurfaceLocked() {
-
final WindowState w = mWin;
-
if (mSurfaceControl == null) {
-
mDrawState = DRAW_PENDING;
这样这个逻辑就通了,当应用绘制完成会在ViewRootImpl中调用Session的finishDrawing,然后一路到WindowStateAnimator的finishDrawingLocked将mDrawState状态变成DRAW_PENDING,然后刷新系统时调用commitFinishDrawingLocked函数,这个时候把状态变成READY_TO_SHOW,最后调用updateAllDrawnLocked函数,把相关的APPWindowToken的allDrawn设置为true,并且再次刷新系统,再次调用commitFinishDrawingLocked函数时,这个时候APPWindowToken的allDrawn为true。就会调用performShowLocked函数了。
-
private final void performLayoutAndPlaceSurfacesLockedLoop() {
-
……
-
-
try {
-
performLayoutAndPlaceSurfacesLockedInner(recoveringMemory);
-
-
mInLayout =
false;
-
-
if (needsLayout()) {
-
if (++mLayoutRepeatCount <
6) {
-
requestTraversalLocked();
-
}
else {
-
Slog.e(TAG,
“Performed 6 layouts in a row. Skipping”);
-
mLayoutRepeatCount =
0;
-
}
-
}
else {
-
mLayoutRepeatCount =
0;
-
}
-
-
……
-
}
catch (RuntimeException e) {
-
mInLayout =
false;
-
Slog.wtf(TAG,
“Unhandled exception while laying out windows”, e);
-
}
-
-
}
-
private boolean needsLayout() {
-
final
int numDisplays = mDisplayContents.size();
-
for (
int displayNdx =
0; displayNdx < numDisplays; ++displayNdx) {
-
final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx);
-
if (displayContent.layoutNeeded) {
-
return
true;
-
}
-
}
-
return
false;
-
}
-
void requestTraversalLocked() {
-
if (!mTraversalScheduled) {
-
mTraversalScheduled =
true;
-
mH.sendEmptyMessage(H.DO_TRAVERSAL);
-
}
-
}
-
case DO_TRAVERSAL: {
-
synchronized(mWindowMap) {
-
mTraversalScheduled =
false;
-
performLayoutAndPlaceSurfacesLocked();
-
}
-
}
break;
三、动画显示
-
boolean commitFinishDrawingLocked() {
-
if (mDrawState != COMMIT_DRAW_PENDING && mDrawState != READY_TO_SHOW) {
-
return
false;
-
}
-
-
mDrawState = READY_TO_SHOW;
-
final AppWindowToken atoken = mWin.mAppToken;
-
if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
-
return performShowLocked();
-
}
-
return
false;
-
}
-
if (mAppTransition.isReady()) {
-
defaultDisplay.pendingLayoutChanges |= handleAppTransitionReadyLocked(defaultWindows);
-
if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats(
“after handleAppTransitionReadyLocked”,
-
defaultDisplay.pendingLayoutChanges);
-
}
-
boolean showAllWindowsLocked() {
-
boolean isAnimating =
false;
-
final
int NW = mAllAppWinAnimators.size();
-
for (
int i=
0; i<NW; i++) {
-
WindowStateAnimator winAnimator = mAllAppWinAnimators.get(i);
-
winAnimator.performShowLocked();
-
isAnimating |= winAnimator.isAnimating();
-
}
-
return isAnimating;
-
}
-
boolean performShowLocked() {
-
……
-
if (mDrawState == READY_TO_SHOW && mWin.isReadyForDisplayIgnoringKeyguard()) {
-
-
mService.enableScreenIfNeededLocked();
-
-
applyEnterAnimationLocked();
-
-
……
-
mDrawState = HAS_DRAWN;
-
mService.scheduleAnimationLocked();
-
-
……
-
-
return
true;
-
}
-
-
return
false;
-
}