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動畫資源的加載


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<NW; i++) {
        WindowStateAnimator winAnimator = mAllAppWinAnimators.get(i);
        if (DEBUG_VISIBILITY) Slog.v(TAG, "performing show on: " + winAnimator);
        winAnimator.mWin.performShowLocked();  //1.4.4.1.1
        isAnimating |= winAnimator.isAnimationSet();
    }
    return isAnimating;
}
    
@/frameworks/base/services/core/java/com/android/server/wm/WindowState.java 
boolean performShowLocked() {
    ...
    logPerformShow("Showing ");

    mService.enableScreenIfNeededLocked();
    mWinAnimator.applyEnterAnimationLocked();   //1 動畫資源的加載

    // Force the show in the next prepareSurfaceLocked() call.
    mWinAnimator.mLastAlpha = -1;
    if (DEBUG_SURFACE_TRACE || DEBUG_ANIM) Slog.v(TAG,
            "performShowLocked: mDrawState=HAS_DRAWN in " + this);
    mWinAnimator.mDrawState = HAS_DRAWN;
    mService.scheduleAnimationLocked();   //2 很多地方比如hideLW之類都會scheduleAnimationLocked

  

    return true;
}

 

 

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開始動畫

前面只是動畫資源的加載過程,下面看下動畫是怎麼執行起來的?

前面在分析窗口申請的過程中,分析過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

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章