美文网首页
Android7.0窗口动画设置流程

Android7.0窗口动画设置流程

作者: Lonelyyy | 来源:发表于2017-11-15 22:45 被阅读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;
            }
        }
    

    相关文章

      网友评论

          本文标题:Android7.0窗口动画设置流程

          本文链接:https://www.haomeiwen.com/subject/ltfvpxtx.html