Android的窗口动画可以分为三类
1.窗口本身的动画
2.被附加的窗口传递的动画(即父窗口的动画)
3.activity组件动画
根据WMS中的窗口动画设置流程,可以把12归为一类,即普通窗口动画,下面分别来讲一讲这两类窗口动画的设置流程
1.普通窗口动画的设置流程
WindowStateAnimator代表一个窗口动画对象,当一个窗口完成绘制之后会调用函数commitFinishDrawingLocked()
boolean commitFinishDrawingLocked() {
if (mDrawState != COMMIT_DRAW_PENDING && mDrawState != READY_TO_SHOW) {
return false;
}
mDrawState = READY_TO_SHOW; //设置当前绘制状态
boolean result = false;
final AppWindowToken atoken = mWin.mAppToken;
if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
result = performShowLocked();
}
return result;
}
如果该窗口动画对象对应的窗口不是一个activity组件窗口或者activity的窗口组件已经绘制完毕,就可以个给这个窗口设置一个窗口动画,即执行函数performShowLocked()
// This must be called while inside a transaction.
boolean performShowLocked() {
if (mWin.isHiddenFromUserLocked()) {
if (DEBUG_VISIBILITY) Slog.w(TAG, "hiding " + mWin + ", belonging to " + mWin.mOwnerUid);
mWin.hideLw(false);
return false;
}
mService.enableScreenIfNeededLocked();
applyEnterAnimationLocked();
// Force the show in the next prepareSurfaceLocked() call.
mLastAlpha = -1;
if (DEBUG_SURFACE_TRACE || DEBUG_ANIM)
Slog.v(TAG, "performShowLocked: mDrawState=HAS_DRAWN in " + mWin);
mDrawState = HAS_DRAWN;
mService.scheduleAnimationLocked();
int i = mWin.mChildWindows.size();
while (i > 0) {
i--;
WindowState c = mWin.mChildWindows.get(i);
if (c.mAttachedHidden) {
c.mAttachedHidden = false;
if (c.mWinAnimator.mSurfaceController != null) {
c.mWinAnimator.performShowLocked();
final DisplayContent displayContent = c.getDisplayContent();
if (displayContent != null) {
displayContent.layoutNeeded = true;
}
}
}
}
if (mWin.mAttrs.type != TYPE_APPLICATION_STARTING && mWin.mAppToken != null) {
mWin.mAppToken.onFirstWindowDrawn(mWin, this);
}
if (mWin.mAttrs.type == TYPE_INPUT_METHOD) {
mWin.mDisplayContent.mDividerControllerLocked.resetImeHideRequested();
}
return true;
}
return false;
}
只有当mDrawState == READY_TO_SHOW的时候才能设置动画,继续看
void applyEnterAnimationLocked() {
// If we are the new part of a window replacement transition and we have requested
// not to animate, we instead want to make it seamless, so we don't want to apply
// an enter transition.
if (mWin.mSkipEnterAnimationForSeamlessReplacement) {
return;
}
final int transit;
if (mEnterAnimationPending) {
mEnterAnimationPending = false;
transit = WindowManagerPolicy.TRANSIT_ENTER;
} else {
transit = WindowManagerPolicy.TRANSIT_SHOW;
}
applyAnimationLocked(transit, true);
//TODO (multidisplay): Magnification is supported only for the default display.
if (mService.mAccessibilityController != null
&& mWin.getDisplayId() == DEFAULT_DISPLAY) {
mService.mAccessibilityController.onWindowTransitionLocked(mWin, transit);
}
}
mEnterAnimationPending代表窗口处于等待显示的过程中,即从invisible变为visible,这时候就需要设置一个进入的动画,反之则设置一个退出的动画
boolean applyAnimationLocked(int transit, boolean isEntrance) {
if ((mLocalAnimating && mAnimationIsEntrance == isEntrance)
|| mKeyguardGoingAwayAnimation) {
// If we are trying to apply an animation, but already running
// an animation of the same type, then just leave that one alone.
// If we are in a keyguard exit animation, and the window should animate away, modify
// keyguard exit animation such that it also fades out.
if (mAnimation != null && mKeyguardGoingAwayAnimation
&& transit == WindowManagerPolicy.TRANSIT_PREVIEW_DONE) {
applyFadeoutDuringKeyguardExitAnimation();
}
return true;
}
// 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.
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#applyAnimationLocked");
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 (DEBUG_ANIM) Slog.v(TAG,
"applyAnimation: win=" + this
+ " anim=" + anim + " attr=0x" + Integer.toHexString(attr)
+ " a=" + a
+ " transit=" + transit
+ " isEntrance=" + isEntrance + " Callers " + Debug.getCallers(3));
if (a != null) {
if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + this);
setAnimation(a);
mAnimationIsEntrance = isEntrance;
}
} else {
clearAnimation();
}
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
if (mWin.mAttrs.type == TYPE_INPUT_METHOD) {
mService.adjustForImeIfNeeded(mWin.mDisplayContent);
if (isEntrance) {
mWin.setDisplayLayoutNeeded();
mService.mWindowPlacerLocked.requestTraversal();
}
}
return mAnimation != null;
}
具体根据样式来加载对应动画的细节不再详述,有兴趣可以查看源码
放一张时序图表示普通窗口动画的加载过程
image.png
2.activity组件动画的设置流程
前面一篇分析apptransition的文章说过,apptransition的最终步骤是调用windowSurfacePlacer的performSurfacePlacement函数来实现系统UI的刷新,同时设置并播放activity的组件动画,下面介绍一下这个过程
2.1系统UI刷新的主要流程
启动系统UI刷新的函数是定义在WindowSurfacePlacer.java中的requestTraversal(),这个函数进一步会执行performSurfacePlacement(),最终结果就是调用到performSurfacePlacementInner(),performSurfacePlacementInner() 是WMS服务中核心的部分,处理窗口属性变化,窗口排序,增加/删除窗口,以及窗口的切换和动画的显示等过程这里从app Transition入手,分析app Transition过程中的窗口动画设置以及显示的过程,其他内容暂不讨论
先看一下WindowSurfacePlacer中的几个关键函数
void requestTraversal() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mService.mH.sendEmptyMessage(DO_TRAVERSAL);
}
}
final void performSurfacePlacement() {
if (mDeferDepth > 0) {
return;
}
int loopCount = 6;
do {
mTraversalScheduled = false;
performSurfacePlacementLoop();
mService.mH.removeMessages(DO_TRAVERSAL);
loopCount--;
} while (mTraversalScheduled && loopCount > 0);
mWallpaperActionPending = false;
}
private void performSurfacePlacementLoop() {
if (mInLayout) {
return;
}
......
mInLayout = true;
......
}
try {
performSurfacePlacementInner(recoveringMemory);
mInLayout = false;
if (mService.needsLayout()) {
if (++mLayoutRepeatCount < 6) {
requestTraversal();
} else {
mLayoutRepeatCount = 0;
}
} else {
mLayoutRepeatCount = 0;
}
......
} catch (RuntimeException e) {
mInLayout = false;
}
......
}
简单来说,系统UI刷新就是通过执行一个至多6次的while循环实现的,核心函数是performSurfacePlacementInner,这些函数执行前后会用一些标志位来保证不被重复调用到,可以用以下一张流程图表示
image.png
2.2activity组件动画设置
activity组件动画是在窗口刷新的时候设置的,performSurfacePlacementInner()在执行的时候会检查是否接下来要执行的apptransition操作是否已经准备好,是的话则会调用相关函数
private void performSurfacePlacementInner(boolean recoveringMemory) {
...
if (mService.mAppTransition.isReady()) {
defaultDisplay.pendingLayoutChanges |= handleAppTransitionReadyLocked(defaultWindows);
}
...
mService.scheduleAnimationLocked();
...
}
在设置动画之前还会对接下来要打开的activity组件进行检查,判断是否可以执行activity组件操作,只有结果为true才能设置切换动画。
同时还会把前面prepareAppTransition()时设置的APP_TRANSITION_TIMEOUT这个message从消息队列中移除后面就是分别对即将要打开或关闭的activity组件设置切换动画,我们继续分析这一过程
private int handleAppTransitionReadyLocked(WindowList windows) {
int appsCount = mService.mOpeningApps.size();
if (!transitionGoodToGo(appsCount)) {
return 0;
}
……
mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
……
handleClosingApps(transit, animLp, voiceInteraction, mTmpLayerAndToken);
……
final AppWindowToken topOpeningApp = handleOpeningApps(transit, animLp, voiceInteraction, topClosingLayer);
……
mService.mAppTransition.goodToGo(openingAppAnimator, closingAppAnimator, mService.mOpeningApps, mService.mClosingApps);
mService.mAppTransition.postAnimationCallback();
mService.mAppTransition.clear();
mService.mOpeningApps.clear();
mService.mClosingApps.clear();
……
return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_CONFIG;
}
handleClosingApps()和handleOpeningApps()的函数内容大致相同,以handleOpeningApps()为例
private AppWindowToken handleOpeningApps(int transit, LayoutParams animLp, boolean voiceInteraction, int topClosingLayer) {
AppWindowToken topOpeningApp = null;
final int appsCount = mService.mOpeningApps.size();
for (int i = 0; i < appsCount; i++) { //1. 遍历所有要打开的activity组件
AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
if (!appAnimator.usingTransferredAnimation) {
appAnimator.clearThumbnail();
appAnimator.setNullAnimation(); //2. 清除原来的动画
}
wtoken.inPendingTransaction = false; //3. 表明对应的组件不是处于等待切换操作的状态
if (!mService.setTokenVisibilityLocked(wtoken, animLp, true, transit, false, voiceInteraction)){ // 4. 设置组件的可见性为true并设置切换动画
mService.mNoAnimationNotifyOnTransitionFinished.add(wtoken.token);
}
wtoken.updateReportedVisibilityLocked(); //5. 向AMS通知组件可见性
wtoken.waitingToShow = false; //6. 表示这个组件不是处于等待显示的状态
...
SurfaceControl.openTransaction();
...
}
return topOpeningApp;
}
具体设置组件切换动画是在设置窗口可见性的时候执行的,即setTokenVisibilityLocked函数
boolean setTokenVisibilityLocked(AppWindowToken wtoken, WindowManager.LayoutParams lp,
boolean visible, int transit, boolean performLayout, boolean isVoiceInteraction) {
boolean delayed = false;
...
boolean visibilityChanged = false;
if (wtoken.hidden == visible || (wtoken.hidden && wtoken.mIsExiting) || (visible && wtoken.waitingForReplacement())) {
boolean changed = false;
boolean runningAppAnimation = false;
if (transit != AppTransition.TRANSIT_UNSET) {
if (wtoken.mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
wtoken.mAppAnimator.setNullAnimation();
}
if (applyAnimationLocked(wtoken, lp, transit, visible, isVoiceInteraction)) {
delayed = runningAppAnimation = true;
}
...
}
... // 没有设置合适的切换动画的话则遍历窗口单独设置窗口动画
...
}
最后设置动画的操作是通过调用函数applyAnimationLocked(),通过这一系列操作,一个合适的切换动画就被设置给了AppWindowToken对象
private boolean applyAnimationLocked(AppWindowToken atoken, WindowManager.LayoutParams lp, int transit, boolean enter, boolean isVoiceInteraction) {
if (okToDisplay()) {
...
Animation a = mAppTransition.loadAnimation(lp, transit, enter, mCurConfiguration.uiMode,
mCurConfiguration.orientation, frame, displayFrame, insets, surfaceInsets,
isVoiceInteraction, freeform, atoken.mTask.mTaskId);
if (a != null) {
final int containingWidth = frame.width();
final int containingHeight = frame.height();
atoken.mAppAnimator.setAnimation(a, containingWidth, containingHeight,
mAppTransition.canSkipFirstFrame(), mAppTransition.getAppStackClipMode());
}
} else {
atoken.mAppAnimator.clearAnimation();
}
...
return atoken.mAppAnimator.animation != null;
}
最终返回动画的函数细节定义在AppTransition.java中
Animation loadAnimation(...) {
Animation a;
if (...) {
...
} else {
int animAttr = 0;
switch (transit) {
case TRANSIT_ACTIVITY_OPEN:
animAttr = enter ? WindowAnimation_activityOpenEnterAnimation : WindowAnimation_activityOpenExitAnimation;
break;
case TRANSIT_ACTIVITY_CLOSE:
animAttr = enter ? WindowAnimation_activityCloseEnterAnimation : WindowAnimation_activityCloseExitAnimation;
break;
case TRANSIT_DOCK_TASK_FROM_RECENTS:
case TRANSIT_TASK_OPEN:
animAttr = enter ? WindowAnimation_taskOpenEnterAnimation : WindowAnimation_taskOpenExitAnimation;
break;
...
}
a = animAttr != 0 ? loadAnimationAttr(lp, animAttr) : null;
...
}
return a;
}
再往后就是资源查找的过程,这里就不一一分析了,贴一张流程图表示以上过程
image.png
3.普通窗口动画与activity组件动画的关系
如果一个窗口属于activity组件,那么普通的窗口动画是会被activity组件给覆盖掉的,否则就执行普通的窗口动画,具体可以看到WindowStateAnimator.java中的定义,stackclip代表的窗口动画的优先级
private int resolveStackClip() {
// App animation overrides window animation stack clip mode.
if (mAppAnimator != null && mAppAnimator.animation != null) {
return mAppAnimator.getStackClip();
} else {
return mStackClip;
}
}