Android視窗系統第四篇—Activity動畫的設定過程

Looperjing發表於2019-03-01

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

####一、動畫型別
在Apptransition.java中定義了很多動畫的型別,每個型別以一個int值來表示。

動畫型別 含義
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,設定不同的型別,後面根據這個設定好的型別,載入不同的動畫。

####二、動畫設定
瞭解了動畫型別了,我們看一下Activity切換的時候,動畫是怎麼設定的,先簡單看一下Activity的切換。

#####1、Activiy切換

Activiy切換
Activiy切換

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

切換的步驟

  • 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,因為開啟的動畫要比關閉的動畫優先順序要高。

如果transit == TRANSIT_ACTIVITY_OPEN 並且isTransitionEqual(TRANSIT_ACTIVITY_CLOSE)返回true,表示上次(之前)給Activity設定的切換操作是TRANSIT_ACTIVITY_CLOSE,那麼可以呼叫setAppTransition,因為開啟的動畫要比關閉的動畫優先順序要高。

   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視窗動畫(過度動畫)有關係,現在就開始第二節內容。

#####2、過度動畫設定

過度動畫設定的設定過程
過度動畫設定的設定過程

粗略的看一共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;
    }複製程式碼

總體來說大致有八個步驟

  • 1、呼叫條件:首先判斷是否超時,超時了不執行,判斷mOpeningApps中每一個AppWindowToken的allDrawn值是否為true
  • 2、判斷牆紙是否需要可見,如果需要,先繪製牆紙,在走切換動畫邏輯
  • 3、取出mAppTransition的切換操作,移除超時訊息
  • 4、視窗堆疊順序重排,rebuildAppWindowListLocked
  • 5、取得頂層全屏視窗的mAttr值(LayoutParams),記錄在animLp
  • 6、判斷切換操作跟牆紙型別是否相關,調整型別(mayUpdateTransitionToWallpaper)
  • 7、分別處理handleClosingApps/handleOpeningApps
  • 8、清理工作

第十三步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被覆蓋了之後,建立動畫的時候就會優先返回設定了這種型別的動畫。

#####3、視窗動畫設定

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,動畫的執行,放在接下來一節中更新。

相關文章