WMS轉屏流程
PhoneWindowManager會通過WindowOrientaionListener監(jiān)聽傳感器數(shù)據,判斷是否需要轉屏,如果需要轉屏,凍屏截屏,設置轉屏動畫,然后通知AMS更新configuration,AMS發(fā)轉屏廣播通知應用進程,調整Activity task ,然后調用WMS.continueSurfaceLayout這里調用WindowSurfacePlacer.performSurfacePlacement重新繪制窗口和執(zhí)行窗口動畫,繼而像Choregrapher發(fā)送繪制動畫請求,開始執(zhí)行動畫,執(zhí)行完轉屏動畫后,解凍窗口
從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發(fā)送新的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. 停止分發(fā)輸入事件,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. 創(chuàng)建并且設置轉屏動畫,因為轉屏動畫和凍屏動畫同時執(zhí)行
screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent,
mPolicy.isDefaultOrientationForced(), isSecure,
this);
mAnimator.setScreenRotationAnimationLocked(mFrozenDisplayId,
screenRotationAnimation);
}
}
在ScreenRotationAnimation構造方法中創(chuàng)建截圖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();
//創(chuàng)建個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();
}
到這已經凍屏,停止輸入事件分發(fā),并設置了截圖Surface顯示在最上面,設置了轉屏動畫,但還沒有開始執(zhí)行,現(xiàn)在回到WindowManagerService.updateRotationUnchecked繼續(xù)往下分析,通知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期間繪制窗口,因為調整完還有更新窗口,所以是為了優(yōu)化性能
mWindowManager.deferSurfaceLayout();
}
try {
if (values != null) {
//AMS發(fā)轉屏廣播,通知應用進程,調整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上面說過會觸發(fā)繪制動畫,最后調用到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這里判斷,如果窗口正處于凍結狀態(tài)那么doRequest為true ,需要再次調用requestTraversal重新繪制解凍,mService.mWindowsFreezingScreen
doRequest = mService.mRoot.copyAnimToLayoutParams();
}
if (hasPendingLayoutChanges || doRequest) {
//這里解凍
mService.mWindowPlacerLocked.requestTraversal();
}
... ...
}
}
開始繪制轉屏動畫,然后發(fā)現(xiàn)屏幕還是凍著的,所以再次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截圖后開始執(zhí)行動畫
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 分發(fā)事件時,如果發(fā)現(xiàn)凍屏標志為true,那么不繼續(xù)分發(fā)該事件,而ANR是因為已經找到處理事件窗口,但是窗口沒有反饋處理結果,超過5s則觸發(fā)ANR