前面的文章說過,AppTransition代表了activity組件的切換過程跃洛,其實(shí)解鎖也是一種AppTrasition,只不過需要隱藏的activity組件是鎖屏窗口罷了(嚴(yán)格來說并不是鎖屏窗口被隱藏了玛追,它只是調(diào)整了窗口的大小及相關(guān)參數(shù)税课,最后變成狀態(tài)欄),解鎖過程的窗口變化主要是以下幾點(diǎn)
- 改變鎖屏窗口狀態(tài)
- 顯示鎖屏下方的窗口
- 播放解鎖動畫
1.解鎖過程的appTransition
解鎖過程的核心實(shí)質(zhì)上是鎖屏啟動了一個(gè)runnable痊剖,通知AMS和WMS顯示鎖屏下方的activity組件窗口以及調(diào)用該activity組件的生命周期韩玩,向AMS和WMS發(fā)送命令的時(shí)候會傳遞一些flag,這些flag和解鎖的場景有關(guān)陆馁,一般來說我們只用關(guān)注第一個(gè)找颓,即WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS,例如熄屏的時(shí)候使用指紋解鎖亮屏叮贩,這期間不需要顯示解鎖動畫击狮,于是就需要傳遞這個(gè)消息給WMS
private final Runnable mKeyguardGoingAwayRunnable = new Runnable() {
@Override
public void run() {
try {
mStatusBarKeyguardViewManager.keyguardGoingAway();
int flags = 0;
if (mStatusBarKeyguardViewManager.shouldDisableWindowAnimationsForUnlock()
|| mWakeAndUnlocking) {
flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
}
if (mStatusBarKeyguardViewManager.isGoingToNotificationShade()) {
flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
}
if (mStatusBarKeyguardViewManager.isUnlockWithWallpaper()) {
flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
}
ActivityManagerNative.getDefault().keyguardGoingAway(flags);
} catch (RemoteException e) {
Log.e(TAG, "Error while calling WindowManager", e);
}
}
};
接著看看AMS以及WMS的具體實(shí)現(xiàn)
AMS
public void keyguardGoingAway(int flags) {
enforceNotIsolatedCaller("keyguardGoingAway");
final long token = Binder.clearCallingIdentity();
try {
synchronized (this) {
if (DEBUG_LOCKSCREEN) logLockScreen("");
mWindowManager.keyguardGoingAway(flags);
if (mLockScreenShown == LOCK_SCREEN_SHOWN) {
mLockScreenShown = LOCK_SCREEN_HIDDEN;
updateSleepIfNeededLocked(); // 執(zhí)行apptransition
// Some stack visibility might change (e.g. docked stack)
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); //apptransition解鎖后調(diào)整activitystack
applyVrModeIfNeededLocked(mFocusedActivity, true);
}
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
WMS
public void keyguardGoingAway(int flags) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires DISABLE_KEYGUARD permission");
}
if (DEBUG_KEYGUARD) Slog.d(TAG_WM,
"keyguardGoingAway: flags=0x" + Integer.toHexString(flags));
synchronized (mWindowMap) {
mAnimator.mKeyguardGoingAway = true;
mAnimator.mKeyguardGoingAwayFlags = flags;
mWindowPlacerLocked.requestTraversal();
}
}
可以看到AMS的主要作用是啟動apptransition和向WMS發(fā)送消息,WMS接收到消息后就會把flag傳遞給負(fù)責(zé)管理窗口動畫的WindowAnimator對象mAnimator益老,同時(shí)刷新系統(tǒng)UI
2.解鎖過程的窗口動畫
AppTransition的過程前面的文章有說彪蓬,這里主要關(guān)注解鎖過程的窗口動畫
2.1activity組件切換動畫
設(shè)置activity組件的切換動畫會調(diào)到如下函數(shù),其中,wtoken.hidden表示activity組件是否應(yīng)該處于hidden狀態(tài)捺萌,然而對于解鎖后需要打開的activity組件來說档冬,hidden = false,而visibility = true,所以實(shí)際上解鎖過程是不會設(shè)置transition動畫
boolean setTokenVisibilityLocked(AppWindowToken wtoken, WindowManager.LayoutParams lp,
boolean visible, int transit, boolean performLayout, boolean isVoiceInteraction) {
boolean delayed = false;
...
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;
}
...
}
...
}
2.2普通窗口動畫
當(dāng)不能設(shè)置transition動畫時(shí)就會設(shè)置普通的窗口動畫酷誓,然而解鎖后的窗口進(jìn)入動畫和普通的窗口動畫不太一樣披坏,那么一定是在哪里完成了動畫的替換
前面有提到,WMS在接受到flag之后會把參數(shù)進(jìn)一步傳遞給WindowAnimtor對象盐数,這個(gè)WindowAnimator是WMS服務(wù)中管理窗口動畫播放的類棒拂,主要函數(shù)是updateWindowsLocked(),這個(gè)函數(shù)比較長玫氢,我們分段分析一下
第一步帚屉,獲取當(dāng)前的窗口列表并根據(jù)傳過來的flag計(jì)算參數(shù),拿到的窗口列表是按Z軸排序過的琐旁,鎖屏窗口在上方涮阔,其余窗口在下方
final WindowList windows = mService.getWindowListLocked(displayId);
final boolean keyguardGoingAwayToShade =
(mKeyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_TO_SHADE) != 0;
final boolean keyguardGoingAwayNoAnimation =
(mKeyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS) != 0;
final boolean keyguardGoingAwayWithWallpaper =
(mKeyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER) != 0;
第二步猜绣,在窗口動畫播放開始前遍歷窗口列表找到需要需要替換解鎖動畫的窗口,這里使用了一個(gè)常量SET_FORCE_HIDING_CHANGED灰殴,通過位運(yùn)算的方式區(qū)分鎖屏以及鎖屏上下方的窗口,需要替換的窗口放在unForceHiding里面掰邢,通常大小為1
for (int i = windows.size() - 1; i >= 0; i--) {
WindowState win = windows.get(i);
WindowStateAnimator winAnimator = win.mWinAnimator;
final int flags = win.mAttrs.flags;
boolean canBeForceHidden = mPolicy.canBeForceHidden(win, win.mAttrs);
boolean shouldBeForceHidden = shouldForceHide(win);
if (winAnimator.hasSurface()) {
final boolean wasAnimating = winAnimator.mWasAnimating;
final boolean nowAnimating = winAnimator.stepAnimationLocked(mCurrentTime);
winAnimator.mWasAnimating = nowAnimating;
orAnimating(nowAnimating);
if (wasAnimating && !winAnimator.mAnimating
&& wallpaperController.isWallpaperTarget(win)) {
mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
setPendingLayoutChanges(Display.DEFAULT_DISPLAY,
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
}
if (mPolicy.isForceHiding(win.mAttrs)) {
if (!wasAnimating && nowAnimating) {// 窗口還沒有開始播放
mBulkUpdateParams |= SET_FORCE_HIDING_CHANGED;
setPendingLayoutChanges(displayId,
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
mService.mFocusMayChange = true;
} else if (mKeyguardGoingAway && !nowAnimating) {
// Timeout!!
Slog.e(TAG, "Timeout waiting for animation to startup");
mPolicy.startKeyguardExitAnimation(0, 0);
mKeyguardGoingAway = false;
}
if (win.isReadyForDisplay()) {
if (nowAnimating && win.mWinAnimator.mKeyguardGoingAwayAnimation) {
mForceHiding = KEYGUARD_ANIMATING_OUT;
} else {
mForceHiding = win.isDrawnLw() ? KEYGUARD_SHOWN : KEYGUARD_NOT_SHOWN;
}
}
} else if (canBeForceHidden) {
if (shouldBeForceHidden) {
if (!win.hideLw(false, false)) {
continue;
}
} else {
boolean applyExistingExitAnimation = mPostKeyguardExitAnimation != null
&& !mPostKeyguardExitAnimation.hasEnded()
&& !winAnimator.mKeyguardGoingAwayAnimation
&& win.hasDrawnLw()
&& win.mAttachedWindow == null
&& !win.mIsImWindow
&& displayId == Display.DEFAULT_DISPLAY;
// If the window is already showing and we don't need to apply an existing
// Keyguard exit animation, skip.
if (!win.showLw(false, false) && !applyExistingExitAnimation) {
continue;
}
final boolean visibleNow = win.isVisibleNow();
if (!visibleNow) {
// Couldn't really show, must showLw() again when win becomes visible.
win.hideLw(false, false);
continue;
}
if ((mBulkUpdateParams & SET_FORCE_HIDING_CHANGED) != 0
&& win.mAttachedWindow == null) {
if (unForceHiding == null) {
unForceHiding = new ArrayList<>();
}
unForceHiding.add(winAnimator);
if ((flags & FLAG_SHOW_WALLPAPER) != 0) {
wallpaperInUnForceHiding = true;
}
if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
startingInUnForceHiding = true;
}
} else if (applyExistingExitAnimation) {
// We're already in the middle of an animation. Use the existing
// animation to bring in this window.
if (DEBUG_KEYGUARD) Slog.v(TAG,
"Applying existing Keyguard exit animation to new window: win="
+ win);
Animation a = mPolicy.createForceHideEnterAnimation(false,
keyguardGoingAwayToShade);
winAnimator.setAnimation(a, mPostKeyguardExitAnimation.getStartTime(),
STACK_CLIP_BEFORE_ANIM);
winAnimator.mKeyguardGoingAwayAnimation = true;
winAnimator.mKeyguardGoingAwayWithWallpaper
= keyguardGoingAwayWithWallpaper;
}
final WindowState currentFocus = mService.mCurrentFocus;
if (currentFocus == null || currentFocus.mLayer < win.mLayer) {
// We are showing on top of the current
// focus, so re-evaluate focus to make
// sure it is correct.
mService.mFocusMayChange = true;
}
}
if ((flags & FLAG_SHOW_WALLPAPER) != 0) {
mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
setPendingLayoutChanges(Display.DEFAULT_DISPLAY,
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
if (DEBUG_LAYOUT_REPEATS) {
mWindowPlacerLocked.debugLayoutRepeats(
"updateWindowsAndWallpaperLocked 4",
getPendingLayoutChanges(Display.DEFAULT_DISPLAY));
}
}
}
}
// If the window doesn't have a surface, the only thing we care about is the correct
// policy visibility.
else if (canBeForceHidden) {
if (shouldBeForceHidden) {
win.hideLw(false, false);
} else {
win.showLw(false, false);
}
}
final AppWindowToken atoken = win.mAppToken;
if (winAnimator.mDrawState == READY_TO_SHOW) {
if (atoken == null || atoken.allDrawn) {
if (winAnimator.performShowLocked()) {
setPendingLayoutChanges(displayId,
WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM);
if (DEBUG_LAYOUT_REPEATS) {
mWindowPlacerLocked.debugLayoutRepeats(
"updateWindowsAndWallpaperLocked 5",
getPendingLayoutChanges(displayId));
}
}
}
}
final AppWindowAnimator appAnimator = winAnimator.mAppAnimator;
if (appAnimator != null && appAnimator.thumbnail != null) {
if (appAnimator.thumbnailTransactionSeq != mAnimTransactionSequence) {
appAnimator.thumbnailTransactionSeq = mAnimTransactionSequence;
appAnimator.thumbnailLayer = 0;
}
if (appAnimator.thumbnailLayer < winAnimator.mAnimLayer) {
appAnimator.thumbnailLayer = winAnimator.mAnimLayer;
}
}
if (win.mIsWallpaper) {
wallpaper = win;
}
}
第三步牺陶,替換動畫,遍歷unForceHiding辣之,用解鎖動畫替換掉原來的窗口動畫
if (unForceHiding != null) {
if (!keyguardGoingAwayNoAnimation) {
boolean first = true;
for (int i=unForceHiding.size()-1; i>=0; i--) {
final WindowStateAnimator winAnimator = unForceHiding.get(i);
Animation a = mPolicy.createForceHideEnterAnimation(
wallpaperInUnForceHiding && !startingInUnForceHiding,
keyguardGoingAwayToShade);
if (a != null) {
if (DEBUG_KEYGUARD) Slog.v(TAG,
"Starting keyguard exit animation on window " + winAnimator.mWin);
winAnimator.setAnimation(a, STACK_CLIP_BEFORE_ANIM);
winAnimator.mKeyguardGoingAwayAnimation = true;
winAnimator.mKeyguardGoingAwayWithWallpaper
= keyguardGoingAwayWithWallpaper;
if (first) {
mPostKeyguardExitAnimation = a;
mPostKeyguardExitAnimation.setStartTime(mCurrentTime);
first = false;
}
}
}
} else if (mKeyguardGoingAway) {
mPolicy.startKeyguardExitAnimation(mCurrentTime, 0 /* duration */);
mKeyguardGoingAway = false;
}
// Wallpaper is going away in un-force-hide motion, animate it as well.
if (!wallpaperInUnForceHiding && wallpaper != null && !keyguardGoingAwayNoAnimation) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "updateWindowsLocked: wallpaper animating away");
Animation a = mPolicy.createForceHideWallpaperExitAnimation(
keyguardGoingAwayToShade);
if (a != null) {
wallpaper.mWinAnimator.setAnimation(a);
}
}
}
當(dāng)然掰伸,里面最主要的還是調(diào)用PhoneWindowManager的createForceHideEnterAnimation函數(shù)來拿到解鎖動畫對象
public Animation createForceHideEnterAnimation(boolean onWallpaper,
boolean goingToNotificationShade) {
if (goingToNotificationShade) {
return AnimationUtils.loadAnimation(mContext, R.anim.lock_screen_behind_enter_fade_in);
}
AnimationSet set = (AnimationSet) AnimationUtils.loadAnimation(mContext, onWallpaper ?
R.anim.lock_screen_behind_enter_wallpaper :
R.anim.lock_screen_behind_enter);
// TODO: Use XML interpolators when we have log interpolators available in XML.
final List<Animation> animations = set.getAnimations();
for (int i = animations.size() - 1; i >= 0; --i) {
animations.get(i).setInterpolator(mLogDecelerateInterpolator);
}
return set;
}
對于透明的窗口(launcher)和一般應(yīng)用的窗口動畫是不一樣的,動畫資源定義在frameworks/base