Android6.0 WMS(十) WMS窗口动画从设置到显示框架

       在前一文中,我们分析了Activity组件的切换过程。从这个过程可以知道,所有参与切换操作的窗口都会被设置切换动画。事实上,一个窗口在打开(关闭)的过程中,除了可能会设置切换动画之外,它本身也可能会设置有进入(退出)动画。再进一步地,如果一个窗口是附加在另外一个窗口之上的,那么被附加窗口所设置的动画也会同时传递给该窗口。

在之前WMS的第六篇博客窗口管理,主要从VSync信号作为切入点分析窗口动画的流程。这里我们就详细分析WindowManagerService服务显示窗口动画的原理。

        在Android系统中,窗口动画的本质就是对原始窗口施加一个变换(Transformation)。在线性数学中,对物体的形状进行变换是通过乘以一个矩阵(Matrix)来实现,目的就是对物体进行偏移、旋转、缩放、切变、反射和投影等。因此,给窗口设置动画实际上就给窗口设置一个变换矩阵(Transformation Matrix)。

        如前所述,一个窗口在打开(关闭)的过程,可能会被设置三个动画,它们分别是窗口本身所设置的进入(退出)动画(Self Transformation)、从被附加窗口传递过来的动画(Attached Transformation),以及宿主Activity组件传递过来的切换动画(App Transformation)。这三个Transformation组合在一起形成一个变换矩阵,以60fps的速度应用在窗口的原始形状之上,完成窗口的动画过程。

        窗口的变换矩阵是应用在窗口的原始位置和大小之上的,因此,在显示窗口的动画之前,除了要给窗口设置变换矩阵之外,还要计算好窗口的原始位置和大小,以及布局和绘制好窗口的UI。我们在之前博客已经分析过窗口的位置和大小计算过程以及窗口UI的布局和绘制过程了,本文主要关注窗口动画的设置、合成和显示过程。这三个过程通过以下四个部分的内容来描述:
       1. 窗口动画的设置过程
       2. 窗口动画的显示框架
       3. 窗口动画的推进过程
       4. 窗口动画的合成过程
       其中,窗口动画的设置过程包括上述三个动画的设置过程,窗口动画的推进过程是指定动画的一步一步地迁移的过程,窗口动画的合成过程是指上述三个动画组合成一个变换矩阵的过程,后两个过程包含在了窗口动画的显示框架中。

一. 窗口动画的设置过程


        窗口被设置的动画虽然可以达到三个,但是这三个动画可以归结为两类,一类是普通动画,例如,窗口在打开过程中被设置的进入动画和在关闭过程中被设置的退出动画,另一类是切换动画。其中,Self Transformation和Attached Transformation都是属于普通动画,而App Transformation属于切换动画。接下来我们就分别分析这两种类型的动画的设置过程。

1.1 普通动画的设置

窗口在打开的过程中,是通过调用WindowState类的成员函数performShowLocked来实现的,WindowStateAnimator类的成员函数performShowLocked调用了applyEnterAnimationLocked函数来给当前正在处理的窗口设置一个进入动画,如下所示:
    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);
        }
    }
       如果mEnterAnimationPending的值等于true,那么就说明它所描述的窗口正在等待显示,也就是正处于不可见到可见状态的过程中,那么函数applyEnterAnimationLocked就会对该窗口设置一个类型为WindowManagerPolicy.TRANSIT_ENTER的动画,否则的话,就会对该窗口设置一个类型为WindowManagerPolicy.TRANSIT_SHOW的动画。
        确定好窗口的动画类型之后,就调用另外一个成员函数applyAnimationLocked来为窗口创建一个动画了。接下来我们先分析窗口在关闭的过程中所设置的动画类型,然后再来分析applyAnimationLocked函数的实现。
        当应用程序进程请求WindowManagerService服务刷新一个窗口的时候,会调用到WindowManagerService类的成员函数relayoutWindow。WindowManagerService类的成员函数relayoutWindow在执行的过程中,如果发现需要将一个窗口从可见状态设置为不可见状态时,也就是发现需要关闭一个窗口时,就会对该窗口设置一个退出动出,如下所示:
            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;
                        }
WindowState对象win描述的便是要刷新的窗口。当参数viewVisibility的值不等于View.VISIBLE时,就说明要将WindowState对象win所描述的窗口设置为不可见。另一方面,如果WindowState对象win的成员变量mAppToken的值不等于null,并且这个成员变量所指向的一个AppWindowToken对象的成员变量clientHidden的值等于true,那么就说明WindowState对象win所描述的窗口是一个与Activity组件相关的窗口,并且该Activity组件是处于不可见状态的。在这种情况下,也需要将WindowState对象win所描述的窗口设置为不可见。
        一旦WindowState对象win所描述的窗口要设置为不可见,就需要考虑给它设置一个退出动画,不过有四个前提条件:
        1. 该窗口有一个绘图表面,即WindowState对象win的成员变量mSurface的值不等于null;
        2. 该窗口的绘图表面不是处于等待销毁的状态,即WindowState对象win的成员变量mSurfacePendingDestroy的值不等于true;
        3. 该窗口不是处于正在关闭的状态,即WindowState对象win的成员变量mExiting的值不等于true;
        4. 该窗口当前正在处于可见的状态,即WindowState对象win的成员isWinVisibleLw的返回值等于true。
        在满足上述四个条件的情况下,就说明WindowState对象win所描述的窗口的状态要由可见变为不可见,因此,就需要给它设置一个退出动画,即一个类型为WindowManagerPolicy.TRANSIT_EXIT的动画,这同样是通过调用WindowManagerService类的成员函数applyAnimationLocked来实现的。
        从上面的分析就可以知道,无论是窗口在打开时所需要的进入动画,还是窗口在关闭时所需要的退出动画,都是通过调用applyAnimationLocked来设置的,它的实现如下所示:
    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;
    }
参数transit描述的是要设置的动画的类型,而参数isEntrance描述的是要设置的动画是进入类型还是退出类型的。
        如果成员变量mLocalAnimating的值等于true,那么就说明它所描述的窗口已经被设置过动画了,并且这个动画正在显示的过程中。在这种情况下,如果这个mAnimationIsEntrance的值等于参数isEntrance的值,那么就说明该窗口正在显示的动画就是所要求设置的动画,这时候就不需要给窗口重新设置一个动画了,因此就直接返回了。
        我们假设设置一个新的动画,这时候还需要继续判断屏幕当前是否是处于非冻结和点亮的状态的。只有在屏幕不是被冻结并且是点亮的情况下,applyAnimationLocked才真正需要设置一个动画,否则的话,设置了也是无法显示的。当WindowManagerService类的成员变量mDisplayFrozen的时候,就说明屏幕不是被冻结的,而当WindowManagerService类的成员变量mPolicy所指向的一个PhoneWindowManager对象的成员函数isScreenOn的返回值等于true的时候,就说明屏幕是点亮的。在满足上述两个条件的情况下(也就是okToDisplay),applyAnimationLocked就开始给参数win所描述的窗口创建动画了。 
        applyAnimationLocked首先是检查WindowManagerService类的成员变量mPolicy所指向的一个PhoneWindowManager对象是否可以为参数win所描述的窗口提供一个类型为transit的动画。如果可以的话,那么调用这个PhoneWindowManager对象的成员函数selectAnimationLw的返回值anim就不等于0,这时候applyAnimationLocked就可以调用AnimationUtils类的静态成员函数loadAnimation来根据该返回值anim来创建一个动画,并且保存在变量a中。
        如果WindowManagerService类的成员变量mPolicy所指向的一个PhoneWindowManager对象不可以为参数win所描述的窗口提供一个类型为transit的动画的话,那么applyAnimationLocked就需要根据该窗口的布局参数来创建这个动画了。这个创建过程分为两步执行:
        1. 将参数transit的值转化为一个对应的动画样式名称;
        2. 调用WindowManagerService类的mAppTransitiond的成员函数loadAnimation来在指定的窗口布局参数中创建前面第1步所指定样式名称的动画,并且保存在变量a中,其中,指定的窗口布局参数是由WindowState对象win的成员变量mAttrs所指向的一个WindowManager.LayoutParams对象来描述的。
        最后,如果变量a的值不等于null,即前面成功地为参数win所描述的窗口创建了一个动画,那么接下来就会将该动画设置给参数win所描述的窗口。这是通过setAnimation函数来实现的,最终保存在mAnimation变量中。同时,还会将参数isEntrance的值保存在参数成员变量mAnimationIsEntrance,以表明前面给它所设置的动画是属于进入类型还是退出类型的。

1.2 切换动画的设置过程

如果一个窗口属于一个Activity组件窗口,那么当该Activity组件被切换的时候,就会被设置一个切换动画,这是通过调用WindowManagerService类的成员函数setTokenVisibilityLocked来实现的,如下所示:
    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;
    }
参数wtoken描述的是要切换的Activity组件,参数lp描述的是要用来创建切换动画的布局参数,参数transit描述的是要创建的切换动画的类型,而参数visible描述的是要切换的Activity组件接下来是否是可见的。
        WindowManagerService类的成员函数setTokenVisibilityLocked首先判断要切换的Activity组件当前的可见性是否已经就是要设置的可见性,即参数wtoken所指向的一个AppWindowToken对象的成员变量hidden的值是否不等于参数visible的值。如果不等于的话,就说明要切换的Activity组件当前的可见性已经就是要设置的可见性了,这时候WindowManagerService类的成员函数setTokenVisibilityLocked就不用再为它设置切换动画了。
        我们假设要切换的Activity组件当前的可见性不是要求设置的可见性,即参数wtoken所指向的一个AppWindowToken对象的成员变量hidden的值等于参数visible的值,那么WindowManagerService类的成员函数setTokenVisibilityLocked还会继续检查参数transit描述的是否是一个有效的动画类型,即它的值是否不等于WindowManagerPolicy.TRANSIT_UNSET。如果参数transit描述的是一个有效的动画类型的话,那么WindowManagerService类的成员函数setTokenVisibilityLocked接下来就会执行以下三个操作:
        1. 判断要切换的Activity组件当前是否被设置了一个哑动画,即参数wtoken所指向的一个AppWindowToken对象的成员变量animation是否与WindowManagerService类的成员变量sDummyAnimation指向了同一个Animation对象。如果是的话,那么就会将wtoken所指向的一个AppWindowToken对象的成员变量animation的值设置为null,因为接下来要重新为它设置一个新的Animation对象。一个需要参与切换的Activity组件会设置可见性的时候,是会被设置一个哑动画的。
        2. 调用WindowManagerService类的四个参数版本的成员函数applyAnimationLocked根据参数lp、transit和visible的值来为要切换的Activity组件创建一个动画。
        3. 如果第2步可以成功地为要切换的Activity组件创建一个动画的话,那么这个动画就会保存在wtoken所指向的一个AppWindowToken对象的成员变量animation中,这时候就会将变量delayed和runningAppAnimation的值均设置为true。
        变量runningAppAnimation的值等于true意味着与参数wtoken所描述的Activity组件所对应的窗口接下来要执行一个切换动画。在这种情况下,WindowManagerService类的成员函数setTokenVisibilityLocked就不需要为这些窗口单独设置一个进入或者退出类型的动画,否则的话,WindowManagerService类的成员函数setTokenVisibilityLocked就会根据这些窗口的当前可见性状态以及参数wtoken所描述的Activity组件被要求设置的可见性来单独设置一个进入或者退出类型的动画:

        1. 如果一个窗口当前是不可见的,即用来描述它的一个WindowState对象的成员函数isVisibleNow的返回值等于false,但是参数wtoken所描述的Activity组件被要求设置成可见的,即参数visible的值等于true,那么就需要给该窗口设置一个类型为WindowManagerPolicy.TRANSIT_ENTER的动画;
        2. 如果一个窗口当前是可见的,即用来描述它的一个WindowState对象的成员函数isVisibleNow的返回值等于true,但是参数wtoken所描述的Activity组件被要求设置成不可见的,即参数visible的值等于false,那么就需要给该窗口设置一个类型为WindowManagerPolicy.TRANSIT_EXIT的动画。
        给与参数wtoken所描述的Activity组件所对应的窗口设置动画是通过调用WindowStateAnimator的成员函数applyAnimationLocked来实现的,这个成员函数在前面已经分析过了。另外,与参数wtoken所描述的Activity组件所对应的窗口是保存在参数wtoken所指向的一个AppWindowToken对象的成员变量allAppWindows所描述的一个ArrayList中的,因此,通过遍历这个ArrayList,就可以为与参数wtoken所描述的Activity组件所对应的每一个窗口设置一个动画。
        最后,如果前面成功地为参数wtoken所描述的Activity组件创建了一个切换动画,即该参数所描述的一个AppWindowToken对象的成员变量animation的值不等于null,那么WindowManagerService类的成员函数setTokenVisibilityLocked的返回值delayed就会等于true,表示参数wtoken所描述的Activity组件要执行一个动换动画,同时也表明该Activity组件的窗口要延迟到切换动画显示结束后,才真正显示出来。
        接下来,我们继续分析WindowManagerService类的四个参数版本的成员函数applyAnimationLocked的实现,以便可以了解Activity组件的切换动画的创建过程,如下所示:

    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;
    }
这个函数基本和前面WindowStateAnimator的的applyAnimationLocked函数类似,不过最后是设置在AppWindowToken的mAppAnimator的animation变量中。


二、窗口动画的显示框架


 窗口动画是在WindowManagerService服务刷新系统UI的时候显示的。WindowManagerService服务刷新系统UI是通过调用WindowManagerService类的成员函数performLayoutAndPlaceSurfacesLockedInner来实现的,这个函数的框架已经和老罗那篇博客分析的有点不一样了,接下来我们就主要分析与窗口动画的显示框架相关的逻辑,如下所示:
    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信号过来,开始动画流程)

    }
我们先来看WindowStateAnimator的commitFinishDrawingLocked函数,将WindowStateAnimator的mDrawState的状态设置成READY_TO_SHOW,当其窗口的 APPWindowToken的allDrawn为true会调用performShowLocked(当然这里还不是),当窗口是TYPE_APPLICATION_STARTING时会调用performShowLocked,之前分析Activity启动窗口的博客 http://blog.csdn.net/kc58236582/article/details/54016765分析过)。
    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 stacks = displayContent.getStacks();
        for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
            final ArrayList 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;
                            }
                        }
                    }
我们再来看WindowState的isDrawnLw如何看窗口是否已经绘制过了,代码如下。首先必要要有Surface,然后其WindowStateAnimator的mDrawState为READY_TO_SHOW或者是HAS_DRAWN状态。
    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函数。


ViewRootImpl的performDraw函数会调用mWindowSession.finishDrawing(mWindow),然后Session会调用WMS的finishDrawingWindow函数,如下,这个函数又会调用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函数了。

我们下面再看看,哪里会再次界面刷新呢,我们来看performLayoutAndPlaceSurfacesLockedLoop函数,当调用完performLayoutAndPlaceSurfacesLockedInner函数后,当调用函数needsLayout函数还需要刷新布局时,会调用requestTraversalLocked函数请求再次刷新。
    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);
        }

    }
我们先看看needsLayout函数,就是遍历各个DisplayContent,看看layoutNeeded是否有true。联想到我们前面在updateAllDrawnLocked函数会把这个layoutNeeded置为 true。
    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;
    }
然后就会调用requestTraversalLocked函数,再次刷新布局。这个函数就是发送消息。
    void requestTraversalLocked() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mH.sendEmptyMessage(H.DO_TRAVERSAL);
        }
    }
这个消息会再次调用performLayoutAndPlaceSurfacesLocked函数来刷新布局。
                case DO_TRAVERSAL: {
                    synchronized(mWindowMap) {
                        mTraversalScheduled = false;
                        performLayoutAndPlaceSurfacesLocked();
                    }
                } break;

三、动画显示

最后我们再来回顾下,当我们是普通应用的启动或者就是正常显示,我们就是走上面流程,最后到commitFinishDrawingLocked函数。这个函数,如果是应用全部绘制结束还是TYPE_APPLICATION_STARTING类型的启动窗口,最后调用performShowLocked函数来显示动画。这个函数我们后面再分析。
    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;
    }
Activity切换我们在 http://blog.csdn.net/kc58236582/article/details/54092667博客中分析,是通过在在WMS的performLayoutAndPlaceSurfacesLocked调用如下代码。
        if (mAppTransition.isReady()) {
            defaultDisplay.pendingLayoutChanges |= handleAppTransitionReadyLocked(defaultWindows);
            if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after handleAppTransitionReadyLocked",
                    defaultDisplay.pendingLayoutChanges);
        }
然后在handleAppTransitionReadyLocked函数中会调用setTokenVisibilityLocked函数,设置切换动画,最后再调用AppWindowAnimator的showAllWindowsLocked函数来调用了WindowStateAnimator的performShowLocked函数,来显示动画。
    boolean showAllWindowsLocked() {
        boolean isAnimating = false;
        final int NW = mAllAppWinAnimators.size();
        for (int i=0; i
最后殊途同归都到了WindowStateAnimator的performShowLocked函数。这个函数主要先调用applyEnterAnimationLocked函数设置普通应用的进去或者退出动画,然后将mDrawState的状态设置为HAS_DRAWN,最后调用WMS的scheduleAnimationLocked函数(这个函数在 http://blog.csdn.net/kc58236582/article/details/53835998博客中分析过),这样当有VSync信号过来,就可以调用WindowAnimator的animateLocked函数播放动画了。
    boolean performShowLocked() {
        ......
        if (mDrawState == READY_TO_SHOW && mWin.isReadyForDisplayIgnoringKeyguard()) {            

            mService.enableScreenIfNeededLocked();

            applyEnterAnimationLocked();

            ......
            mDrawState = HAS_DRAWN;
            mService.scheduleAnimationLocked();

            ......

            return true;
        }

        return false;
    }

下篇博客我们再继续分析下动画的播放过程。





你可能感兴趣的:(android,WMS)