無論是系統中視窗的動畫,還是應用中某一個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切換
什麼是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的值設定為true。
if (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不等於true等
if (!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、視窗動畫設定
相對與過度動畫,視窗動畫的設定過程會簡單一些,從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,動畫的執行,放在接下來一節中更新。