WindowManagerService动画分析

    我们知道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切换动画设置

    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()) {
     
  • 5
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值