Android窗口系統第四篇---Activity動畫的設置過程

不管是系統中窗口的動畫,仍是應用中某一個View的動畫,它們的原理都是同樣的。當一個窗口打開的時候,爲了看起來更緩和一點,系統都會給每個Activity窗口添加一個動畫,關於動畫的部分,我所想寫的有四點。第1、動畫有哪些類型;第二動畫是怎麼設置的,因爲窗口動畫和過分動畫(Activity窗口動畫)是不同的,就須要分開講,在小米手機上,能夠去開發者選項中將窗口動畫的播放速度降慢5倍或者10倍,能夠清楚的看到動畫的過程。第三,動畫設置完成以後,怎麼觸發垂直刷新信號一幀幀顯示的,因爲一個窗口可能存在多個動畫,好比轉屏動畫、過分動畫、窗口動畫,自身動畫等等,最終交給SurfaceFlinger繪製顯示的時候,須要合成爲一個動畫,因此在談一下動畫的合成;第四,簡單總結應用動畫,用一個貝塞爾曲線繪製直播間點贊效果的例子講解一下,總結而言,系統中的動畫和應用中的動畫原理是同樣的,這篇文章是站在系統的角度上,搞清楚動畫的原理,本文基於Android7.0源碼。java

####1、動畫類型
在Apptransition.java中定義了不少動畫的類型,每一個類型以一個int值來表示。android

動畫類型 含義
TRANSIT_UNSET -1 初始值,還沒有設定
TRANSIT_NONE 0 沒有動畫
TRANSIT_ACTIVITY_OPEN 6 在同一task中在最頂端打開一個窗口
TRANSIT_ACTIVITY_CLOSE 7 關閉當前活動窗口,恢復同一個task中的上一個窗口
TRANSIT_TASK_OPEN 8 新建任務並建立窗口
TRANSIT_TASK_CLOSE 9 關閉當前活動窗口,回到上一個任務
TRANSIT_TASK_TO_FRONT 10 將任務移至最頂
TRANSIT_TASK_TO_BACK 11 將當前任務移至最末
TRANSIT_WALLPAPER_CLOSE 12 關閉到無牆紙的應用
TRANSIT_WALLPAPER_OPEN 13 啓動牆紙應用
TRANSIT_WALLPAPER_INTRA_OPEN 14 有牆紙打開
TRANSIT_WALLPAPER_INTRA_CLOSE 15 有牆紙關閉

默認是沒有動畫,即類型是TRANSIT_UNSET,拿Activity的啓動來舉例子,好比當一個Activity打開的時候,那麼系統就會設置一個TRANSIT_ACTIVITY_OPEN的動畫,若是你startActivity組件的時候,Intent對象帶有FLAG_ACTIVITY_NO_ANIMATION這樣的flag,那麼系統就會給你設置一個TRANSIT_NONE,表示沒有動畫,不須要動畫,若是你指定了lauchMode,跨Task棧新起了一個Actiivty,那麼就會設置一個TRANSIT_TASK_OPEN類型,表示新建任務並建立窗口時候要用的動畫,同理當Activity的關閉的時候,也相似,總之根據不一樣的case,設置不一樣的類型,後面根據這個設置好的類型,加載不一樣的動畫。windows

####2、動畫設置
瞭解了動畫類型了,咱們看一下Activity切換的時候,動畫是怎麼設置的,先簡單看一下Activity的切換。bash

#####一、Activiy切換
app

Activiy切換
Activiy切換

什麼是Activity的切換呢?
  前一個Activity從resume狀態變成pause狀態,後一個Activity進入到resume狀態,將前一個resume狀態的窗口設置成不可見,後一個窗口設置成可見。less

切換的步驟ide

  • ActivityStack類的成員函數startActivityLocked首先會給正在啓動的Activity組件準備一個切換操做,這裏所說的切換操做,你能夠理解成前面設置的動畫類型。
  • 接着再調用其它的成員函數來通知前一個激活的Activity組件進入到Paused狀態。
  • 等到前一個激活的Activity組件進入到Paused狀態以後,ActivityManagerService服務就會檢查用來運行正在啓動的Activity組件的進程啓動起來了沒有。若是這個進程尚未啓動,那麼ActivityManagerService服務就會將該進程啓動起來,而後再調用ActivityStack類的成員函數realStartActivityLocked來將正在啓動的Activity組件加載起來,而且將它的狀態設置爲Resumed。這裏面具體又分紅兩個小點,一是setAppVisibility 、二是通知lauch Activity。
  • 最後通知WindowManagerService服務執行前面所準備的切換操做

首先梳理一下prepareAppTransition方法。設置什麼樣的切換操做,其實由Activity的行爲決定。函數

frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

    @Override
    public void prepareAppTransition(int transit, boolean alwaysKeepCurrent) {
        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
                "prepareAppTransition()")) {
            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
        }
        synchronized(mWindowMap) {
            boolean prepared = mAppTransition.prepareAppTransitionLocked(
                    transit, alwaysKeepCurrent);
            //prepared爲ture,說明已經被成功設置了切換操做,可是當前凍屏、熄屏、Display沒有準備好的狀況下,
            //設置mSkipAppTransitionAnimation等於true,表示要跳過這切換操做對應動畫的執行。
            if (prepared && okToDisplay()) {
                mSkipAppTransitionAnimation = false;
            }
        }
    }複製代碼
frameworks/base/services/core/java/com/android/server/wm/AppTransition.java

    boolean prepareAppTransitionLocked(int transit, boolean alwaysKeepCurrent) {
       .....
        //isTransitionSet()表示已經設置了切換操做類型
        if (!isTransitionSet() || mNextAppTransition == TRANSIT_NONE) {
            setAppTransition(transit);
        } else if (!alwaysKeepCurrent) {
            //alwaysKeepCurrent若等於true,表示要維持上次設置的切換類型,本次新設置的不能覆蓋它
            if (transit == TRANSIT_TASK_OPEN && isTransitionEqual(TRANSIT_TASK_CLOSE)) {
                // Opening a new task always supersedes a close for the anim.
                setAppTransition(transit);
            } else if (transit == TRANSIT_ACTIVITY_OPEN
                    && isTransitionEqual(TRANSIT_ACTIVITY_CLOSE)) {
                // Opening a new activity always supersedes a close for the anim.
                setAppTransition(transit);
            }
        }
        boolean prepared = prepare();
        if (isTransitionSet()) {
            //發送一個5秒的超時消息給WMS運行的線程(android.display線程),表示要在5秒時間類完成設置的切換操做。
            mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
            mService.mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, APP_TRANSITION_TIMEOUT_MS);
        }
        return prepared;
    }複製代碼

若是transit == TRANSIT_TASK_OPEN 而且isTransitionEqual(TRANSIT_TASK_CLOSE)返回true,表示上次(以前)給Activity設置的切換操做是TRANSIT_TASK_CLOSE,那麼能夠調用setAppTransition,由於打開的動畫要比關閉的動畫優先級要高。oop

若是transit == TRANSIT_ACTIVITY_OPEN 而且isTransitionEqual(TRANSIT_ACTIVITY_CLOSE)返回true,表示上次(以前)給Activity設置的切換操做是TRANSIT_ACTIVITY_CLOSE,那麼能夠調用setAppTransition,由於打開的動畫要比關閉的動畫優先級要高。post

frameworks/base/services/core/java/com/android/server/wm/AppTransition.java
    private void setAppTransition(int transit) {
        mNextAppTransition = transit;
        setLastAppTransition(TRANSIT_UNSET, null, null);
    }複製代碼

setAppTransition執行事後,而且前一個激活的Activity組件進入到Paused狀態了,而且客戶端進程已經啓動了,這個時候ActivityManagerService服務就會調用ActivityStack類的成員函數realStartActivityLocked來將正在啓動的Activity組件加載起來,而且將它的狀態設置爲Resumed,首先看一下setAppVisibility,將窗口可見性的設置。

frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

  @Override
    public void setAppVisibility(IBinder token, boolean visible) {
        .....
        AppWindowToken wtoken;

        synchronized(mWindowMap) {
           //經過ActivityRecord:Token找到AppWindowToken,即找到這個token對應的Activity窗口
            wtoken = findAppWindowToken(token);
            if (wtoken == null) {
                Slog.w(TAG_WM, "Attempted to set visibility of non-existing app token: " + token);
                return;
            }
            .....
             //mOpeningApps是WMS的成員,裏面存放全部打開的窗口的AppWindowToken,首先移除,後面根據visible添加
            mOpeningApps.remove(wtoken);
              //mClosingApps是WMS的成員,裏面存放全部關閉的窗口的AppWindowToken,首先移除,後面根據visible添加
            mClosingApps.remove(wtoken);
            //表示等待着去顯示
            wtoken.waitingToShow = false;
            wtoken.hiddenRequested = !visible;
            if (!visible) {
                // If the app is dead while it was visible, we kept its dead window on screen.
                // Now that the app is going invisible, we can remove it. It will be restarted
                // if made visible again.
                wtoken.removeAllDeadWindows();
                wtoken.setVisibleBeforeClientHidden();
            } else if (visible) {
                if (!mAppTransition.isTransitionSet() && mAppTransition.isReady()) {
                    // Add the app mOpeningApps if transition is unset but ready. This means
                    // we're doing a screen freeze, and the unfreeze will wait for all opening // apps to be ready. mOpeningApps.add(wtoken); } wtoken.startingMoved = false; // If the token is currently hidden (should be the common case), or has been // stopped, then we need to set up to wait for its windows to be ready. if (wtoken.hidden || wtoken.mAppStopped) { wtoken.clearAllDrawn(); // If the app was already visible, don't reset the waitingToShow state.
                   //若是hidden的值等於false,說明Activity組件當前是不可見的。又因爲上面visible爲true,表示Activity將要被設置成可見的,
                   //所以,這時候就須要將AppWindowToken對象wtoken的成員變量waitingToShow的值設置爲trueif (wtoken.hidden) {
                        wtoken.waitingToShow = true;
                    }

                    if (wtoken.clientHidden) {
                        // In the case where we are making an app visible
                        // but holding off for a transition, we still need
                        // to tell the client to make its windows visible so
                        // they get drawn.  Otherwise, we will wait on
                        // performing the transition until all windows have
                        // been drawn, they never will be, and we are sad.
                        wtoken.clientHidden = false;
                           //通知應用程序進程將參數token所描述的Activity組件設置爲true
                        wtoken.sendAppVisibilityToClients();
                    }
                }
                wtoken.requestUpdateWallpaperIfNeeded();

                if (DEBUG_ADD_REMOVE) Slog.v(
                        TAG_WM, "No longer Stopped: " + wtoken);
                wtoken.mAppStopped = false;
            }
           //這個if分之在動畫設置完成而且屏幕不凍屏,亮屏、Display OK的狀況下才會走
            if (okToDisplay() && mAppTransition.isTransitionSet()) {
                if (wtoken.mAppAnimator.usingTransferredAnimation
                        && wtoken.mAppAnimator.animation == null) {
                    Slog.wtf(TAG_WM, "Will NOT set dummy animation on: " + wtoken
                            + ", using null transfered animation!");
                }
                if (!wtoken.mAppAnimator.usingTransferredAnimation &&
                        (!wtoken.startingDisplayed || mSkipAppTransitionAnimation)) {
                    if (DEBUG_APP_TRANSITIONS) Slog.v(
                            TAG_WM, "Setting dummy animation on: " + wtoken);
                   //設置啞動畫,能夠理解是一個站位的做用,後面會對它設置真正的動畫
                    wtoken.mAppAnimator.setDummyAnimation();
                }
                wtoken.inPendingTransaction = true;
                if (visible) {
                //可見,把wtoken加入到mOpeningApps
                    mOpeningApps.add(wtoken);
                    wtoken.mEnteringAnimation = true;
                } else {
                //不可見,把wtoken加入到mClosingApps
                    mClosingApps.add(wtoken);
                    wtoken.mEnteringAnimation = false;
                }
                if (mAppTransition.getAppTransition() == AppTransition.TRANSIT_TASK_OPEN_BEHIND) {
                    // We're launchingBehind, add the launching activity to mOpeningApps. final WindowState win = findFocusedWindowLocked(getDefaultDisplayContentLocked()); if (win != null) { final AppWindowToken focusedToken = win.mAppToken; if (focusedToken != null) { focusedToken.hidden = true; mOpeningApps.add(focusedToken); } } } return; } final long origId = Binder.clearCallingIdentity(); wtoken.inPendingTransaction = false; //將參數token所描述的Activity組件的可見性設置爲參數visible所描述的值; setTokenVisibilityLocked(wtoken, null, visible, AppTransition.TRANSIT_UNSET,true, wtoken.voiceInteraction); //向WMS服務上報參數token所描述的Activity組件的可見性 wtoken.updateReportedVisibilityLocked(); Binder.restoreCallingIdentity(origId); } }複製代碼

這個方法變量比較多,要所有弄明白,仍是要花費一些功夫的。setAppVisibility設置好以後,就能夠通知客戶端啓動APP進程了,(因此這樣看來,當一個Activity的實例還不存在的時候,它的窗口的token就已經被肯定了)接着往下走,completeResumeLocked方法主要是從上下到檢查哪些Activity組件是須要設置爲可見的,哪些Activity組件是須要設置爲不可見的,找到棧頂部第一個全屏顯示的Activity組件,調用setAppVisibility設置爲true,這個全屏顯示Activity組件下面的全部Activity組件的可見性設置爲false。
最後通知WindowManagerService服務調用executeAppTransition方法執行前面所準備的切換操做,執行這個切換操做跟Activity窗口動畫(過分動畫)有關係,如今就開始第二節內容。

#####二、過分動畫設置

過分動畫設置的設置過程
過分動畫設置的設置過程

粗略的看一共19個步驟,前面prepareAppTransition設置切換操做和sendAppVisibility方法設置哪一個Activity要隱藏,哪一個Activity的要顯示,已經解釋過了,如今從sendAppVisibilityToClient開始。sendAppVisibilityToClient/dispatchAppVibility 這兩個函數就是通知上層應用窗口可見性發生變化。若是下一個Activity是冷啓動,那麼這個函數並不能通知下一個Activity的窗口變爲可見,由於此時該函數調用時,下一個Activity的窗口還沒加到到WMS中來,Activity的窗口添加是Activity 的onResume方法中添加的。而後到第四步finishDrawingWindow下一個被Resume起來後,添加窗口、measure、layout、draw等一系列操做完成後便會調用WMS.finishDrawingWindow()來通知WMS,該窗口已經繪製好了,能夠開始作動畫了。WMS.finishDrawingWindow()會調用WindowStateAnimator.finishDrawingLocked()更新窗口狀態mDrawState爲COMMIT_DRAW_PENDING。其次WindowSurfacePlacer的requestTraversal方法,WindowSurfacePlacer的requestTraversal方法只是向WMS的主線程發送了一個DO_TRAVERSAL消息,WMS收到這個消息後,performSurfacePlacement方法就會執行。

frameworks/base/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
 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;
  }複製代碼

序列圖中performSurfacePlacement、performSurfacePlacementLoop、performSurfacePlacementInner三個方法都是跟渲染相關的。performSurfacePlacement中調用了performSurfacePlacementLoop,performSurfacePlacementLoop中調用了performSurfacePlacementInner。(todolist:梳理performSurfacePlacement方法)

第十步commitFinishDrawingLocked是applySurfaceChangesTransaction方法調用進來的,該函數將窗口狀態爲COMMIT_DRAW_PENDING或READY_TO_SHOW的窗口,所有更新到READY_TO_SHOW狀態

第十一步updateAllDrawnLocked函數更新AppWindowToken.allDrawn值。只有屬於該AppWindowToken的全部窗口都是繪製完成狀態(通常狀況下只有一個窗口,有時候會有父窗口、子窗口,這時屬於該AppWindowToken的窗口數量就不止一個了),AppWindowToken.allDrawn纔會置爲true。AppWindowToken.allDrawn爲true纔會使得第十步中的WMS.handleAppTransitionReadyLocked()完整的執行。
第十二步handleAppTransitionReadyLocked主要作了如下幾件事情。

private int handleAppTransitionReadyLocked(WindowList windows) {
        //獲取系統中全部打開的activity數量
        int appsCount = mService.mOpeningApps.size();
        //transitionGoodToGo會判斷多種case狀況下,不用執行動畫的狀況,
         //好比正在作轉屏動畫,mOpeningApps中任何一個allDrawn不等於trueif (!transitionGoodToGo(appsCount)) {
            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();
         //若是由於動畫沒有成功設置好,或者由於凍屏等緣由,致使的WMS中mSkipAppTransitionAnimation爲true的話,切換操做類型設置爲TRANSIT_UNSET
        if (mService.mSkipAppTransitionAnimation) {
            transit = AppTransition.TRANSIT_UNSET;
        }
        mService.mSkipAppTransitionAnimation = false;
        mService.mNoAnimationNotifyOnTransitionFinished.clear();
       //這個時候能夠移除prepareAppTransition中設置的超時消息
        mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
       //從新進行窗口的排序,防止亂序
        mService.rebuildAppWindowListLocked();

        mWallpaperMayChange = false;

        // The top-most window will supply the layout params,
        // and we will determine it below.
        //用來保存窗口參數
        LayoutParams animLp = null;
        int bestAnimLayer = -1;
        boolean fullscreenAnim = false;
       //是否有語音交互
        boolean voiceInteraction = false;

        int i;
        for (i = 0; i < appsCount; i++) {
            final AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
            // Clearing the mAnimatingExit flag before entering animation. It's set to // true if app window is removed, or window relayout to invisible. // This also affects window visibility. We need to clear it *before* // maybeUpdateTransitToWallpaper() as the transition selection depends on // wallpaper target visibility. wtoken.clearAnimatingFlags(); } // Adjust wallpaper before we pull the lower/upper target, since pending changes // (like the clearAnimatingFlags() above) might affect wallpaper target result. final DisplayContent displayContent = mService.getDefaultDisplayContentLocked(); if ((displayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0 && mWallpaperControllerLocked.adjustWallpaperWindows()) { //上面執行了clearAnimatingFlags,會影響Z-order.這裏從新調整一下 mService.mLayersController.assignLayersLocked(windows); displayContent.layoutNeeded = true; } //在調整壁紙窗口在窗口堆棧的位置的時候,若是碰到系統在執行兩個Activity組件的切換操做, //而且這兩個Activity組件都須要顯示壁紙, //那麼Z軸位置較低的窗口就會lowerWallpaperTarget中, //而Z軸位置較高的窗口就會保存在upperWallpaperTarget中。 final WindowState lowerWallpaperTarget = mWallpaperControllerLocked.getLowerWallpaperTarget(); final WindowState upperWallpaperTarget = mWallpaperControllerLocked.getUpperWallpaperTarget(); boolean openingAppHasWallpaper = false; boolean closingAppHasWallpaper = false; final AppWindowToken lowerWallpaperAppToken; final AppWindowToken upperWallpaperAppToken; if (lowerWallpaperTarget == null) { lowerWallpaperAppToken = upperWallpaperAppToken = null; } else { lowerWallpaperAppToken = lowerWallpaperTarget.mAppToken; upperWallpaperAppToken = upperWallpaperTarget.mAppToken; } // Do a first pass through the tokens for two // things: // (1) Determine if both the closing and opening // app token sets are wallpaper targets, in which // case special animations are needed // (since the wallpaper needs to stay static // behind them). // (2) Find the layout params of the top-most // application window in the tokens, which is // what will control the animation theme. //獲取關閉的activiy數量 final int closingAppsCount = mService.mClosingApps.size(); //獲取打開的activiy數量 appsCount = closingAppsCount + mService.mOpeningApps.size(); //這段代碼的for循環就是要從參與切換操做的Activity組件的窗口的WindowManager.LayoutParams對象中挑選出一個來建立切換動畫 //要求是主窗口,它是全部候選窗口中Z軸位置最高的 for (i = 0; i < appsCount; i++) { final AppWindowToken wtoken; if (i < closingAppsCount) { wtoken = mService.mClosingApps.valueAt(i); if (wtoken == lowerWallpaperAppToken || wtoken == upperWallpaperAppToken) { //Activity關閉的時候,要顯示牆紙窗口 closingAppHasWallpaper = true; } } else { wtoken = mService.mOpeningApps.valueAt(i - closingAppsCount); if (wtoken == lowerWallpaperAppToken || wtoken == upperWallpaperAppToken) { //Activity打開的時候,要顯示牆紙窗口 openingAppHasWallpaper = true; } } voiceInteraction |= wtoken.voiceInteraction; //是不是全屏 if (wtoken.appFullscreen) { //找到主窗口,類型爲TYPE_BASE_APPLICATION或者TYPE_APPLICATION_STARTING類型的 WindowState ws = wtoken.findMainWindow(); if (ws != null) { animLp = ws.mAttrs; bestAnimLayer = ws.mLayer; fullscreenAnim = true; } } else if (!fullscreenAnim) { WindowState ws = wtoken.findMainWindow(); if (ws != null) { if (ws.mLayer > bestAnimLayer) { animLp = ws.mAttrs; bestAnimLayer = ws.mLayer; } } } } //判斷切換操做跟牆紙類型是否相關,調整窗口的類型(mayUpdateTransitionToWallpaper) transit = maybeUpdateTransitToWallpaper(transit, openingAppHasWallpaper, closingAppHasWallpaper, lowerWallpaperTarget, upperWallpaperTarget); // If all closing windows are obscured, then there is // no need to do an animation. This is the case, for // example, when this transition is being done behind // the lock screen. if (!mService.mPolicy.allowAppAnimationsLw()) { if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Animations disallowed by keyguard or dream."); animLp = null; } processApplicationsAnimatingInPlace(transit); mTmpLayerAndToken.token = null; // MIUI ADD: mService.mAppTransition.updateAllowCustomAnimationIfNeeded(mService.mClosingApps); //處理關閉的Activity handleClosingApps(transit, animLp, voiceInteraction, mTmpLayerAndToken); final AppWindowToken topClosingApp = mTmpLayerAndToken.token; final int topClosingLayer = mTmpLayerAndToken.layer; //處理打開的Activity,下面會說 final AppWindowToken topOpeningApp = handleOpeningApps(transit, animLp, voiceInteraction, topClosingLayer); mService.mAppTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp); final AppWindowAnimator openingAppAnimator = (topOpeningApp == null) ? null : topOpeningApp.mAppAnimator; final AppWindowAnimator closingAppAnimator = (topClosingApp == null) ? null : topClosingApp.mAppAnimator; 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; }複製代碼

整體來講大體有八個步驟

  • 一、調用條件:首先判斷是否超時,超時了不執行,判斷mOpeningApps中每個AppWindowToken的allDrawn值是否爲true
  • 二、判斷牆紙是否須要可見,若是須要,先繪製牆紙,在走切換動畫邏輯
  • 三、取出mAppTransition的切換操做,移除超時消息
  • 四、窗口堆棧順序重排,rebuildAppWindowListLocked
  • 五、取得頂層全屏窗口的mAttr值(LayoutParams),記錄在animLp
  • 六、判斷切換操做跟牆紙類型是否相關,調整類型(mayUpdateTransitionToWallpaper)
  • 七、分別處理handleClosingApps/handleOpeningApps
  • 八、清理工做

第十三步handleOpeningApps這個函數用來設置APPWindowToken.hidden的可見性、設置Activity切換動畫(若是參數transit==AppTransition.TRANSIT_UNSET,那就是會設置窗口動畫,不然就會設置Activity切換動畫),若是存在Activity切換動畫或屬於該Activity的窗口正在作窗口動畫,那麼返回值爲true,handleOpeningApps中調用了setTokenVisibilityLocked方法。

/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
 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();
        }
        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;
                }
                WindowState window = wtoken.findMainWindow();
                if (window != null && mAccessibilityController != null
                        && window.getDisplayId() == Display.DEFAULT_DISPLAY) {
                    mAccessibilityController.onAppWindowTransitionLocked(window, transit);
                }
                changed = true;
            }
         .......
      }
      .......
        return delayed;
    }複製代碼

建立動畫是applyAnimationLocked方法乾的事情。

frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
 private boolean applyAnimationLocked(AppWindowToken atoken, WindowManager.LayoutParams lp,
            int transit, boolean enter, boolean isVoiceInteraction) {
        // 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, "WM#applyAnimationLocked"); if (okToDisplay()) { .... //傳入各類參數,用AppTransition的loadAnimation建立一個動畫 Animation a = mAppTransition.loadAnimation(lp, transit, enter, mCurConfiguration.uiMode, mCurConfiguration.orientation, frame, displayFrame, insets, surfaceInsets, isVoiceInteraction, freeform, atoken.mTask.mTaskId, mIsInMultiWindowMode); .... if (a != null) { .... atoken.mAppAnimator.setAnimation(a, containingWidth, containingHeight, mAppTransition.canSkipFirstFrame(), mAppTransition.getAppStackClipMode()); .... } } else { atoken.mAppAnimator.clearAnimation(); } return atoken.mAppAnimator.animation != null; }複製代碼

applyAnimationLocked內部實質上仍是調用loadAnimation。

frameworks/base/services/core/java/com/android/server/wm/AppTransition.java
 Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, int uiMode,
            int orientation, Rect frame, Rect displayFrame, Rect insets,
            @Nullable Rect surfaceInsets, boolean isVoiceInteraction, boolean freeform,
            int taskId, boolean isInMultiWindowMode) {

        if (transit == TRANSIT_WALLPAPER_OPEN && mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM
                && !mAllowCustomAnimation) {
            mNextAppTransitionType = AppTransitionInjector.NEXT_TRANSIT_TYPE_BACK_HOME;
        }
        Animation a;
        if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_OPEN
                || transit == TRANSIT_TASK_OPEN
                || transit == TRANSIT_TASK_TO_FRONT)) {
            a = loadAnimationRes(lp, enter
                    ? com.android.internal.R.anim.voice_activity_open_enter
                    : com.android.internal.R.anim.voice_activity_open_exit);
            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                    "applyAnimation voice:"
                    + " anim=" + a + " transit=" + appTransitionToString(transit)
                    + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3));
        } else if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_CLOSE
                || transit == TRANSIT_TASK_CLOSE
                || transit == TRANSIT_TASK_TO_BACK)) {
            a = loadAnimationRes(lp, enter
                    ? com.android.internal.R.anim.voice_activity_close_enter
                    : com.android.internal.R.anim.voice_activity_close_exit);
            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                    "applyAnimation voice:"
                    + " anim=" + a + " transit=" + appTransitionToString(transit)
                    + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3));
        } else if (transit == TRANSIT_ACTIVITY_RELAUNCH) {
            a = createRelaunchAnimation(frame, insets);
            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                    "applyAnimation:"
                    + " anim=" + a + " nextAppTransition=" + mNextAppTransition
                    + " transit=" + appTransitionToString(transit)
                    + " Callers=" + Debug.getCallers(3));
        } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
            a = loadAnimationRes(mNextAppTransitionPackage, enter ?
                    mNextAppTransitionEnter : mNextAppTransitionExit);
            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                    "applyAnimation:"
                    + " anim=" + a + " nextAppTransition=ANIM_CUSTOM"
                    + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
                    + " Callers=" + Debug.getCallers(3));
        } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE) {
            a = loadAnimationRes(mNextAppTransitionPackage, mNextAppTransitionInPlace);
            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                    "applyAnimation:"
                    + " anim=" + a + " nextAppTransition=ANIM_CUSTOM_IN_PLACE"
                    + " transit=" + appTransitionToString(transit)
                    + " Callers=" + Debug.getCallers(3));
        } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) {
            a = createClipRevealAnimationLocked(transit, enter, frame, displayFrame);
            mLauncherAnimationRect.setEmpty();
            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                    "applyAnimation:"
                            + " anim=" + a + " nextAppTransition=ANIM_CLIP_REVEAL"
                            + " transit=" + appTransitionToString(transit)
                            + " Callers=" + Debug.getCallers(3));
        } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) {
            a = createScaleUpAnimationLocked(transit, enter, frame);
            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                    "applyAnimation:"
                    + " anim=" + a + " nextAppTransition=ANIM_SCALE_UP"
                    + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
                    + " Callers=" + Debug.getCallers(3));
        } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP ||
                mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN) {
            mNextAppTransitionScaleUp =
                    (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
            a = createThumbnailEnterExitAnimationLocked(getThumbnailTransitionState(enter),
                    frame, transit, taskId);
            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
                String animName = mNextAppTransitionScaleUp ?
                        "ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN";
                Slog.v(TAG, "applyAnimation:"
                        + " anim=" + a + " nextAppTransition=" + animName
                        + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
                        + " Callers=" + Debug.getCallers(3));
            }
        } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP ||
                mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN) {
            mNextAppTransitionScaleUp =
                    (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP);
            a = createAspectScaledThumbnailEnterExitAnimationLocked(
                    getThumbnailTransitionState(enter), uiMode, orientation, transit, frame,
                    insets, surfaceInsets, freeform, taskId);
            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
                String animName = mNextAppTransitionScaleUp ?
                        "ANIM_THUMBNAIL_ASPECT_SCALE_UP" : "ANIM_THUMBNAIL_ASPECT_SCALE_DOWN";
                Slog.v(TAG, "applyAnimation:"
                        + " anim=" + a + " nextAppTransition=" + animName
                        + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
                        + " Callers=" + Debug.getCallers(3));
            }
        } 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;
                case TRANSIT_TASK_CLOSE:
                    animAttr = enter
                            ? WindowAnimation_taskCloseEnterAnimation
                            : WindowAnimation_taskCloseExitAnimation;
                    break;
                case TRANSIT_TASK_TO_FRONT:
                    animAttr = enter
                            ? WindowAnimation_taskToFrontEnterAnimation
                            : WindowAnimation_taskToFrontExitAnimation;
                    break;
                case TRANSIT_TASK_TO_BACK:
                    animAttr = enter
                            ? WindowAnimation_taskToBackEnterAnimation
                            : WindowAnimation_taskToBackExitAnimation;
                    break;
                case TRANSIT_WALLPAPER_OPEN:
                    animAttr = enter
                            ? WindowAnimation_wallpaperOpenEnterAnimation
                            : WindowAnimation_wallpaperOpenExitAnimation;
                    break;
                case TRANSIT_WALLPAPER_CLOSE:
                    animAttr = enter
                            ? WindowAnimation_wallpaperCloseEnterAnimation
                            : WindowAnimation_wallpaperCloseExitAnimation;
                    break;
                case TRANSIT_WALLPAPER_INTRA_OPEN:
                    animAttr = enter
                            ? WindowAnimation_wallpaperIntraOpenEnterAnimation
                            : WindowAnimation_wallpaperIntraOpenExitAnimation;
                    break;
                case TRANSIT_WALLPAPER_INTRA_CLOSE:
                    animAttr = enter
                            ? WindowAnimation_wallpaperIntraCloseEnterAnimation
                            : WindowAnimation_wallpaperIntraCloseExitAnimation;
                    break;
                case TRANSIT_TASK_OPEN_BEHIND:
                    animAttr = enter
                            ? WindowAnimation_launchTaskBehindSourceAnimation
                            : WindowAnimation_launchTaskBehindTargetAnimation;
            }
            a = animAttr != 0 ? loadAnimationAttr(lp, animAttr) : null;
            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                    "applyAnimation:"
                    + " anim=" + a
                    + " animAttr=0x" + Integer.toHexString(animAttr)
                    + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
                    + " Callers=" + Debug.getCallers(3));
        }
        return a;
    }複製代碼

關於loadAnimation這個方法代碼也是不少,可是邏輯很是簡單了,就是根據設置的操做類型, 根據參數,使用loadAnimationRes()或loadAnimationAttr()或其餘建立Animation接口來加載一個Animation出來。加載動畫的時候須要注意一個優先級的問題。

  • 若是開啓語音交互,則根據mNextAppTransition類型返回對應動畫,由AMS設置的動畫類型(由Activity發生的行爲決定)如TRANSIT_ACTIVITY_OPEN表示當前發生了Activity打開的行爲
  • 若是設置了某種mNextAppTransitionType類型,則根據此類型返回對應動畫。:由客戶端進程設置的動畫類型(由客戶端決定) 如Activity#overridePendingTransition,在決定要爲當前窗口設置何種動畫時,此類型的優先級高於第一種。
  • 根據mNextAppTransition類型返回對應動畫。
  • 沒有任何條件知足,返回空

好比Activity關閉的時候,加載的動畫資源是下面這樣

<set xmlns:android="http://schemas.android.com/apk/res/android" android:zAdjustment="normal">
    <alpha android:fromAlpha="0.7" android:toAlpha="1.0"
            android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
            android:interpolator="@interpolator/linear_out_slow_in"
            android:duration="250"/>
</set>複製代碼

動畫設置好了以後,就會經過setAnimation方法將動畫anim保存在AppWindowAnimator的成員變量animation中,動畫的執行時候,就會來取這個animation。

frameworks/base/services/core/java/com/android/server/wm/AppWindowAnimator.java
public void setAnimation(Animation anim, int width, int height, boolean skipFirstFrame,
            int stackClip) {
        animation = anim;
        animating = false;
        if (!anim.isInitialized()) {
            anim.initialize(width, height, width, height);
        }
      ....
    }複製代碼

剛剛在說動畫的優先級的時候,說過若是設置了某種mNextAppTransitionType類型,就跟以這個mNextAppTransitionType類型做爲返回,優先級高於第一種。這個過程的時序圖以下。

過分動畫
過分動畫

frameworks/base/services/core/java/com/android/server/wm/AppTransition.java

  void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim,
            IRemoteCallback startedCallback) {
        if (isTransitionSet()) {
            clear();
            mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM;
            mNextAppTransitionPackage = packageName;
            mNextAppTransitionEnter = enterAnim;
            mNextAppTransitionExit = exitAnim;
            postAnimationCallback();
            mNextAppTransitionCallback = startedCallback;
        } else {
            postAnimationCallback();
        }
    }複製代碼

mNextAppTransitionType被覆蓋了以後,建立動畫的時候就會優先返回設置了這種類型的動畫。

#####三、窗口動畫設置

image.png
image.png

相對與過分動畫,窗口動畫的設置過程會簡單一些,從commitFinishDrawingLocked方法提及,commitFinishDrawingLocked是也是從performSurfacePlacementInner裏面調用而來的。

boolean commitFinishDrawingLocked() {
         .....
        mDrawState = READY_TO_SHOW;
        boolean result = false;
        final AppWindowToken atoken = mWin.mAppToken;
        //去取出atoken,若是atoken等於null,那麼說明不是Activity窗口,就能夠調用performShowLocked,進行窗口動畫的設置
        if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
            result = performShowLocked();
        }
        return result;
    }複製代碼

performShowLocked中主要是調用了applyEnterAnimationLocked方法進行建立動畫。

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); ..... }複製代碼

mEnterAnimationPending的值等於true,說明當前所描述的窗口正在等待顯示,也就是正處於不可見到可見狀態的過程當中,那WindowManagerService類的成員函數applyEnterAnimationLocked就會對該窗口設置一個類型爲WindowManagerPolicy.TRANSIT_ENTER的動畫,不然的話,就會對該窗口設置一個類型爲WindowManagerPolicy.TRANSIT_SHOW的動畫。後面會根據這個類型,肯定styleable, 將參數transit的值轉化爲一個對應的動畫樣式名稱。

frameworks/base/services/core/java/com/android/server/wm/WindowStateAnimator.java
boolean applyAnimationLocked(int transit, boolean isEntrance) {
        ......
        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;
    }複製代碼

首先會調用PhoneWindowManager的selectAnimationLw方法去查找特殊窗口的動畫類型,這裏特殊窗口主要是StatusBar、NavigationBar或者窗口的類型是TYPE_DOCK_DIVIDER(分屏)等,若是是這些窗口的話,就會直接返回一個動畫類型(transit)保存在anim中,接下來會判斷anim是否爲-1,由於selectAnimationLw在窗口是Keyguard或者DREAM類型的時候會返回-1,若是不是-1,說明查找到了,返回到WindowStateAnimator中使用AnimationUtils的loadAnimation方法去查找出一個動畫a保存在Animation所描述的變量a中。

若是上面都不是,那麼就根據transit類型,肯定attr,調用AppTransition的loadAnimationAttr方法加載一個動畫

frameworks/base/services/core/java/com/android/server/wm/AppTransition.java
    Animation loadAnimationAttr(WindowManager.LayoutParams lp, int animAttr) {
        int anim = 0;
        Context context = mContext;
        if (animAttr >= 0) {
            AttributeCache.Entry ent = getCachedAnimations(lp);
            if (ent != null) {
                context = ent.context;
                anim = ent.array.getResourceId(animAttr, 0);
            }
        }
        if (anim != 0) {
            return AnimationUtils.loadAnimation(context, anim);
        }
        return null;
    }複製代碼
frameworks/base/services/core/java/com/android/server/wm/WindowStateAnimator.java
  public void setAnimation(Animation anim, long startTime, int stackClip) {
        if (localLOGV) Slog.v(TAG, "Setting animation in " + this + ": " + anim);
        mAnimating = false;
        mLocalAnimating = false;
        mAnimation = anim;
        ...
    }複製代碼

最後動畫被保存在WindowStateAnimator的成員變量mAnimation中。對比前面的過分動畫,最後是經過setAnimation方法將動畫anim保存在AppWindowAnimator的成員變量animation中。當動畫的執行時候,就會來取這個animation,動畫的執行,放在接下來一節中更新。

相關文章
相關標籤/搜索