本文介绍安卓动画系统中的转屏动画流程。
1 旋转的开始
若当前屏幕方向需要旋转,会调用frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java的updateRotation方法:
/**
* Recalculate the current rotation.
*
* Called by the window manager policy whenever the state of the system changes
* such that the current rotation might need to be updated, such as when the
* device is docked or rotated into a new posture.
*/
@Override
public void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout) {
updateRotationUnchecked(alwaysSendConfiguration, forceRelayout);
}
private void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
ProtoLog.v(WM_DEBUG_ORIENTATION, "updateRotationUnchecked:"
+ " alwaysSendConfiguration=%b forceRelayout=%b",
alwaysSendConfiguration, forceRelayout);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation");
long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
boolean layoutNeeded = false;
final int displayCount = mRoot.mChildren.size();
for (int i = 0; i < displayCount; ++i) {
final DisplayContent displayContent = mRoot.mChildren.get(i);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation: display");
final boolean rotationChanged = displayContent.updateRotationUnchecked();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (rotationChanged) {
mAtmService.getTaskChangeNotificationController()
.notifyOnActivityRotation(displayContent.mDisplayId);
}
if (!rotationChanged || forceRelayout) {
displayContent.setLayoutNeeded();
layoutNeeded = true;
}
if (rotationChanged || alwaysSendConfiguration) {
displayContent.sendNewConfiguration();
}
}
if (layoutNeeded) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"updateRotation: performSurfacePlacement");
mWindowPlacerLocked.performSurfacePlacement();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
} finally {
Binder.restoreCallingIdentity(origId);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
遍历在mRoot.mChildren中的每个DisplayContent(frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java),首先调用了其updateRotationUnchecked方法:
/**
* Update rotation of the display.
*
* @return {@code true} if the rotation has been changed. In this case YOU MUST CALL
* {@link #sendNewConfiguration} TO UNFREEZE THE SCREEN.
*/
boolean updateRotationUnchecked() {
return mDisplayRotation.updateRotationUnchecked(false /* forceUpdate */);
}
这里直接调用了frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java的updateRotationUnchecked方法:
/**
* Update rotation with an option to force the update. This updates the container's perception
* of rotation and, depending on the top activities, will freeze the screen or start seamless
* rotation. The display itself gets rotated in {@link DisplayContent#applyRotationLocked}
* during {@link DisplayContent#sendNewConfiguration}.
*
* @param forceUpdate Force the rotation update. Sometimes in WM we might skip updating
* orientation because we're waiting for some rotation to finish or display
* to unfreeze, which results in configuration of the previously visible
* activity being applied to a newly visible one. Forcing the rotation
* update allows to workaround this issue.
* @return {@code true} if the rotation has been changed. In this case YOU MUST CALL
* {@link DisplayContent#sendNewConfiguration} TO COMPLETE THE ROTATION AND UNFREEZE
* THE SCREEN.
*/
boolean updateRotationUnchecked(boolean forceUpdate) {
......
mDisplayContent.setLayoutNeeded();
if (shouldRotateSeamlessly(oldRotation, rotation, forceUpdate)) {
// The screen rotation animation uses a screenshot to freeze the screen while windows
// resize underneath. When we are rotating seamlessly, we allow the elements to
// transition to their rotated state independently and without a freeze required.
prepareSeamlessRotation();
} else {
prepareNormalRotationAnimation();
}
// Give a remote handler (system ui) some time to reposition things.
startRemoteRotation(oldRotation, mRotation);
return true;
}
这里通过DisplayRotation的shouldRotateSeamlessly方法判断是否可以无缝旋转:
@VisibleForTesting
boolean shouldRotateSeamlessly(int oldRotation, int newRotation, boolean forceUpdate) {
// Display doesn't need to be frozen because application has been started in correct
// rotation already, so the rest of the windows can use seamless rotation.
if (mDisplayContent.hasTopFixedRotationLaunchingApp()) {
return true;
}
final WindowState w = mDisplayPolicy.getTopFullscreenOpaqueWindow();
if (w == null || w != mDisplayContent.mCurrentFocus) {
return false;
}
// We only enable seamless rotation if the top window has requested it and is in the
// fullscreen opaque state. Seamless rotation requires freezing various Surface states and
// won't work well with animations, so we disable it in the animation case for now.
if (w.getAttrs().rotationAnimation != ROTATION_ANIMATION_SEAMLESS || w.isAnimatingLw()) {
return false;
}
// For the upside down rotation we don't rotate seamlessly as the navigation bar moves
// position. Note most apps (using orientation:sensor or user as opposed to fullSensor)
// will not enter the reverse portrait orientation, so actually the orientation won't change
// at all.
if (oldRotation == mUpsideDownRotation || newRotation == mUpsideDownRotation) {
return false;
}
// If the navigation bar can't change sides, then it will jump when we change orientations
// and we don't rotate seamlessly - unless that is allowed, eg. with gesture navigation
// where the navbar is low-profile enough that this isn't very noticeable.
if (!mAllowSeamlessRotationDespiteNavBarMoving && !mDisplayPolicy.navigationBarCanMove()) {
return false;
}
// If the bounds of activity window is different from its parent, then reject to be seamless
// because the window position may change after rotation that will look like a sudden jump.
if (w.mActivityRecord != null && !w.mActivityRecord.matchParentBounds()) {
return false;
}
// In the presence of the PINNED stack or System Alert windows we unfortunately can not
// seamlessly rotate.
if (mDisplayContent.getDefaultTaskDisplayArea().hasPinnedTask()
|| mDisplayContent.hasAlertWindowSurfaces()) {
return false;
}
// We can't rotate (seamlessly or not) while waiting for the last seamless rotation to
// complete (that is, waiting for windows to redraw). It's tempting to check
// mSeamlessRotationCount but that could be incorrect in the case of window-removal.
if (!forceUpdate && mDisplayContent.getWindow(win -> win.mSeamlesslyRotated) != null) {
return false;
}
return true;
}
2 旋转方法
2.1 无缝旋转
若能无缝旋转,则调用了DisplayRotation的prepareSeamlessRotation方法:
private void prepareSeamlessRotation() {
// We are careful to reset this in case a window was removed before it finished
// seamless rotation.
mSeamlessRotationCount = 0;
mRotatingSeamlessly = true;
}
2.2 冻屏旋转
若不能无缝旋转,则调用了DisplayRotation的prepareNormalRotationAnimation方法:
void prepareNormalRotationAnimation() {
cancelSeamlessRotation();
final RotationAnimationPair anim = selectRotationAnimation();
mService.startFreezingDisplay(anim.mExit, anim.mEnter, mDisplayContent);
}
在这里调用了frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java的startFreezingDisplay方法进行冻屏:
void startFreezingDisplay(int exitAnim, int enterAnim) {
startFreezingDisplay(exitAnim, enterAnim, getDefaultDisplayContentLocked());
}
void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent) {
startFreezingDisplay(exitAnim, enterAnim, displayContent,
ROTATION_UNDEFINED /* overrideOriginalRotation */);
}
void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent,
int overrideOriginalRotation) {
......
displayContent.setRotationAnimation(new ScreenRotationAnimation(displayContent,
originalRotation));
}
ScreenRotationAnimation在frameworks/base/services/core/java/com/android/server/wm/ScreenRotationAnimation.java中实现,其构造函数:
ScreenRotationAnimation(DisplayContent displayContent, @Surface.Rotation int originalRotation) {
......
// Check whether the current screen contains any secure content.
final boolean isSecure = displayContent.hasSecureWindowOnScreen();
final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
try {
mBackColorSurface = displayContent.makeChildSurface(null)
.setName("BackColorSurface")
.setColorLayer()
.setCallsite("ScreenRotationAnimation")
.build();
mScreenshotLayer = displayContent.makeOverlay()
.setName("RotationLayer")
.setBufferSize(mWidth, mHeight)
.setSecure(isSecure)
.setCallsite("ScreenRotationAnimation")
.build();
mEnterBlackFrameLayer = displayContent.makeOverlay()
.setName("EnterBlackFrameLayer")
.setContainerLayer()
.setCallsite("ScreenRotationAnimation")
.build();
// In case display bounds change, screenshot buffer and surface may mismatch so set a
// scaling mode.
SurfaceControl.Transaction t2 = mService.mTransactionFactory.get();
t2.setOverrideScalingMode(mScreenshotLayer, Surface.SCALING_MODE_SCALE_TO_WINDOW);
t2.apply(true /* sync */);
// Capture a screenshot into the surface we just created.
final int displayId = displayContent.getDisplayId();
final Surface surface = mService.mSurfaceFactory.get();
surface.copyFrom(mScreenshotLayer);
SurfaceControl.ScreenshotGraphicBuffer gb =
mService.mDisplayManagerInternal.systemScreenshot(displayId);
if (gb != null) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"ScreenRotationAnimation#getMedianBorderLuma");
mStartLuma = RotationAnimationUtils.getMedianBorderLuma(gb.getGraphicBuffer(),
gb.getColorSpace());
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
try {
surface.attachAndQueueBufferWithColorSpace(gb.getGraphicBuffer(),
gb.getColorSpace());
} catch (RuntimeException e) {
Slog.w(TAG, "Failed to attach screenshot - " + e.getMessage());
}
// If the screenshot contains secure layers, we have to make sure the
// screenshot surface we display it in also has FLAG_SECURE so that
// the user can not screenshot secure layers via the screenshot surface.
if (gb.containsSecureLayers()) {
t.setSecure(mScreenshotLayer, true);
}
t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE);
t.reparent(mBackColorSurface, displayContent.getSurfaceControl());
t.setLayer(mBackColorSurface, -1);
t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
t.setAlpha(mBackColorSurface, 1);
t.show(mScreenshotLayer);
t.show(mBackColorSurface);
} else {
Slog.w(TAG, "Unable to take screenshot of display " + displayId);
}
surface.destroy();
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate freeze surface", e);
}
ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
" FREEZE %s: CREATE", mScreenshotLayer);
setRotation(t, realOriginalRotation);
t.apply();
}
在这里对屏幕进行了截屏,保存在mScreenshotLayer中。
3 旋转动画
接下来回到WindowManagerService的updateRotation方法中,还调用了displayContent(frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java)的sendNewConfiguration方法:
void sendNewConfiguration() {
......
// Something changed (E.g. device rotation), but no configuration update is needed.
// E.g. changing device rotation by 180 degrees. Go ahead and perform surface placement to
// unfreeze the display since we froze it when the rotation was updated in
// DisplayContent#updateRotationUnchecked.
if (mWaitingForConfig) {
mWaitingForConfig = false;
mWmService.mLastFinishedFreezeSource = "config-unchanged";
setLayoutNeeded();
mWmService.mWindowPlacerLocked.performSurfacePlacement();
}
}
mWmService.mWindowPlacerLocked是frameworks/base/services/core/java/com/android/server/wm/WindowSurfacePlacer.java,调用了其performSurfacePlacement方法进入窗口布局流程,会调用frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java的performSurfacePlacementNoTrace方法:
// "Something has changed! Let's make it correct now."
// TODO: Super crazy long method that should be broken down...
void performSurfacePlacementNoTrace() {
......
if (mOrientationChangeComplete) {
if (mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
mWmService.mLastFinishedFreezeSource = mLastWindowFreezeSource;
mWmService.mH.removeMessages(WINDOW_FREEZE_TIMEOUT);
}
mWmService.stopFreezingDisplayLocked();
}
......
}
其中调用了WindowManagerService的stopFreezingDisplayLocked方法:
void stopFreezingDisplayLocked() {
......
ScreenRotationAnimation screenRotationAnimation = displayContent == null ? null
: displayContent.getRotationAnimation();
if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
ProtoLog.i(WM_DEBUG_ORIENTATION, "**** Dismissing screen rotation animation");
DisplayInfo displayInfo = displayContent.getDisplayInfo();
// Get rotation animation again, with new top window
if (!displayContent.getDisplayRotation().validateRotationAnimation(
mExitAnimId, mEnterAnimId, false /* forceDefault */)) {
mExitAnimId = mEnterAnimId = 0;
}
if (screenRotationAnimation.dismiss(mTransaction, MAX_ANIMATION_DURATION,
getTransitionAnimationScaleLocked(), displayInfo.logicalWidth,
displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
mTransaction.apply();
} else {
screenRotationAnimation.kill();
displayContent.setRotationAnimation(null);
updateRotation = true;
}
} else {
if (screenRotationAnimation != null) {
screenRotationAnimation.kill();
displayContent.setRotationAnimation(null);
}
updateRotation = true;
}
......
}
其中调用了ScreenRotationAnimation的dismiss方法:
/**
* Returns true if animating.
*/
public boolean dismiss(SurfaceControl.Transaction t, long maxAnimationDuration,
float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
if (mScreenshotLayer == null) {
// Can't do animation.
return false;
}
if (!mStarted) {
mEndLuma = RotationAnimationUtils.getLumaOfSurfaceControl(mDisplayContent.getDisplay(),
mDisplayContent.getWindowingLayer());
startAnimation(t, maxAnimationDuration, animationScale, finalWidth, finalHeight,
exitAnim, enterAnim);
if (mPerf != null && !mIsPerfLockAcquired) {
mPerf.perfHint(BoostFramework.VENDOR_HINT_ROTATION_ANIM_BOOST, null);
mIsPerfLockAcquired = true;
}
}
if (!mStarted) {
return false;
}
mFinishAnimReady = true;
return true;
}
又调用了ScreenRotationAnimation的startAnimation方法:
/**
* Returns true if animating.
*/
private boolean startAnimation(SurfaceControl.Transaction t, long maxAnimationDuration,
float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
......
mStarted = true;
// Figure out how the screen has moved from the original rotation.
int delta = DisplayContent.deltaRotation(mCurRotation, mOriginalRotation);
final boolean customAnim;
if (exitAnim != 0 && enterAnim != 0) {
customAnim = true;
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, exitAnim);
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, enterAnim);
mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.screen_rotate_alpha);
} else {
customAnim = false;
switch (delta) { /* Counter-Clockwise Rotations */
case Surface.ROTATION_0:
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.screen_rotate_0_exit);
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.screen_rotate_0_enter);
break;
case Surface.ROTATION_90:
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.screen_rotate_plus_90_exit);
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.screen_rotate_plus_90_enter);
break;
case Surface.ROTATION_180:
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.screen_rotate_180_exit);
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.screen_rotate_180_enter);
break;
case Surface.ROTATION_270:
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.screen_rotate_minus_90_exit);
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.screen_rotate_minus_90_enter);
break;
}
}
......
if (customAnim) {
mSurfaceRotationAnimationController.startCustomAnimation();
} else {
mSurfaceRotationAnimationController.startScreenRotationAnimation();
}
return true;
}
这里首先通过AnimationUtils的loadAnimation方法加载动画,然后调用了ScreenRotationAnimation的内部类SurfaceRotationAnimationController的startCustomAnimation或startScreenRotationAnimation方法:
void startCustomAnimation() {
try {
mService.mSurfaceAnimationRunner.deferStartingAnimations();
mRotateScreenAnimator = startScreenshotAlphaAnimation();
mDisplayAnimator = startDisplayRotation();
if (mEnteringBlackFrame != null) {
mEnterBlackFrameAnimator = startEnterBlackFrameAnimation();
}
} finally {
mService.mSurfaceAnimationRunner.continueStartingAnimations();
}
}
/**
* Start the rotation animation of the display and the screenshot on the
* {@link SurfaceAnimationRunner}.
*/
void startScreenRotationAnimation() {
try {
mService.mSurfaceAnimationRunner.deferStartingAnimations();
mDisplayAnimator = startDisplayRotation();
mScreenshotRotationAnimator = startScreenshotRotationAnimation();
startColorAnimation();
} finally {
mService.mSurfaceAnimationRunner.continueStartingAnimations();
}
}
这里面有许多startXXXAnimation方法,它们都会调用SurfaceRotationAnimationController的startAnimation方法:
/**
* Start an animation defined by animationSpec on a new {@link SurfaceAnimator}.
*
* @param animatable The animatable used for the animation.
* @param animationSpec The spec of the animation.
* @param animationFinishedCallback Callback passed to the {@link SurfaceAnimator}
* and called when the animation finishes.
* @return The newly created {@link SurfaceAnimator} that as been started.
*/
private SurfaceAnimator startAnimation(
SurfaceAnimator.Animatable animatable,
LocalAnimationAdapter.AnimationSpec animationSpec,
OnAnimationFinishedCallback animationFinishedCallback) {
SurfaceAnimator animator = new SurfaceAnimator(
animatable, animationFinishedCallback, mService);
LocalAnimationAdapter localAnimationAdapter = new LocalAnimationAdapter(
animationSpec, mService.mSurfaceAnimationRunner);
animator.startAnimation(mDisplayContent.getPendingTransaction(),
localAnimationAdapter, false, ANIMATION_TYPE_SCREEN_ROTATION);
return animator;
}
在这里首先创建了一个frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimator.java,然后调用了其startAnimation方法开始动画。