在前一文中,我們分析了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;
- }