解锁的framework流程与解锁后的加载动画
前面的文章说过,AppTransition代表了activity组件的切换过程,其实解锁也是一种AppTrasition,只不过需要隐藏的activity组件是锁屏窗口罢了(严格来说并不是锁屏窗口被隐藏了,它只是调整了窗口的大小及相关参数,最后变成状态栏),解锁过程的窗口变化主要是以下几点
- 改变锁屏窗口状态
- 显示锁屏下方的窗口
- 播放解锁动画
1.解锁过程的appTransition
解锁过程的核心实质上是锁屏启动了一个runnable,通知AMS和WMS显示锁屏下方的activity组件窗口以及调用该activity组件的生命周期,向AMS和WMS发送命令的时候会传递一些flag,这些flag和解锁的场景有关,一般来说我们只用关注第一个,即WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS,例如熄屏的时候使用指纹解锁亮屏,这期间不需要显示解锁动画,于是就需要传递这个消息给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的具体实现
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(); // 执行apptransition
// Some stack visibility might change (e.g. docked stack)
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); //apptransition解锁后调整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发送消息,WMS接收到消息后就会把flag传递给负责管理窗口动画的WindowAnimator对象mAnimator,同时刷新系统UI
2.解锁过程的窗口动画
AppTransition的过程前面的文章有说,这里主要关注解锁过程的窗口动画
2.1activity组件切换动画
设置activity组件的切换动画会调到如下函数,其中,wtoken.hidden表示activity组件是否应该处于hidden状态,然而对于解锁后需要打开的activity组件来说,hidden = false,而visibility = true,所以实际上解锁过程是不会设置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普通窗口动画
当不能设置transition动画时就会设置普通的窗口动画,然而解锁后的窗口进入动画和普通的窗口动画不太一样,那么一定是在哪里完成了动画的替换
前面有提到,WMS在接受到flag之后会把参数进一步传递给WindowAnimtor对象,这个WindowAnimator是WMS服务中管理窗口动画播放的类,主要函数是updateWindowsLocked(),这个函数比较长,我们分段分析一下
第一步,获取当前的窗口列表并根据传过来的flag计算参数,拿到的窗口列表是按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;
第二步,在窗口动画播放开始前遍历窗口列表找到需要需要替换解锁动画的窗口,这里使用了一个常量SET_FORCE_HIDING_CHANGED,通过位运算的方式区分锁屏以及锁屏上下方的窗口,需要替换的窗口放在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);
}
}
}
当然,里面最主要的还是调用PhoneWindowManager的createForceHideEnterAnimation函数来拿到解锁动画对象
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)和一般应用的窗口动画是不一样的,动画资源定义在frameworks/base