Android P WMS(6) -- windowanimator

Android P WMS(1) -- wms简介

Android P WMS(2) -- wms初始化

Android O WMS(3) -- addwindow

Android P WMS(4) -- removewindow

Android P WMS(5) -- relayoutWindow

Android P WMS(6) -- windowanimator

Android P WMS(7) --wms 问题种类和debug技巧

Android P WMS(8) --View SYstem 简介

Android P WMS(9) --Surface

1. 动画概述

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

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

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

Window动画
     包括dialog显示隐藏,toast显示隐藏,系统window(keyguard navigationbar wallpaper  startwindow)进入或退出
过渡动画控制
     包括转屏动画,activity切换  fragment切换
View动画
关键类对应
AppWindowAnimator.java  设置,管理过渡动画
WindowStateAnimator.java  管理窗口状态和窗口动画
screenRotationAnimation.java  旋转动画
WindowAnimator.java  动画打包,wms只有一个此对象
关键流程
loadAnimation  setAnimation  stepAnimationLocked

 

2. applyEnterAnimationLocked动画资源的加载

Android P WMS(6) -- windowanimator_第1张图片


1)跟窗口有关的动画WindowStateAnimator,当一个Activity启动时,会调用到WMS的relayoutWindow来申请窗口,这其中就应用到窗口的切换动画,如果窗口进入动画,具体就是调用WindowStateAnimator.java中的函数applyEnterAnimationLocked。

/frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
void performSurfacePlacement(boolean recoveringMemory) {
    if (DEBUG_WINDOW_TRACE) Slog.v(TAG, "performSurfacePlacementInner: entry. Called by "
            + Debug.getCallers(3));


    if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
            ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
    mService.openSurfaceTransaction();      //1.1
    try {
        applySurfaceChangesTransaction(recoveringMemory, defaultDw, defaultDh);  //1.2
    } catch (RuntimeException e) {
        Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
    } finally {
        mService.closeSurfaceTransaction();     //1.3
        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
    }

    final WindowSurfacePlacer surfacePlacer = mService.mWindowPlacerLocked;

    // If we are ready to perform an app transition, check through all of the app tokens to be
    // shown and see if they are ready to go.
    if (mService.mAppTransition.isReady()) {
        defaultDisplay.pendingLayoutChanges |=
                surfacePlacer.handleAppTransitionReadyLocked();   //1.4

    }

    if (!mService.mAnimator.mAppWindowAnimating && mService.mAppTransition.isRunning()) {
        // We have finished the animation of an app transition. To do this, we have delayed a
        // lot of operations like showing and hiding apps, moving apps in Z-order, etc. The app
        // token list reflects the correct Z-order, but the window list may now be out of sync
        // with it. So here we will just rebuild the entire app window list. Fun!
        defaultDisplay.pendingLayoutChanges |=
                mService.handleAnimatingStoppedAndTransitionLocked();
        if (DEBUG_LAYOUT_REPEATS)
            surfacePlacer.debugLayoutRepeats("after handleAnimStopAndXitionLock",
                    defaultDisplay.pendingLayoutChanges);
    }

    
    // Check to see if we are now in a state where the screen should
    // be enabled, because the window obscured flags have changed.
    mService.enableScreenIfNeededLocked();

    mService.scheduleAnimationLocked(); //1.5
    mService.mWindowPlacerLocked.destroyPendingSurfaces();

    if (DEBUG_WINDOW_TRACE) Slog.e(TAG,
            "performSurfacePlacementInner exit: animating=" + mService.mAnimator.isAnimating());
}



 /frameworks/base/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
    int handleAppTransitionReadyLocked() {
        int appsCount = mService.mOpeningApps.size();
        if (!transitionGoodToGo(appsCount)) {   //1.4.1  transitionGoodToGo
            return 0;
        }
        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");

        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO");
        int transit = mService.mAppTransition.getAppTransition();
        if (mService.mSkipAppTransitionAnimation && !isKeyguardGoingAwayTransit(transit)) {
            transit = AppTransition.TRANSIT_UNSET;
        }
        mService.mSkipAppTransitionAnimation = false;
        mService.mNoAnimationNotifyOnTransitionFinished.clear();

        mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);  //1.4.2

        
        processApplicationsAnimatingInPlace(transit);

        mTmpLayerAndToken.token = null;
        handleClosingApps(transit, animLp, voiceInteraction, mTmpLayerAndToken);  //1.4.3
        final AppWindowToken topClosingApp = mTmpLayerAndToken.token;
        final int topClosingLayer = mTmpLayerAndToken.layer;

        final AppWindowToken topOpeningApp = handleOpeningApps(transit,  //1.4.4
                animLp, voiceInteraction, topClosingLayer);

        mService.mAppTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp);


    }

/frameworks/base/services/core/java/com/android/server/wm/WindowSurfacePlacer.java 
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++) {
  

        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                ">>> OPEN TRANSACTION handleAppTransitionReadyLocked()");
        mService.openSurfaceTransaction();
        try {
            mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());  //1.4.4.1
        } finally {
            mService.closeSurfaceTransaction();
            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                    "<<< CLOSE TRANSACTION handleAppTransitionReadyLocked()");
        }
       ...
    return topOpeningApp;
}
    
    
/frameworks/base/services/core/java/com/android/server/wm/AppWindowAnimator.java

// This must be called while inside a transaction.
boolean showAllWindowsLocked() {
    boolean isAnimating = false;
    final int NW = mAllAppWinAnimators.size();
    for (int i=0; i

 

 

WindowStateAnimator除了处理surface相关的操作,还处理动画流程的跟踪。

void applyEnterAnimationLocked() @WindowStateAnimator.java{
    final int transit;
    if (mEnterAnimationPending) {
        mEnterAnimationPending = false;
        transit = WindowManagerPolicy.TRANSIT_ENTER;
    }else{
        transit = WindowManagerPolicy.TRANSIT_SHOW;
    }
    applyAnimationLocked(transit, true);
}


从上面的函数中,进入动画分为TRANSIT_ENTER和TRANSIT_SHOW两种,当mEnterAnimationPending为true时,程序执行TRANSIT_ENTER动画。mEnterAnimationPending的值是在WMS中设置的,一种情况是新添加窗口addWindow时:winAnimator.mEnterAnimationPending= true;还有一种情况是relayoutVisibleWindow时,可见状态从GONE到VISIBLE:

if (oldVisibility ==View.GONE)winAnimator.mEnterAnimationPending = true;

与window相关的动画类型除了TRANSIT_ENTER和TRANSIT_SHOW外,还有:

WindowManagerPolicy.java
    /** Window has been added to the screen. */
    public static final int TRANSIT_ENTER = 1;
    /** Window has been removed from the screen. */
    public static final int TRANSIT_EXIT = 2;
    /** Window has been made visible. */
    public static final int TRANSIT_SHOW = 3;
    /** Window has been made invisible.
     * TODO: Consider removal as this is unused. */
    public static final int TRANSIT_HIDE = 4;
    /** The "application starting" preview window is no longer needed, and will
     * animate away to show the real window. */
    public static final int TRANSIT_PREVIEW_DONE = 5;

接着看applyAnimationLocked函数在处理TRANSIT_ENTER和TRANSIT_SHOW上的区别:

boolean applyAnimationLocked(int transit, boolean isEntrance) @WindowStateAnimator.java{
//如果当前正在执行动画跟这个进入动画是同类型的,那么系统不重复执行动画。
    if ((mLocalAnimating && mAnimationIsEntrance == isEntrance)|| mKeyguardGoingAwayAnimation) {
        if (mAnimation != null && mKeyguardGoingAwayAnimation
            && transit == WindowManagerPolicy.TRANSIT_PREVIEW_DONE) {
//如果tranit是 TRANSIT_PREVIEW_DONE,应用窗口已经绘制过了,那么动画类型将是app_starting_exit。
            applyFadeoutDuringKeyguardExitAnimation();
        }
        return true;
    }
//当前屏幕处于可显示状态。
    if (mService.okToDisplay()) {
//特殊窗口如状态栏、导航栏通过phoneWindowManager.java选择匹配的动画资源,这个函数直接返回的是动画资源的ID如:R.anim.dock_top_enter。
        int anim = mPolicy.selectAnimationLw(mWin, transit);
        int attr = -1;
        Animation a = null;
        if (anim != 0) {
//加载指定动画资源ID的动画资源。
            a = anim != -1 ? AnimationUtils.loadAnimation(mContext, anim) : null;
        }else{
//根据transit类型,获取相应的属性ID,如: com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation即是 TRANSIT_ENTER对应的属性id。
            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;
            }
//加载动画资源,先有属性值attr,获取对应的动画id,然后加载指定的资源。
             a = mService.mAppTransition.loadAnimationAttr(mWin.mAttrs, attr);
        }
//设置动画,把这个动画资源a记录到WindowStateAnimator.java中的变量mAnimation中。
        setAnimation(a);
    }else{
//如果当前屏幕不可见,清除动画。
        clearAnimation();
    }
}


/这个函数可以根据属性id,动态获取动画资源id,这意味着可以通过这些属性自定义动画资源。

Animation loadAnimationAttr(WindowManager.LayoutParams lp, int animAttr) @AppTransition.java{
        int anim = 0;
        Context context = mContext;
        if (animAttr >= 0) {
            AttributeCache.Entry ent = getCachedAnimations(lp);
            if (ent != null) {
                context = ent.context;
//获取动画id。
                anim = ent.array.getResourceId(animAttr, 0);
            }
        }
        if (anim != 0) {
//加载动画资源。
            return AnimationUtils.loadAnimation(context, anim);
        }
        return null;
    }

 

3.scheduleAnimationLocked开始动画

Android P WMS(6) -- windowanimator_第2张图片

前面只是动画资源的加载过程,下面看下动画是怎么执行起来的?

前面在分析窗口申请的过程中,分析过relayoutWindow中的调用performSurfacePlacement,在这个函数的最后调用了mService.scheduleAnimationLocked(),来安排动画的执行。

void scheduleAnimationLocked() @WindowManagerService.java{
        if (!mAnimationScheduled) {
            mAnimationScheduled = true;
//通过Choreographer设置一个触发源,mChoreographer 是通过ThreadLocal保存的,所以它是一个线程中的单实例。
            mChoreographer.postFrameCallback(mAnimator.mAnimationFrameCallback);
        }
    }    

mAnimationScheduled为true,表示当前正在scheduleanimantion,在mAnimationFrameCallback的回调中会把mAnimationScheduled再次置为false。

Choreographer设置的动画的触发源是什么?这样看下Choreographer这个类的主要功能是什么:Choreographer会接从显示系统接收定时脉冲(如Vsync),然后安排渲染下一个显示帧的工作。动画的触发源就是Vsync。

在动画框架或view层次中,应用程序通常使用较高的抽象跟Choreography间接交互,如:使用android.animation.ValueAnimator#start方法,post一个动画与渲染帧同步处理。

使用View#postOnAnimation方法,post一个Runnable,让其在下一个显示帧开始时调用。

有些情况,可能需要直接使用choreography的函数,如:

如果应用程序想在不同的线程里做渲染,可能是用GL,或者不使用animation框架、View层级,又要确保跟显示同步,这时可以使用Choreographer#postFrameCallback。

Choreography一方面接收VSYNC信号,另一方面把这一事件转发给感兴趣的人,所有希望监听VSYNC信号的对象都要在Choreography中注册。

scheduleAnimationLocked中调用的是postFrameCallback,然后直接调用了postFrameCallbackDelayed。

public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) @Choreographer.java{
        postCallbackDelayedInternal(CALLBACK_ANIMATION,
                callback, FRAME_CALLBACK_TOKEN, delayMillis);
    }

这里的CALLBACK_ANIMATION是回调类型,Choreography将注册者分为三个类型:CALLBACK_INPUT,CALLBACK_ANIMATION,CALLBACK_TRAVERSAL(处理layout和draw)。

注册的回调都放在一个数组mCallbackQueues中。

当Vsync信号产生后,Choreography会通过doCallbacks,进一步调用到相应注册对象的doFrame函数。

这里回调的是WindowAnimator.java中的doFrame。

WindowAnimator.java
        mAnimationFrameCallback = new Choreographer.FrameCallback() {
            public void doFrame(long frameTimeNs) {
                synchronized (mService.mWindowMap) {
                    mService.mAnimationScheduled = false;
                    animateLocked(frameTimeNs);
                }
            }
        };


通过animateLocked,各种动画开始单步执行。

private void animateLocked(long frameTimeNs) {
//当前时间。
    mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
//记录上一次的动画状态,判断动画是否结束。
    boolean wasAnimating = mAnimating;
//先本地记录对surface的更改,最后统一统计给surfaceflinger。
    SurfaceControl.openTransaction();
    SurfaceControl.setAnimationTransaction();
 
    try {
        final int numDisplays = mDisplayContentsAnimators.size();
//循环处理每个显示屏中的动画。
        for (int i = 0; i < numDisplays; i++) {
            final int displayId = mDisplayContentsAnimators.keyAt(i);
//执行appTransition中设置的appWindow动画,
            updateAppWindowsLocked(displayId);
//执行屏幕旋转动画,比如屏幕的横竖屏。
            final ScreenRotationAnimation screenRotationAnimation 
                =displayAnimator.mScreenRotationAnimation;
            if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
                if (screenRotationAnimation.stepAnimationLocked(mCurrentTime)) {
//动画没有结束,通过setAnimating把mAnimating设置为true。
                    setAnimating(true);
                }else{
//动画结束了。
                    …...
                }
            }
//更新每个所有应用中的动画,包括与退出、移除app相关的。这里会执行WindowStateAnimator动画。
            updateWindowsLocked(displayId);
            updateWallpaperLocked(displayId);
 
 
            final WindowList windows = mService.getWindowListLocked(displayId);
            final int N = windows.size();
            for (int j = 0; j < N; j++) {
//更新surface。
                windows.get(j).mWinAnimator.prepareSurfaceLocked(true);
            }
        }
//动画还没结束,再次调用 scheduleAnimationLocked。
        if (mAnimating) {
            mService.scheduleAnimationLocked();
        }
    }finally{
//统一提交给surfaceflinger处理。
        SurfaceControl.closeTransaction();
    }
 
//在执行动画最后一步step时, mAnimating为false, wasAnimating记录是上一次mAnimating 的值,所以为true,这里将请求一次遍历,计算窗口大小,performsurface等。
    if (!mAnimating && wasAnimating) {
        mWindowPlacerLocked.requestTraversal();
    }
}

不管那类动画,要更新动画调用的都是stepAnimationLocked,指定动画的单步,就是每个特定的时间计算动画的最新状态。如果函数true,说明动画还在进行,否则返回false。

boolean stepAnimationLocked(long currentTime) @WindowStateAnimator.java{
//mWasAnimating 保存上一次的状态,通过比较mWasAnimating 和 isAnimationSet(),就知道动画是刚刚开始,还是停止了。    mWasAnimating = mAnimating;
//第一次执行动画,需要做些初始化。
    if (!mLocalAnimating) {
        mAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(),
            displayInfo.appWidth, displayInfo.appHeight);
        mAnimDx = displayInfo.appWidth;
        mAnimDy = displayInfo.appHeight;
        mAnimation.setStartTime(mAnimationStartTime != -1
            ? mAnimationStartTime : currentTime);
//已经执行过一次, mLocalAnimating置为true。
        mLocalAnimating = true;
//表示当前正在执行动画。
        mAnimating = true;
    }
 
// mAnimation做非null判断,因为不是每个WindowStateAnimator当前都有动画在执行。
    if ((mAnimation != null) && mLocalAnimating) {
        mLastAnimationTime = currentTime;
//执行一次单步,计算当前的动画状态,返回true,表示动画还没结束。
        if (stepAnimation(currentTime)) {
            return true;
        }
    }
}

如何执行一个单步。

private boolean stepAnimation(long currentTime) @WindowStateAnimator.java{
    currentTime = getAnimationFrameTime(mAnimation, currentTime);
    mTransformation.clear();
    final boolean more = mAnimation.getTransformation(currentTime, mTransformation);
    return more;
}

上面的函数会根据mAnimation设置的开始时间,结合当前时间,计算出动画的新的变换值,动画的四个要素是平移、缩放、旋转、透明度,在getTransformation()中将会根据设置的属性分别调用相应类型的applyTransformation,计算变换值,保存在mTransformation中

中,其中平移、缩放、旋转由mMatrix表示,透明度由mAlpha表示。

完成动画数据的计算,接着就是真正显示到屏幕上,先由WMS对surface做更新,具体就是prepareSurfaceLocked的调用。

最后通过SurfaceControl.closeTransaction()关闭业务,将surface中的更新信息统一传给SurfaceFlinger,然后写入到framebuffer中,完成显示。
 

参考:

Android 7.1 GUI系统-窗口管理WMS-窗口动画、应用动画的加载(六)

Android 7.1 GUI系统-窗口管理WMS-动画的执行(七)

Android6.0 WMS(六) WMS动画管理

深入理解Android 卷3

 

 

你可能感兴趣的:(WMS)