我们知道Android动画主要包括Activity切换动画、窗口动画、转屏动画、窗口内部View动画,前三者在WindowManagerService(简称WMS)中完成,本篇文章重点分析Activity切换动画、窗口动画的设置,动画每一帧计算,及如何快速分析动画类型资源。阅读本文最好有一定的WindowManagerService基础,本文所有代码基于AndroidM。
典型的Activity切换动画包括:activityOpenEnterAnimation,activityOpenExitAnimation,taskOpenEnterAnimation, taskCloseExitAnimation, wallpaperOpenEnterAnimation, wallpaperCloseEnterAnimation。典型的窗口动画包括:windowEnterAnimation,windowExitAnimation。
我们知道每一个Activity在WMS中对应有一个APPWindowToken,APPWindowToken内部有一个AppWindowAnimator类型对象,Activity切换动画逻辑便是在AppTransition、AppWindowAnimator中完成。每个Activity一般情况下都有一个窗口,窗口是用WindowState来描述的,每个窗口有一个WindowStateAnimator类型对象,这个对象管理着窗口Surface和窗口动画。有了这些基本信息,下面就来分析Activity切换和窗口动画。
文章会分为四个部分,分别为:Activity切换动画设
置,窗口动画设置,动画每一帧计算,
通过日志快速分析动画资源来自哪里。
第一部分:Activity切换动画设置
第1步、WMS.prepareAppTransition()
AMS调用
WMS.
prepareAppTransition()设置Activity切换动画类型。比较常见的就是TRANSIT_ACTIVITY_OPEN、TRANSIT_ACTIVITY_CLOSE、TRANSIT_TASK_OPEN、TRANSIT_TASK_CLOSE、TRANSIT_TASK_TO_FRONT、TRANSIT_TASK_TO_BACK这几种Activity切换动画类型了。prepareAppTransition()函数做了两件事:①将动画类型设置到AppTransition中,后面将用到这个值;②发送一个APP_TRANSITION_TIMEOUT的5s超时消息。
第2步、WMS.setAppVisibility()
AMS调用WMS.setAppVisibility()设置Activity可见性。一般情况下在一次Activity切换过程中会有两个Activity的可见性发生变化,pre Activity变为不可见,next Activity变为可见。这个函数有两条逻辑:①前面已经调用WMS.prepareAppTransition()设置了Activity切换动画类型,那么将可见Activity加入mOpeningApps,不可见Activity加入mClosingApps,同时调用AppWindowToken.sendAppVisibilityToClients()通知应用窗口可见性变化。mOpeningApps、mClosingApps这两个变量就是为Activity切换动画而存在的,将在后面用到;②没有调用过WMS.prepareAppTransition(),那么直接调用WMS.setTokenVisibilityLocked(),这个函数将在第11步中分析。
public void setAppVisibility(IBinder token, boolean visible) {
......
synchronized(mWindowMap) {
wtoken = findAppWindowToken(token);
......
// If we are preparing an app transition, then delay changing
// the visibility of this token until we execute that transition.
if (okToDisplay() && mAppTransition.isTransitionSet()) {
......
wtoken.inPendingTransaction = true;
if (visible) {
mOpeningApps.add(wtoken); //将可见Activity加到mOpeningApps中;
wtoken.startingMoved = false;
wtoken.mEnteringAnimation = true;
// If the token is currently hidden (should be the
// common case), then we need to set up to wait for
// its windows to be ready.
if (wtoken.hidden) {
wtoken.allDrawn = false;
wtoken.deferClearAllDrawn = false;
wtoken.waitingToShow = true;
if (wtoken.clientHidden) {
// In the case where we are making an app visible
// but holding off for a transition, we still need
// to tell the client to make its windows visible so
// they get drawn. Otherwise, we will wait on
// performing the transition until all windows have
// been drawn, they never will be, and we are sad.
wtoken.clientHidden = false;
wtoken.sendAppVisibilityToClients(); //这个调用就是通知应用窗口可见性变化;
}
}
} else {
mClosingApps.add(wtoken); //不可见Activity加到mClosingApps中;
wtoken.mEnteringAnimation = false;
}
......
return;
}
......
setTokenVisibilityLocked(wtoken, null, visible, AppTransition.TRANSIT_UNSET,true, wtoken.voiceInteraction);
}
}
第3、4步、AppWindowToken.sendAppVisibilityToClients()/dispatchAppVisibility()
这个函数就是通知上层应用窗口可见性发生变化。如果next Activity是冷启动,那么该函数并不能通知next Activity的窗口变为可见,因为此时该函数调用时,next Activity的窗口还没add到WMS中来。
第5、6步、WMS.finishDrawingWindow()
next Activity被Resume起来后,添加窗口、measure、layout、draw等一系列操作完成后便会调用WMS.finishDrawingWindow()来通知WMS,该窗口已经绘制好了,可以开始做动画了。WMS.finishDrawingWindow()会调用WindowStateAnimator.finishDrawingLocked()更新窗口状态mDrawState为COMMIT_DRAW_PENDING。同时还会调用requestTraversalLocked()来触发一次界面刷新函数performLayoutAndPlaceSurfacesLockedInner()调用。
第7步、performLayoutAndPlaceSurfacesLockedInner()
该函数就是鼎鼎大名的界面刷新函数。该函数里面跟Activity切换动画相关的调用拆分到接下来的那些步骤中。
第8步、WindowStateAnimator.commitFinishDrawingLocked()
performLayoutAndPlaceSurfacesLockedInner()函数中,对于窗口堆栈中有Surface的窗口均会调用WindowStateAnimator.commitFinishDrawingLocked(),该函数将窗口状态为COMMIT_DRAW_PENDING或READY_TO_SHOW的窗口,全部更新到READY_TO_SHOW状态。将状态从第五中中的COMMIT_DRAW_PENDING更新到READY_TO_SHOW,WindowState.isDrawnLw()便会返回true,代表窗口已经是绘制完成状态。w.isDrawnLw()返回为true,那么才有updateAllDrawn = true,接着才会触发调用第九步中的WMS.updateAllDrawnLocked()函数,将AppWindowToken.allDrawn置为true。
boolean commitFinishDrawingLocked() {
if (DEBUG_STARTING_WINDOW &&
mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) {
Slog.i(TAG, "commitFinishDrawingLocked: " + mWin + " cur mDrawState="
+ drawStateToString());
}
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; //将窗口状态为COMMIT_DRAW_PENDING或READY_TO_SHOW更新到READY_TO_SHOW;
final AppWindowToken atoken = mWin.mAppToken;
if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
return performShowLocked(); //对于Activity切换这种场景,此时该条件无法满足,故不会调用。
}
return false;
}
if (w != atoken.startingWindow) {
if (!atoken.mAppAnimator.freezingScreen || !w.mAppFreezing) {
atoken.numInterestingWindows++; //计算属于该AppWindowToken的窗口总数;
if (w.isDrawnLw()) {
atoken.numDrawnWindows++; //计算已经绘制完成的窗口总数;
if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) Slog.v(TAG,
"tokenMayBeDrawn: " + atoken
+ " freezingScreen=" + atoken.mAppAnimator.freezingScreen
+ " mAppFreezing=" + w.mAppFreezing);
updateAllDrawn = true;
}
}
第9步、WMS.updateAllDrawnLocked()
该函数更新AppWindowToken.allDrawn值。只有属于该AppWindowToken的所有窗口都是绘制完成状态(一般情况下只有一个窗口,有时候会有父窗口、子窗口,这时属于该AppWindowToken的窗口数量就不止一个了),AppWindowToken.allDrawn才会置为true。AppWindowToken.allDrawn为true才会使得第十步中的WMS.handleAppTransitionReadyLocked()完整的执行。
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) { //绘制完成窗口总数>=窗口总数,就代表属于该AppWindowToken的所有窗口均已绘制完成;
if (DEBUG_VISIBILITY) Slog.v(TAG,
"allDrawn: " + wtoken
+ " interesting=" + numInteresting
+ " drawn=" + wtoken.numDrawnWindows);
wtoken.allDrawn = true; //将allDrawn置为true;
// Force an additional layout pass where WindowStateAnimator#
// commitFinishDrawingLocked() will call performShowLocked().
displayContent.layoutNeeded = true;
mH.obtainMessage(H.NOTIFY_ACTIVITY_DRAWN, wtoken.token).sendToTarget();
}
}
}
}
}
}
第10步、WMS.handleAppTransitionReadyLocked()
设置Activity切换动画选择逻辑就是在该函数中完成。该函数只有在调用了第一步后才会被调用,并且只有在wtoken.allDrawn=true时才会完整的执行完。这个函数接近300行,逻辑看起来非常复杂,下面将仔细分析这个函数。
public int handleAppTransitionReadyLocked(WindowList windows) {
int changes = 0;
int i;
int appsCount = mOpeningApps.size();
boolean goodToGo = true;
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"Checking " + appsCount + " opening apps (frozen="
+ mDisplayFrozen + " timeout="
+ mAppTransition.isTimeout() + ")...");
if (!mAppTransition.isTimeout()) {
for (i = 0; i < appsCount && goodToGo; i++) {
AppWindowToken wtoken = mOpeningApps.valueAt(i);
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"Check opening app=" + wtoken + ": allDrawn="
+ wtoken.allDrawn + " startingDisplayed="
+ wtoken.startingDisplayed + " startingMoved="
+ wtoken.startingMoved);
if (!wtoken.allDrawn && !wtoken.startingDisplayed
&& !wtoken.startingMoved) { //wtoken.allDrawn=true,goodToGo才不会被置为false,下面的逻辑才能继续执行;
goodToGo = false;
}
}
if (goodToGo && isWallpaperVisible(mWallpaperTarget)) { //如果壁纸需要可见,但是壁纸此时是不可见状态,那么就需要等待壁纸绘制完成。。。才能继续执行下面的Activity切换动画选择逻辑;
boolean wallpaperGoodToGo = true;
for (int curTokenIndex = mWallpaperTokens.size() - 1;
curTokenIndex >= 0 && wallpaperGoodToGo; curTokenIndex--) {
WindowToken token = mWallpaperTokens.get(curTokenIndex);
for (int curWallpaperIndex = token.windows.size() - 1; curWallpaperIndex >= 0;
curWallpaperIndex--) {
WindowState wallpaper = token.windows.get(curWallpaperIndex);
if (wallpaper.mWallpaperVisible && !wallpaper.isDrawnLw()) {
// We've told this wallpaper to be visible, but it is not drawn yet
wallpaperGoodToGo = false;
if (mWallpaperDrawState != WALLPAPER_DRAW_TIMEOUT) {
// wait for this wallpaper until it is drawn or timeout
goodToGo = false;
}
if (mWallpaperDrawState == WALLPAPER_DRAW_NORMAL) {
mWallpaperDrawState = WALLPAPER_DRAW_PENDING;
mH.removeMessages(H.WALLPAPER_DRAW_PENDING_TIMEOUT);
mH.sendEmptyMessageDelayed(H.WALLPAPER_DRAW_PENDING_TIMEOUT,
WALLPAPER_DRAW_PENDING_TIMEOUT_DURATION);
}
if (DEBUG_APP_TRANSITIONS || DEBUG_WALLPAPER) Slog.v(TAG,
"Wallpaper should be visible but has not been drawn yet. " +
"mWallpaperDrawState=" + mWallpaperDrawState);
break;
}
}
}
if (wallpaperGoodToGo) {
mWallpaperDrawState = WALLPAPER_DRAW_NORMAL;
mH.removeMessages(H.WALLPAPER_DRAW_PENDING_TIMEOUT);
}
}
}
if (goodToGo) {
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO");
int transit = mAppTransition.getAppTransition(); //取出第一步中设置的Activity切换动画类型;
if (mSkipAppTransitionAnimation) { //如果设置了StartingWindow窗口,那么就忽略第一步中设置的Activity切换动画类型;
transit = AppTransition.TRANSIT_UNSET;
}
mSkipAppTransitionAnimation = false;
mNoAnimationNotifyOnTransitionFinished.clear();
mH.removeMessages(H.APP_TRANSITION_TIMEOUT); //移除超时;
rebuildAppWindowListLocked(); //窗口堆栈顺序重排,偶现Activity窗口显示在launcher图标之下就可能就是窗口堆栈顺序没有重排导致,Android4.4就有该问题。
// if wallpaper is animating in or out set oldWallpaper to null else to wallpaper
WindowState oldWallpaper =
mWallpaperTarget != null && mWallpaperTarget.mWinAnimator.isAnimating()
&& !mWallpaperTarget.mWinAnimator.isDummyAnimation()
? null : mWallpaperTarget; //oldWallpaper指向null或mWallpaperTarget
mInnerFields.mWallpaperMayChange = false;
// The top-most window will supply the layout params,
// and we will determine it below.
LayoutParams animLp = null;
int bestAnimLayer = -1;
boolean fullscreenAnim = false;
boolean voiceInteraction = false;
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"New wallpaper target=" + mWallpaperTarget
+ ", oldWallpaper=" + oldWallpaper
+ ", lower target=" + mLowerWallpaperTarget
+ ", upper target=" + mUpperWallpaperTarget);
boolean openingAppHasWallpaper = false;
boolean closingAppHasWallpaper = false;
final AppWindowToken lowerWallpaperAppToken;
final AppWindowToken upperWallpaperAppToken;
if (mLowerWallpaperTarget == null) {
lowerWallpaperAppToken = upperWallpaperAppToken = null;
} else {
lowerWallpaperAppToken = mLowerWallpaperTarget.mAppToken;
upperWallpaperAppToken = mUpperWallpaperTarget.mAppToken;
}
// Do a first pass through the tokens for two
// things:
// (1) Determine if both the closing and opening
// app token sets are wallpaper targets, in which
// case special animations are needed
// (since the wallpaper needs to stay static
// behind them).
// (2) Find the layout params of the top-most
// application window in the tokens, which is
// what will control the animation theme.
final int closingAppsCount = mClosingApps.size();
appsCount = closingAppsCount + mOpeningApps.size();
for (i = 0; i < appsCount; i++) { //这个for循环主要干两件事情,第一事判断closing and opening APP token是否是壁纸目标窗口;第二件事是找到最顶部(全屏窗口)的Activity窗口,取得窗口属性mAttrs的值,其实这个地方有四种情况:全屏Activity-->全屏Activity、全屏Activity-->非全屏Activity、非全屏Activity-->全屏Activity、非全屏Activity-->非全屏Activity,这四种情况使得animLp值会有所差异;
final AppWindowToken wtoken;
if (i < closingAppsCount) {
wtoken = mClosingApps.valueAt(i);
if (wtoken == lowerWallpaperAppToken || wtoken == upperWallpaperAppToken) {
closingAppHasWallpaper = true;
}
} else {
wtoken = mOpeningApps.valueAt(i - closingAppsCount);
if (wtoken == lowerWallpaperAppToken || wtoken == upperWallpaperAppToken) {
openingAppHasWallpaper = true;
}
}
voiceInteraction |= wtoken.voiceInteraction;
if (wtoken.appFullscreen) {
WindowState ws = wtoken.findMainWindow();
if (ws != null) {
animLp = ws.mAttrs;
bestAnimLayer = ws.mLayer;
fullscreenAnim = true;
}
} else if (!fullscreenAnim) {
WindowState ws = wtoken.findMainWindow();
if (ws != null) {
if (ws.mLayer > bestAnimLayer) {
animLp = ws.mAttrs;
bestAnimLayer = ws.mLayer;
}
}
}
}
mAnimateWallpaperWithTarget = false;
if (closingAppHasWallpaper && openingAppHasWallpaper) { //下面这段逻辑都是根据壁纸目标窗口的各种情况重新设置指定的transit值;
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Wallpaper animation!");
switch (transit) {
case AppTransition.TRANSIT_ACTIVITY_OPEN:
case AppTransition.TRANSIT_TASK_OPEN:
case AppTransition.TRANSIT_TASK_TO_FRONT:
transit = AppTransition.TRANSIT_WALLPAPER_INTRA_OPEN;
break;
case AppTransition.TRANSIT_ACTIVITY_CLOSE:
case AppTransition.TRANSIT_TASK_CLOSE:
case AppTransition.TRANSIT_TASK_TO_BACK:
transit = AppTransition.TRANSIT_WALLPAPER_INTRA_CLOSE;
break;
}
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"New transit: " + AppTransition.appTransitionToString(transit));
} else if ((oldWallpaper != null) && !mOpeningApps.isEmpty()
&& !mOpeningApps.contains(oldWallpaper.mAppToken)) {
// We are transitioning from an activity with
// a wallpaper to one without.
transit = AppTransition.TRANSIT_WALLPAPER_CLOSE;
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"New transit away from wallpaper: "
+ AppTransition.appTransitionToString(transit));
} else if (mWallpaperTarget != null && mWallpaperTarget.isVisibleLw()) {
// We are transitioning from an activity without
// a wallpaper to now showing the wallpaper
transit = AppTransition.TRANSIT_WALLPAPER_OPEN;
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"New transit into wallpaper: "
+ AppTransition.appTransitionToString(transit));
} else {
mAnimateWallpaperWithTarget = true;
}
// If all closing windows are obscured, then there is
// no need to do an animation. This is the case, for
// example, when this transition is being done behind
// the lock screen.
if (!mPolicy.allowAppAnimationsLw()) {