WMS转屏流程
WMS转屏流程
PhoneWindowManager会通过WindowOrientaionListener监听传感器数据,判断是否需要转屏,如果需要转屏,冻屏截屏,设置转屏动画,然后通知AMS更新configuration,AMS发转屏广播通知应用进程,调整Activity task ,然后调用WMS.continueSurfaceLayout这里调用WindowSurfacePlacer.performSurfacePlacement重新绘制窗口和执行窗口动画,继而像Choregrapher发送绘制动画请求,开始执行动画,执行完转屏动画后,解冻窗口
从WMS.updateRotationUnchecked开始处理转屏分析:
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
private void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
if(DEBUG_ORIENTATION) Slog.v(TAG_WM, "updateRotationUnchecked:"
+ " alwaysSendConfiguration=" + alwaysSendConfiguration
+ " forceRelayout=" + forceRelayout);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation");
long origId = Binder.clearCallingIdentity();
try {
// TODO(multi-display): Update rotation for different displays separately.
final boolean rotationChanged;
final int displayId;
synchronized (mWindowMap) {
final DisplayContent displayContent = getDefaultDisplayContentLocked();
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation: display");
//1. 开始冻屏
rotationChanged = displayContent.updateRotationUnchecked();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (!rotationChanged || forceRelayout) {
displayContent.setLayoutNeeded();
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"updateRotation: performSurfacePlacement");
mWindowPlacerLocked.performSurfacePlacement();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
displayId = displayContent.getDisplayId();
}
if (rotationChanged || alwaysSendConfiguration) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation: sendNewConfiguration");
//2. 给AMS发送新的Configuration信息
sendNewConfiguration(displayId);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
} finally {
Binder.restoreCallingIdentity(origId);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
displayContent.updateRotationUnchecked()方法主要调用startFreezingDisplayLocked 开始冻屏
/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
void startFreezingDisplayLocked(int exitAnim, int enterAnim,
DisplayContent displayContent) {
... ...
mScreenFrozenLock.acquire();
mDisplayFrozen = true;
mDisplayFreezeTime = SystemClock.elapsedRealtime();
mLastFinishedFreezeSource = null;
// {@link mDisplayFrozen} prevents us from freezing on multiple displays at the same time.
// As a result, we only track the display that has initially froze the screen.
mFrozenDisplayId = displayContent.getDisplayId();
//1. 停止分发输入事件,mInputMonitor用于WMS和InputManagerService 通信
mInputMonitor.freezeInputDispatchingLw();
// Clear the last input window -- that is just used for
// clean transitions between IMEs, and if we are freezing
// the screen then the whole world is changing behind the scenes.
mPolicy.setLastInputMethodWindowLw(null, null);
//2.结束app动画
if (mAppTransition.isTransitionSet()) {
mAppTransition.freeze();
}
if (PROFILE_ORIENTATION) {
File file = new File("/data/system/frozen");
Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
}
mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
// TODO(multidisplay): rotation on non-default displays
if (CUSTOM_SCREEN_ROTATION && displayContent.isDefaultDisplay) {
mExitAnimId = exitAnim;
mEnterAnimId = enterAnim;
//3. 停止窗口动画
ScreenRotationAnimation screenRotationAnimation =
mAnimator.getScreenRotationAnimationLocked(mFrozenDisplayId);
if (screenRotationAnimation != null) {
screenRotationAnimation.kill();
}
// Check whether the current screen contains any secure content.
boolean isSecure = displayContent.hasSecureWindowOnScreen();
displayContent.updateDisplayInfo();
//4. 创建并且设置转屏动画,因为转屏动画和冻屏动画同时执行
screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent,
mPolicy.isDefaultOrientationForced(), isSecure,
this);
mAnimator.setScreenRotationAnimationLocked(mFrozenDisplayId,
screenRotationAnimation);
}
}
在ScreenRotationAnimation构造方法中创建截图Surface
public ScreenRotationAnimation(Context context, DisplayContent displayContent,
boolean forceDefaultOrientation, boolean isSecure, WindowManagerService service) {
... ...
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
try {
mSurfaceControl = displayContent.makeOverlay()
.setName("ScreenshotSurface")
.setSize(mWidth, mHeight)
.setSecure(isSecure)
.build();
//创建个Surface并将截图显示在上面, Surface layer非常高
// capture a screenshot into the surface we just created
// TODO(multidisplay): we should use the proper display
final int displayId = SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN;
final IBinder displayHandle = SurfaceControl.getBuiltInDisplay(displayId);
// This null check below is to guard a race condition where WMS didn't have a chance to
// respond to display disconnection before handling rotation , that surfaceflinger may
// return a null handle here because it doesn't think that display is valid anymore.
if (displayHandle != null) {
Surface sur = new Surface();
sur.copyFrom(mSurfaceControl);
SurfaceControl.screenshot(displayHandle, sur);
t.setLayer(mSurfaceControl, SCREEN_FREEZE_LAYER_SCREENSHOT);
t.setAlpha(mSurfaceControl, 0);
t.show(mSurfaceControl);
sur.destroy();
} else {
Slog.w(TAG, "Built-in display " + displayId + " is null.");
}
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate freeze surface", e);
}
if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG_WM,
" FREEZE " + mSurfaceControl + ": CREATE");
//设置旋转动画矩阵
setRotation(t, originalRotation);
t.apply();
}
到这已经冻屏,停止输入事件分发,并设置了截图Surface显示在最上面,设置了转屏动画,但还没有开始执行,现在回到WindowManagerService.updateRotationUnchecked继续往下分析,通知AMS转屏
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
private boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
boolean initLocale, boolean persistent, int userId, boolean deferResume,
UpdateConfigurationResult result) {
int changes = 0;
boolean kept = true;
if (mWindowManager != null) {
//延迟绘制窗口,应该是防止调整Activity期间绘制窗口,因为调整完还有更新窗口,所以是为了优化性能
mWindowManager.deferSurfaceLayout();
}
try {
if (values != null) {
//AMS发转屏广播,通知应用进程,调整Activity
changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId,
deferResume);
}
kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
} finally {
if (mWindowManager != null) {
//最后再回到WMS中调整窗口,重新绘制窗口
mWindowManager.continueSurfaceLayout();
}
}
if (result != null) {
result.changes = changes;
result.activityRelaunched = !kept;
}
return kept;
}
WMS.continueSurfaceLayout上面说过会触发绘制动画,最后调用到WindowAnimator.animate
frameworks/base/services/core/java/com/android/server/wm/WindowAnimator.java
/**
* DO NOT HOLD THE WINDOW MANAGER LOCK WHILE CALLING THIS METHOD. Reason: the method closes
* an animation transaction, that might be blocking until the next sf-vsync, so we want to make
* sure other threads can make progress if this happens.
*/
private void animate(long frameTimeNs) {
synchronized (mService.mWindowMap) {
if (!mInitialized) {
return;
}
// Schedule next frame already such that back-pressure happens continuously
scheduleAnimation();
}
synchronized (mService.mWindowMap) {
mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
//WindowSurfacePlacer.performSurfacePlacement中会根据这个值调用stopFreezingDisplayLocked解冻
mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE;
mAnimating = false;
if (DEBUG_WINDOW_TRACE) {
Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
}
if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION animate");
mService.openSurfaceTransaction();
try {
final AccessibilityController accessibilityController =
mService.mAccessibilityController;
final int numDisplays = mDisplayContentsAnimators.size();
for (int i = 0; i < numDisplays; i++) {
final int displayId = mDisplayContentsAnimators.keyAt(i);
final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);
final ScreenRotationAnimation screenRotationAnimation =
displayAnimator.mScreenRotationAnimation;
if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
//开始屏幕旋转动画
if (screenRotationAnimation.stepAnimationLocked(mCurrentTime)) {
setAnimating(true);
} else {
mBulkUpdateParams |= SET_UPDATE_ROTATION;
screenRotationAnimation.kill();
displayAnimator.mScreenRotationAnimation = null;
//TODO (multidisplay): Accessibility supported only for the default
// display.
if (accessibilityController != null && dc.isDefaultDisplay) {
// We just finished rotation animation which means we did not
// announce the rotation and waited for it to end, announce now.
accessibilityController.onRotationChangedLocked(
mService.getDefaultDisplayContentLocked());
}
}
}
// Update animations of all applications, including those
// associated with exiting/removed apps
++mAnimTransactionSequence;
//开始窗口动画
dc.updateWindowsForAnimator(this);
//开始壁纸动画
dc.updateWallpaperForAnimator(this);
//将变换矩阵设置在Surface上
dc.prepareSurfaces();
}
... ...
SurfaceControl.mergeToGlobalTransaction(mTransaction);
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
mService.closeSurfaceTransaction("WindowAnimator");
if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION animate");
}
boolean hasPendingLayoutChanges = mService.mRoot.hasPendingLayoutChanges(this);
boolean doRequest = false;
if (mBulkUpdateParams != 0) {
//copyAnimToLayoutParams这里判断,如果窗口正处于冻结状态那么doRequest为true ,需要再次调用requestTraversal重新绘制解冻,mService.mWindowsFreezingScreen
doRequest = mService.mRoot.copyAnimToLayoutParams();
}
if (hasPendingLayoutChanges || doRequest) {
//这里解冻
mService.mWindowPlacerLocked.requestTraversal();
}
... ...
}
}
开始绘制转屏动画,然后发现屏幕还是冻着的,所以再次WindowSurfacePlacer.requestTraversal重新绘制窗口->Choregrapher回调->WindowSurfacePlacer.performSurfacePlacement->RootWindowContainer.performSurfacePlacement
frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
void performSurfacePlacement(boolean recoveringMemory) {
... ...
//mOrientationChangeComplete根据mBulkUpdateParams& SET_ORIENTATION_CHANGE_COMPLETE判断,所以这里为true
if (mOrientationChangeComplete) {
if (mService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
mService.mLastFinishedFreezeSource = mLastWindowFreezeSource;
mService.mH.removeMessages(WINDOW_FREEZE_TIMEOUT);
}
mService.stopFreezingDisplayLocked();
}
... ...
}
最后冻屏结束处理
void stopFreezingDisplayLocked() {
... ...
final DisplayContent displayContent = mRoot.getDisplayContent(mFrozenDisplayId);
// We must make a local copy of the displayId as it can be potentially overwritten later on
// in this method. For example, {@link startFreezingDisplayLocked} may be called as a result
// of update rotation, but we reference the frozen display after that call in this method.
final int displayId = mFrozenDisplayId;
mFrozenDisplayId = INVALID_DISPLAY;
mDisplayFrozen = false;
//通知InputManagerService 解除冻屏
mInputMonitor.thawInputDispatchingLw();
... ...
boolean updateRotation = false;
ScreenRotationAnimation screenRotationAnimation =
mAnimator.getScreenRotationAnimationLocked(displayId);
if (CUSTOM_SCREEN_ROTATION && screenRotationAnimation != null
&& screenRotationAnimation.hasScreenshot()) {
if (DEBUG_ORIENTATION) Slog.i(TAG_WM, "**** Dismissing screen rotation animation");
// TODO(multidisplay): rotation on main screen only.
DisplayInfo displayInfo = displayContent.getDisplayInfo();
// Get rotation animation again, with new top window
if (!mPolicy.validateRotationAnimationLw(mExitAnimId, mEnterAnimId, false)) {
mExitAnimId = mEnterAnimId = 0;
}
//dismiss截图后开始执行动画
if (screenRotationAnimation.dismiss(mTransaction, MAX_ANIMATION_DURATION,
getTransitionAnimationScaleLocked(), displayInfo.logicalWidth,
displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
mTransaction.apply();
scheduleAnimationLocked();
} else {
screenRotationAnimation.kill();
mAnimator.setScreenRotationAnimationLocked(displayId, null);
updateRotation = true;
}
} else {
if (screenRotationAnimation != null) {
screenRotationAnimation.kill();
mAnimator.setScreenRotationAnimationLocked(displayId, null);
}
updateRotation = true;
}
boolean configChanged;
// While the display is frozen we don't re-compute the orientation
// to avoid inconsistent states. However, something interesting
// could have actually changed during that time so re-evaluate it
// now to catch that.
configChanged = updateOrientationFromAppTokensLocked(displayId);
... ...
//释放weak lock
mScreenFrozenLock.release();
... ...
}
需要注意的是冻屏不会导致ANR,冻屏只是在InputDispatcher 分发事件时,如果发现冻屏标志为true,那么不继续分发该事件,而ANR是因为已经找到处理事件窗口,但是窗口没有反馈处理结果,超过5s则触发ANR