在前一文中,我们分析了Activity组件的切换过程。从这个过程可以知道,所有参与切换操作的窗口都会被设置切换动画。事实上,一个窗口在打开(关闭)的过程中,除了可能会设置切换动画之外,它本身也可能会设置有进入(退出)动画。再进一步地,如果一个窗口是附加在另外一个窗口之上的,那么被附加窗口所设置的动画也会同时传递给该窗口。
在之前WMS的第六篇博客窗口管理,主要从VSync信号作为切入点分析窗口动画的流程。这里我们就详细分析WindowManagerService服务显示窗口动画的原理。
在Android系统中,窗口动画的本质就是对原始窗口施加一个变换(Transformation)。在线性数学中,对物体的形状进行变换是通过乘以一个矩阵(Matrix)来实现,目的就是对物体进行偏移、旋转、缩放、切变、反射和投影等。因此,给窗口设置动画实际上就给窗口设置一个变换矩阵(Transformation Matrix)。
如前所述,一个窗口在打开(关闭)的过程,可能会被设置三个动画,它们分别是窗口本身所设置的进入(退出)动画(Self Transformation)、从被附加窗口传递过来的动画(Attached Transformation),以及宿主Activity组件传递过来的切换动画(App Transformation)。这三个Transformation组合在一起形成一个变换矩阵,以60fps的速度应用在窗口的原始形状之上,完成窗口的动画过程。
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;
}
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 stacks = displayContent.getStacks();
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ArrayList 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
boolean performShowLocked() {
......
if (mDrawState == READY_TO_SHOW && mWin.isReadyForDisplayIgnoringKeyguard()) {
mService.enableScreenIfNeededLocked();
applyEnterAnimationLocked();
......
mDrawState = HAS_DRAWN;
mService.scheduleAnimationLocked();
......
return true;
}
return false;
}