Android7.0窗口动画设置流程

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;
        }
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容