该流程适用于android12-android14
- Sensor检测到方向发生变化,计算出最终的角度
- 冻屏并准备旋转动画、
- 通知到systemui。
- 根据新的方向计算出新的Configuration,并向下分发
- 等待reszie的窗口完成重绘,relaunch的activity完成relaunch
- 解冻条件满足,准备解冻屏幕并开始屏幕旋转动画
1. Sensor检测到方向发生变化,计算出最终的角度
sensor检测到方向发生变化后会调用到DisplayRotation的updateRotationUnchecked:主要做了以下处理
- 根据之前的和现在的得到最终计算出新的rotation(比如屏幕锁定,等等条件)
- 设置 mService.mWindowsFreezingScreen的状态为 WINDOWS_FREEZING_SCREENS_ACTIVE,随后向handler发送一个窗口冻结超时的延时回调,避免因为旋转引起的窗口重新绘制过久引起的屏幕冻结过久
boolean updateRotationUnchecked(boolean forceUpdate) {
final int displayId = mDisplayContent.getDisplayId();
if (!forceUpdate) {
// 一般为false
if (mDeferredRotationPauseCount > 0) {
// Rotation updates have been paused temporarily. Defer the update until updates
// have been resumed.
ProtoLog.v(WM_DEBUG_ORIENTATION, "Deferring rotation, rotation is paused.");
return false;
}
......
// 当前屏幕是否处于冻结状态 , 目前还未开始冻屏
if (mService.mDisplayFrozen) {
// Even if the screen rotation animation has finished (e.g. isAnimating returns
// false), there is still some time where we haven't yet unfrozen the display. We
// also need to abort rotation here.
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Deferring rotation, still finishing previous rotation");
return false;
}
}
// 这两个值先留意下
final int oldRotation = mRotation; // 之前的角度
final int lastOrientation = mLastOrientation; // 上一次旋转后的角度
// 1.根据之前的和现在的得到最终计算出新的rotation (比如屏幕锁定,等等条件)
int rotation = rotationForOrientation(lastOrientation, oldRotation);
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Computed rotation=%s (%d) for display id=%d based on lastOrientation=%s (%d) and "
+ "oldRotation=%s (%d)",
Surface.rotationToString(rotation), rotation,
displayId,
ActivityInfo.screenOrientationToString(lastOrientation), lastOrientation,
Surface.rotationToString(oldRotation), oldRotation);
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d selected orientation %s (%d), got rotation %s (%d)", displayId,
ActivityInfo.screenOrientationToString(lastOrientation), lastOrientation,
Surface.rotationToString(rotation), rotation);
// 角度没变 直接返回 FASLE
if (oldRotation == rotation) {
// No change.
return false;
}
......
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d rotation changed to %d from %d, lastOrientation=%d",
displayId, rotation, oldRotation, lastOrientation);
if (deltaRotation(oldRotation, rotation) != Surface.ROTATION_180) {
mDisplayContent.mWaitingForConfig = true;
}
// 这里先把 计算出的rotation 保存下来
mRotation = rotation;
mDisplayContent.setLayoutNeeded();
.....
// 2.设置 mService.mWindowsFreezingScreen的状态为 WINDOWS_FREEZING_SCREENS_ACTIVE
mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
// 2. 向handler发送一个窗口冻结超时的延时回调,避免因为旋转引起的窗口重新绘制过久引起的屏幕冻结过久
mService.mH.sendNewMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,
mDisplayContent, WINDOW_FREEZE_TIMEOUT_DURATION);
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 {
// 3. 准备旋转动画
prepareNormalRotationAnimation();
}
// Give a remote handler (system ui) some time to reposition things.
// 继续处理 oldRotation: 原来的角度 mRotation 计算出来的新角度
startRemoteRotation(oldRotation, mRotation);
return true;
}
根据传感器的方向,docking mode、旋转锁定等因素计算出最终的屏幕方向
int rotationForOrientation(@ScreenOrientation int orientation,
@Surface.Rotation int lastRotation) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"rotationForOrientation(orient=%s (%d), last=%s (%d)); user=%s (%d) %s",
ActivityInfo.screenOrientationToString(orientation), orientation,
Surface.rotationToString(lastRotation), lastRotation,
Surface.rotationToString(mUserRotation), mUserRotation,
mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED
? "USER_ROTATION_LOCKED" : "");
// 是否使用用户设置的的rotation ,直接返回用户的rotation
if (isFixedToUserRotation()) {
return mUserRotation;
}
// 获取传感器的rotation
int sensorRotation = mOrientationListener != null
? mOrientationListener.getProposedRotation() // may be -1
: -1;
mLastSensorRotation = sensorRotation;
// 传感器返回的rotation不合法,
if (sensorRotation < 0) {
sensorRotation = lastRotation;
}
final int lidState = mDisplayPolicy.getLidState();
final int dockMode = mDisplayPolicy.getDockMode();
final boolean hdmiPlugged = mDisplayPolicy.isHdmiPlugged();
.....
2. 冻屏并准备旋转动画
2.1 读取并设置设置屏幕旋转动画参数
接上文,计算出最终的旋转方向后,开始冻结屏幕并准备旋转动画。 首先通过selectRotationAnimation根据配置信息选择旋转动画参数,一般情况下anim.mExit, anim.mEnter 都等于 0,即采用默认的动画旋转参数
void prepareNormalRotationAnimation() {
cancelSeamlessRotation();
final RotationAnimationPair anim = selectRotationAnimation();
// anim.mExit, anim.mEnter 都等于 0
// 开始冻屏
mService.startFreezingDisplay(anim.mExit, anim.mEnter, mDisplayContent);
}
动画参数确认后,随即开始冻结屏幕
void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent,
int overrideOriginalRotation) {
// 屏幕已经冻结,或采用Seamlessly旋转就不冻结屏幕
if (mDisplayFrozen || displayContent.getDisplayRotation().isRotatingSeamlessly()) {
return;
}
.....
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.doStartFreezingDisplay");
// 开始冻屏
doStartFreezingDisplay(exitAnim, enterAnim, displayContent, overrideOriginalRotation);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
2.2 开始冻结屏幕
开始冻结屏幕,首先初始化以下几个重要的变量,用于记录当前屏幕的冻结状态,冻结的开始时间,解冻条件中最后一个满足的等。
- mDisplayFrozen:标记当前的DisplayContent是否正处于冻结状态
- mDisplayFreezeTime: 记录本次冻结屏幕的开始时间
- mLastFinishedFreezeSource:一个Object,记录解冻条件中最后一个满足的
然后调用mInputManagerCallback.freezeInputDispatchingLw(),将一路调用到inputdispatcher使得DispatchFrozen = true,输入将会被冻结,事件将不会继续向下分发,。
private void doStartFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent,
int overrideOriginalRotation) {
ProtoLog.d(WM_DEBUG_ORIENTATION,
"startFreezingDisplayLocked: exitAnim=%d enterAnim=%d called by %s",
exitAnim, enterAnim, Debug.getCallers(8));
mScreenFrozenLock.acquire();
// Apply launch power mode to reduce screen frozen time because orientation change may
// relaunch activity and redraw windows. This may also help speed up user switching.
mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
// 用于记录当前屏幕的冻结状态,冻结的开始时间,解冻条件中最后一个满足的等
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();
// 冻结输入
mInputManagerCallback.freezeInputDispatchingLw();
if (displayContent.mAppTransition.isTransitionSet()) {
displayContent.mAppTransition.freeze();
}
.....
mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
mExitAnimId = exitAnim;
mEnterAnimId = enterAnim;
// 此时的DisplayInfo应该还没变
displayContent.updateDisplayInfo();
final int originalRotation = overrideOriginalRotation != ROTATION_UNDEFINED
? overrideOriginalRotation
: displayContent.getDisplayInfo().rotation;
// 给对应的屏幕设置屏幕旋转动画,此时的 originalRotation 还是原来的 角度,还没有发生变化呢.
// 设置动画的时候 会截图 并覆盖屏幕上
displayContent.setRotationAnimation(new ScreenRotationAnimation(displayContent,
originalRotation));
}
2.3 设置屏幕旋转动画
继续冻结流程,将会创建一个屏幕旋转动画对象ScreenRotationAnimation,并将其设置到对应的DisplayContent上,需要注意的是此时通过displayContent.getDisplayInfo().rotation获取到的屏幕方向还是原来方向,因为新的方向目前还只是保存在DisplayRotaion中,还并未同步到DisplayContent中。
重点看ScreenRotationAnimation的创建流程,此时参数originalRotation还是当前屏幕的原本方向
- 获取当前DisplayContent的宽高和屏幕方向信息,因为新的屏幕方向信息此时还未同步到DisplayContent中,所以此时 realOriginalRotation == originalRotation,delta == 0;
- 新建一个内部类SurfaceRotationAnimationController对象,该类专门用来控制屏幕旋转相关图层(Layer)的动画的启动和取消等。
- 对当前的DisplayContent进行截图,截图的内容保存在screenshotBuffer中,创建BackColorSurface的颜色layer,创建ScreenRotationAnimation的layer,并将该layer挂在displayContent的Overlay layer下,随后对截图进行采样,并把采样结果渲染到BackColorSurface中,随后把截图的结果转换为GraphicBuffer并绘制到ScreenRotationAnimation layer中,随后展示两个layer。
ScreenRotationAnimation(DisplayContent displayContent, @Surface.Rotation int originalRotation) {
// 进行初始化的操作
mService = displayContent.mWmService;
mContext = mService.mContext;
mDisplayContent = displayContent;
// 获取当前屏幕的 宽高
final Rect currentBounds = displayContent.getBounds();
final int width = currentBounds.width();
final int height = currentBounds.height();
// Screenshot does NOT include rotation!
// 第一次进来的的时候 rotation 还未发生变化
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
final int realOriginalRotation = displayInfo.rotation;
mOriginalRotation = originalRotation;
//第一次进来的时候 delta = 0
final int delta = deltaRotation(originalRotation, realOriginalRotation);
final boolean flipped = delta == Surface.ROTATION_90 || delta == Surface.ROTATION_270;
mOriginalWidth = flipped ? height : width;
mOriginalHeight = flipped ? width : height;
final int logicalWidth = displayInfo.logicalWidth;
final int logicalHeight = displayInfo.logicalHeight;
final boolean isSizeChanged =
logicalWidth > mOriginalWidth == logicalHeight > mOriginalHeight
&& (logicalWidth != mOriginalWidth || logicalHeight != mOriginalHeight);
// 新建一个SurfaceRotationAnimationController,用于控制layer的动画
mSurfaceRotationAnimationController = new SurfaceRotationAnimationController();
// Check whether the current screen contains any secure content.
boolean isSecure = displayContent.hasSecureWindowOnScreen();
final int displayId = displayContent.getDisplayId();
final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
try {
// 准备截图的buffer
final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer;
.....
// 对当前的DisplayContent进行截图
SurfaceControl.LayerCaptureArgs captureArgs =
new SurfaceControl.LayerCaptureArgs.Builder(
displayContent.getSurfaceControl())
.setCaptureSecureLayers(true)
.setAllowProtected(true)
.setSourceCrop(new Rect(0, 0, width, height))
.build();
screenshotBuffer = SurfaceControl.captureLayers(captureArgs);
}
....
// 设置一个 颜色layer
mBackColorSurface = displayContent.makeChildSurface(null)
.setName("BackColorSurface")
.setColorLayer()
.setCallsite("ScreenRotationAnimation")
.build();
String name = "RotationLayer";
// 设置截图layer
mScreenshotLayer = displayContent.makeOverlay()
.setName(name)
.setOpaque(true)
.setSecure(isSecure)
.setCallsite("ScreenRotationAnimation")
.setBLASTLayer()
.build();
// This is the way to tell the input system to exclude this surface from occlusion
// detection since we don't have a window for it. We do this because this window is
// generated by the system as well as its content.
InputMonitor.setTrustedOverlayInputInfo(mScreenshotLayer, t, displayId, name);
// 设置进入的colorlayer
mEnterBlackFrameLayer = displayContent.makeOverlay()
.setName("EnterBlackFrameLayer")
.setContainerLayer()
.setCallsite("ScreenRotationAnimation")
.build();
HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
// 将提供的buffer 转换为位图,然后对位图边界处的亮度进行采样
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"ScreenRotationAnimation#getMedianBorderLuma");
mStartLuma = RotationAnimationUtils.getMedianBorderLuma(hardwareBuffer,
screenshotBuffer.getColorSpace());
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
// 获取截图的buffer
GraphicBuffer buffer = GraphicBuffer.createFromHardwareBuffer(
screenshotBuffer.getHardwareBuffer());
t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE);
//
t.reparent(mBackColorSurface, displayContent.getSurfaceControl());
// If hdr layers are on-screen, e.g. picture-in-picture mode, the screenshot of
// rotation animation is an sdr image containing tone-mapping hdr content, then
// disable dimming effect to get avoid of hdr content being dimmed during animation.
t.setDimmingEnabled(mScreenshotLayer, !screenshotBuffer.containsHdrLayers());
t.setLayer(mBackColorSurface, -1);
t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
t.setAlpha(mBackColorSurface, 1);
// 把截图内容绘制在mScreenshotLayer中
t.setBuffer(mScreenshotLayer, buffer);
t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());
// 显示 mScreenshotLayer mBackColorSurface
t.show(mScreenshotLayer);
t.show(mBackColorSurface);
if (mRoundedCornerOverlay != null) {
for (SurfaceControl sc : mRoundedCornerOverlay) {
if (sc.isValid()) {
t.hide(sc);
}
}
}
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate freeze surface", e);
}
......
ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
" FREEZE %s: CREATE", mScreenshotLayer);
if (originalRotation == realOriginalRotation) {
// 第一次 走这里
setRotation(t, realOriginalRotation);
} else {
// If the given original rotation is different from real original display rotation,
// this is playing non-zero degree rotation animation without display rotation change,
// so the snapshot doesn't need to be transformed.
mCurRotation = realOriginalRotation;
mSnapshotInitialMatrix.reset();
setRotationTransform(t, mSnapshotInitialMatrix);
}
// 对所有修改进行提交
t.apply();
}
冻结屏幕启动主要就是设置了一些冻屏相关的状态变量,然后冻结输入,对当前屏幕截图,并把截图盖在当前屏幕上,这样屏幕旋转过程中就不会响应用户的操作,同时旋转过程中因为窗口需要重新绘制等可能引起的窗口闪黑等不良的现象会被遮盖在截图下面。
3. 跨进程到SystemUI,待SystemUI处理完毕后再回到system_server
屏幕冻结完成后,接下来将把新的屏幕方向发送给SystemUI,SystemUI处理完毕后将会通过binder回调到system_server,执行continueRotation继续执行屏幕旋转流程,如果SystemUI处理过程中,屏幕方向又发生了变化,则终止。
private void startRemoteRotation(int fromRotation, int toRotation) {
// 发送给 SystemUI, SystemUI 处理完毕后 回调 continueRotation
mDisplayContent.mRemoteDisplayChangeController.performRemoteDisplayChange(
fromRotation, toRotation, null /* newDisplayAreaInfo */,
(transaction) -> continueRotation(toRotation, transaction)
);
}
// 回调回来 继续执行 targetRotation: 新的角度
private void continueRotation(int targetRotation, WindowContainerTransaction t) {
// targetRotation != mRotation 说明在此期间 ,角度又发生了 新的变化 , 则终止 , 因为屏幕旋转还会再调用过来
if (targetRotation != mRotation) {
// Drop it, this is either coming from an outdated remote rotation; or, we've
// already moved on.
return;
}
mService.mAtmService.deferWindowLayout();
try {
// 设置新的配置
mDisplayContent.sendNewConfiguration();
.....
} finally {
mService.mAtmService.continueWindowLayout();
}
}
4. 根据新的方向计算出新的Configuration,并向下分发
需要注意的是还会将DisplayContent.mWaitingForConfig = false,标志新的Configuration向各个子节点分发完成,还会mWmService.mLastFinishedFreezeSource = "config-unchanged" 标记当前最后一个结束的冻结屏幕的根源是 config-unchanged
DisplayContent.java
void sendNewConfiguration() {
// // 这个一般正常都是true
if (!isReady()) {
return;
}
// 接上篇末尾的分析,systemui在完成处理后完成回调后该条件为true
if (mRemoteDisplayChangeController.isWaitingForRemoteDisplayChange()) {
return;
}
// 更新Configuration
final boolean configUpdated = updateDisplayOverrideConfigurationLocked();
if (configUpdated) {
return;
}
......
// 将mWaitingForConfig设置为false,意味这新的Config已经生成,
if (mWaitingForConfig) {
mWaitingForConfig = false;
// 标记当前最后一个结束的冻结屏幕的根源是 config-unchanged
mWmService.mLastFinishedFreezeSource = "config-unchanged";
setLayoutNeeded();
mWmService.mWindowPlacerLocked.performSurfacePlacement();
}
....
}
4.1 结合新的屏幕方向计算出新的Configuration
在computeScreenConfiguration中将会从当前DisplayContent对应的DisplayRotation获取旋转后计算出的最终方向,然后把新的方法向信息设置到DisplayContent中,还会根据新的方向生成一个新的Configuration
boolean updateDisplayOverrideConfigurationLocked() {
// Preemptively cancel the running recents animation -- SysUI can't currently handle this
// case properly since the signals it receives all happen post-change
final RecentsAnimationController recentsAnimationController =
mWmService.getRecentsAnimationController();
if (recentsAnimationController != null) {
recentsAnimationController.cancelAnimationForDisplayChange();
}
// 创建一个新的Configuration对象
Configuration values = new Configuration();
// 计算配置,计算后的配置放到 Configuration 中,其中封装了屏幕的 方向信息
computeScreenConfiguration(values);
mAtmService.mH.sendMessage(PooledLambda.obtainMessage(
ActivityManagerInternal::updateOomLevelsForDisplay, mAtmService.mAmInternal,
mDisplayId));
Settings.System.clearConfiguration(values);
// 更新配置 经过前面的处理,现在有了一个新的 Configuration ,就需要更新到设备上
// value 是新的配置信息
updateDisplayOverrideConfigurationLocked(values, null /* starting */,
false /* deferResume */, mAtmService.mTmpUpdateConfigurationResult);
return mAtmService.mTmpUpdateConfigurationResult.changes != 0;
}
4.2 分发新的配置信息Configuration
现在保存着新屏幕方向Configuration已经有了,接下来分发新的配置信息
boolean updateDisplayOverrideConfigurationLocked(Configuration values,
ActivityRecord starting, boolean deferResume,
ActivityTaskManagerService.UpdateConfigurationResult result) {
int changes = 0;
boolean kept = true;
mAtmService.deferWindowLayout();
try {
if (values != null) {
if (mDisplayId == DEFAULT_DISPLAY) {
// Override configuration of the default display duplicates global config, so
// we're calling global config update instead for default display. It will also
// apply the correct override config.
// // 1. 更改全局配置
changes = mAtmService.updateGlobalConfigurationLocked(values,
false /* initLocale */, false /* persistent */,
UserHandle.USER_NULL /* userId */);
} else {
changes = performDisplayOverrideConfigUpdate(values);
}
}
if (!deferResume) {
// 2 . 重要,更新可见性的信息,确保设置更新后正确的显示(recreate/relunch)
kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
}
} finally {
mAtmService.continueWindowLayout();
}
if (result != null) {
result.changes = changes;
result.activityRelaunched = !kept;
}
return kept;
}
4.2.1 向app进程的Appilcation发送新的Configuration
首先会合并新的Configuration信息,随后遍历所有Android应用进程在WMS的体现:WindowProcessController,即每一个app进程在WMS都有一个对应的WindowProcessController,将会把新的保存在ConfigurationChangeItem中,然后把该ConfigurationChangeItem跨进程发送到App端,App端会触发Appication的onConfigurationChanged并保存新的Configuration
// 去更新全局的 Configuration
/** Update default (global) configuration and notify listeners about changes. */
int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,
boolean persistent, int userId) {
mTempConfig.setTo(getGlobalConfiguration());
// 1.合并更新 信息,changes 反应 Configuration 有没有变化则返回
final int changes = mTempConfig.updateFrom(values);
if (changes == 0) {
return 0;
}
ProtoLog.i(WM_DEBUG_CONFIGURATION, "Updating global configuration "
+ "to: %s", values);
writeConfigurationChanged(changes);
FrameworkStatsLog.write(FrameworkStatsLog.RESOURCE_CONFIGURATION_CHANGED,
values.colorMode,
values.densityDpi,
values.fontScale,
values.hardKeyboardHidden,
values.keyboard,
values.keyboardHidden,
values.mcc,
values.mnc,
values.navigation,
values.navigationHidden,
values.orientation,
values.screenHeightDp,
values.screenLayout,
values.screenWidthDp,
values.smallestScreenWidthDp,
values.touchscreen,
values.uiMode);
......
SparseArray<WindowProcessController> pidMap = mProcessMap.getPidMap();
// 2.遍历所有进程 ,把 新的 Configuration 发送各个进程
for (int i = pidMap.size() - 1; i >= 0; i--) {
final int pid = pidMap.keyAt(i);
final WindowProcessController app = pidMap.get(pid);
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Update process config of %s to new "
+ "config %s", app.mName, mTempConfig);
//会触发 app 的 Application 类的onConfigurationChanged
app.onConfigurationChanged(mTempConfig);
}
// 3. 发送配置改变的 广播
final Message msg = PooledLambda.obtainMessage(
ActivityManagerInternal::broadcastGlobalConfigurationChanged,
mAmInternal, changes, initLocale);
mH.sendMessage(msg);
// Update stored global config and notify everyone about the change.
// 4 . 重要 同最顶层的容器开始 ,开始派发 Configuration
mRootWindowContainer.onConfigurationChanged(mTempConfig);
return changes;
}
4.2.2 从WMS的顶层容器开始向窗口层级树上分发新的Configuration
4.2.2.1 标记窗口正处于旋转状态
- 分发新的configuration过程中将会遍历到DisplayContent时,会将旋转后的方向正式设置到ScreenRotationAnimation中
- 紧接着不用等分发的新的configuration通过层级树遍历到WindowState,将直接在DisplayContent中通过for循环直接遍历当前DisplayContent下所有窗口,如果该窗口有Surface,且非rotateSeamlessly就调用WindowState.setOrientationChanging(true);
private void applyRotation(final int oldRotation, final int rotation) {
mDisplayRotation.applyCurrentRotation(rotation);
....
if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
// 在这里 设置真正的屏幕旋转角度
screenRotationAnimation.setRotation(transaction, rotation);
}
.....
mWmService.mDisplayManagerInternal.performTraversal(transaction);
scheduleAnimation();
Slog.d(TAG, "applyRotation oldRotation:" + oldRotation + " rotation:" + rotation, new Throwable());
forAllWindows(w -> {
if (!w.mHasSurface) return;
if (!rotateSeamlessly) {
ProtoLog.v(WM_DEBUG_ORIENTATION, "Set mOrientationChanging of %s", w);
w.setOrientationChanging(true);
}
}, true /* traverseTopToBottom */);
.....
}
将WindowState的mOrientationChanging设置为true,同时如果RootWindowContaner的mOrientationChangeComplete为false,且该窗口需要同步方向改变,则将RootWindowContainer的mOrientationChangeComplete设置为true;
需要注意的是
- RootWindowContainer.mOrientationChangeComplete是判断是否进行屏幕解冻的重要依据之一,mOrientationChangeComplete为false意味着当前屏幕旋转流程还未结束,将不能触发解冻流程。
- shouldSyncRotationChange() 用于判断当前的窗口需要等待同步方向发生变化,除了 状态栏, 导航栏,返回手势栏等系统窗口,大部分窗口都是需要等待同步方向发生变化,即需要等待窗口在屏幕方向发生变化完成根据新的屏幕方向完成重新绘制。 该方法在下文解冻过程中也会被调用,下文再具体展开。
WindowState.java
void setOrientationChanging(boolean changing) {
mOrientationChangeTimedOut = false;
if (mOrientationChanging == changing) {
return;
}
// 将该窗口的mOrientationChanging设置为true
mOrientationChanging = changing;
if (changing) {
mLastFreezeDuration = 0;
// 如果mOrientationChangeComplete为false,且该窗口需要同步屏幕旋转变化
if (mWmService.mRoot.mOrientationChangeComplete
&& mDisplayContent.shouldSyncRotationChange(this)) {
mWmService.mRoot.mOrientationChangeComplete = false;
}
} else {
// The orientation change is completed. If it was hidden by the animation, reshow it.
mDisplayContent.finishAsyncRotation(mToken);
}
}
4.2.2.1 标记窗口正处于旋转状态
接上文,接下来将会从顶层容器RootWindowContainer开始向各个子节点分发新的Configuration,各个子节点将结合自己的Configuration和新的Configuration进行更新,因为屏幕方向发生了改变将会引发宽高发生改变,进而引起所有自己点的size信息发生变化: 堆栈信息如下:
最终将会调用到WindowState.onResize()中:
将会遍历到当前屏幕上的所有WindowState,调用WindowState的onResize方法,如果当前窗口有用Surface,且“可见”,就把该窗口添加到 WindowManagerService的mResizingWindows列表中。
WindowState.java
@Override
void onResize() {
final ArrayList<WindowState> resizingWindows = mWmService.mResizingWindows;
if (mHasSurface && !isGoneForLayout() && !resizingWindows.contains(this)) {
ProtoLog.d(WM_DEBUG_RESIZE, "onResize: Resizing %s", this);
// 当函数被调用的话 ,就把该windowState 添加到 mWmService.mResizingWindows 中,
// 最后会在 RootWindowContainer的 handleResizeWindow 中做处理
resizingWindows.add(this);
}
if (isGoneForLayout()) {
mResizedWhileGone = true;
}
super.onResize();
随后,当下一次窗口布局过程的后期,调用handleResizingWindows,统一对需要resize的WindowState进行处理:
RootWindowContainer.java
private void handleResizingWindows() {
for (int i = mWmService.mResizingWindows.size() - 1; i >= 0; i--) {
WindowState win = mWmService.mResizingWindows.get(i);
// 该窗口属于冻结的app 则跳过
if (win.mAppFreezing || win.getDisplayContent().mWaitingForConfig) {
// Don't remove this window until rotation has completed and is not waiting for the
// complete configuration.
continue;
}
//
win.reportResized();
mWmService.mResizingWindows.remove(i);
}
}
再次来到WindowState,调用reportResized,如果当前WindowState属于正在Relunch的Activity则返回,否则将跨进程到app端,去触发窗口的重新布局和绘制。同时将当前的时间保存在WindowState的mOrientationChangeRedrawRequestTime中,用于记录当前WindowState重绘的开始时间。
WindowState.java
void reportResized() {
// 如果是属于正在 Relaunching的activity 则不作处理直接返回
if (inRelaunchingActivity()) {
return;
}
// 再检查下当前窗口的可见性,如果对应的Token请求了不可见,则不需要重新绘制。
if (shouldCheckTokenVisibleRequested() && !mToken.isVisibleRequested()) {
return;
}
if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wm.reportResized_" + getWindowTag());
}
// 重要打印
ProtoLog.v(WM_DEBUG_RESIZE, "Reporting new frame to %s: %s", this,
mWindowFrames.mCompatFrame);
final boolean drawPending = mWinAnimator.mDrawState == DRAW_PENDING;
if (drawPending) {
ProtoLog.i(WM_DEBUG_ORIENTATION, "Resizing %s WITH DRAW PENDING", this);
}
......
try {
// 调用到这里 通知应用端重新布局绘制窗口
mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
syncWithBuffers ? mSyncSeqId : -1, resizeMode);
if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration
.getMergedConfiguration().windowConfiguration.getRotation()) {
// 同于记录当前窗口的开始重绘的开始时间
mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime();
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Requested redraw for orientation change: %s", this);
}
.....
} catch (RemoteException e) {
// Cancel orientation change of this window to avoid blocking unfreeze display.
setOrientationChanging(false);
mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
- mWmService.mDisplayFreezeTime);
Slog.w(TAG, "Failed to report 'resized' to " + this + " due to " + e);
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
4.3 根据新的configuration当前处于resume的且需要进行relaunch的activity进行relaunch
继续屏幕旋转流程,接下来因为新的Configuration,将会去更新ActivityRecord的可见性,将会对每一个RootTask最顶部处于可运行状态的ActivityRecord进行可见性检查,一路调用到ActivityRecord的ensureActivityConfiguration方法,堆栈如下:
ActivityRecord.java
boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
boolean ignoreVisibility, boolean isRequestedOrientationChanged) {
final Task rootTask = getRootTask();
.....
// Figure out how to handle the changes between the configurations.
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Checking to restart %s: changed=0x%s, "
+ "handles=0x%s, mLastReportedConfiguration=%s", info.name,
Integer.toHexString(changes), Integer.toHexString(info.getRealConfigChanged()),
mLastReportedConfiguration);
// 1.根据清单文件的配置判断是不是走重启逻辑
if (shouldRelaunchLocked(changes, mTmpConfig) || forceNewConfig) {
// Aha, the activity isn't handling the change, so DIE DIE DIE.
configChangeFlags |= changes;
//activityReocrd的冻结 和之前的 wms屏幕的冻结不一样的
// mAppsFreezingScreen++
// 2. 对当前的ActivityRecord进行冻结操作
startFreezingScreenLocked(globalChanges);
forceNewConfig = false;
// Do not preserve window if it is freezing screen because the original window won't be
// able to update drawn state that causes freeze timeout.
preserveWindow &= isResizeOnlyChange(changes) && !mFreezingScreen;
final boolean hasResizeChange = hasResizeChange(changes & ~info.getRealConfigChanged());
if (hasResizeChange) {
final boolean isDragResizing = task.isDragResizing();
mRelaunchReason = isDragResizing ? RELAUNCH_REASON_FREE_RESIZE
: RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
} else {
mRelaunchReason = RELAUNCH_REASON_NONE;
}
if (isRequestedOrientationChanged) {
mLetterboxUiController.setRelauchingAfterRequestedOrientationChanged(true);
}
if (mState == PAUSING) {
// A little annoying: we are waiting for this activity to finish pausing. Let's not
// do anything now, but just flag that it needs to be restarted when done pausing.
ProtoLog.v(WM_DEBUG_CONFIGURATION,
"Config is skipping already pausing %s", this);
deferRelaunchUntilPaused = true;
preserveWindowOnDeferredRelaunch = preserveWindow;
return true;
} else {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Config is relaunching %s",
this);
if (!mVisibleRequested) {
ProtoLog.v(WM_DEBUG_STATES, "Config is relaunching invisible "
+ "activity %s called by %s", this, Debug.getCallers(4));
}
// 3. 向需要进行relaunch的Activity发送一个Relaunch类型的生命周期事务
relaunchActivityLocked(preserveWindow);
}
// All done... tell the caller we weren't able to keep this activity around.
return false;
}
.....
}
接下来有三个重要操作:
- 根据Activity的配置文件判断当前ActivityRecord对应的Activity是否需要进行reluanch
- 如果对应的Activity需要relaunch则调用startFreezingScreenLocked对ActivityRecord进行冻结
- 向需要进行relaunch的Activity发送一个Relaunch类型的生命周期事务,app端对应的Activity在接受到reluanch的生命周期事务后将进行reluanch操作。
4.3.1 判断对应的Activity对新的configuration是否需要relaunch
ActivityRecord.java
/**
* When assessing a configuration change, decide if the changes flags and the new configurations
* should cause the Activity to relaunch.
*
* @param changes the changes due to the given configuration.
* @param changesConfig the configuration that was used to calculate the given changes via a
* call to getConfigurationChanges.
*/
private boolean shouldRelaunchLocked(int changes, Configuration changesConfig) {
int configChanged = info.getRealConfigChanged();
boolean onlyVrUiModeChanged = onlyVrUiModeChanged(changes, changesConfig);
// Override for apps targeting pre-O sdks
// If a device is in VR mode, and we're transitioning into VR ui mode, add ignore ui mode
// to the config change.
// For O and later, apps will be required to add configChanges="uimode" to their manifest.
if (info.applicationInfo.targetSdkVersion < O
&& requestedVrComponent != null
&& onlyVrUiModeChanged) {
configChanged |= CONFIG_UI_MODE;
}
return (changes&(~configChanged)) != 0;
}
4.3.2 对需要relaunch的activity对应的ActivityRecord进行冻结操作
对ActivityRecord的冻结操作: 需要注意的是该冻结只是针对ActivityRecord进行冻结,和上文提到的屏幕冻结不是一个概念,ActivityRecord的冻结没有什么特殊的操作,更多只是设置一些变量,将该ActivityRecord设置标为冻结状态。
- mFreezingScreen = true,标记该ActivityRecord正处于冻结状态
- 将WindowManagerService的mAppsFreezingScreen + 1,也就是说屏幕旋转过程中,每有一个activiy进行relaunch就会使得mAppsFreezingScreen + 1,需要注意的是mAppsFreezingScree是判断当前屏幕是否能够解冻的重要依据之一
- 随后添加一个Activity冻结超时回调,以避免Activity进行relaunch操作耗时过久引起的屏幕不能解冻。
void startFreezingScreenLocked(int configChanges) {
startFreezingScreenLocked(app, configChanges);
}
void startFreezingScreenLocked(WindowProcessController app, int configChanges) {
// 看看这个activity 是不是 有进程,且没有crash 且有相应
if (mayFreezeScreenLocked(app)) {
if (getParent() == null) {
Slog.w(TAG_WM,
"Attempted to freeze screen with non-existing app token: " + token);
return;
}
// Window configuration changes only effect windows, so don't require a screen freeze.
int freezableConfigChanges = configChanges & ~(CONFIG_WINDOW_CONFIGURATION);
if (freezableConfigChanges == 0 && okToDisplay()) {
ProtoLog.v(WM_DEBUG_ORIENTATION, "Skipping set freeze of %s", token);
return;
}
// 开始冻结ActvityRecord
startFreezingScreen();
}
}
void startFreezingScreen() {
startFreezingScreen(ROTATION_UNDEFINED /* overrideOriginalDisplayRotation */);
}
void startFreezingScreen(int overrideOriginalDisplayRotation) {
.....
ProtoLog.i(WM_DEBUG_ORIENTATION,
"Set freezing of %s: visible=%b freezing=%b visibleRequested=%b. %s",
token, isVisible(), mFreezingScreen, mVisibleRequested,
new RuntimeException().fillInStackTrace());
....
final boolean forceRotation = overrideOriginalDisplayRotation != ROTATION_UNDEFINED;
if (!mFreezingScreen) {
// 标记该ActivityRecord正处于冻结状态
mFreezingScreen = true;
mWmService.registerAppFreezeListener(this);
// mAppsFreezingScreen + 1
// 重要:将WindowManagerService的mAppsFreezingScreen + 1;
mWmService.mAppsFreezingScreen++;
if (mWmService.mAppsFreezingScreen == 1) {
if (forceRotation) {
// Make sure normal rotation animation will be applied.
mDisplayContent.getDisplayRotation().cancelSeamlessRotation();
}
// 屏幕已经冻结,startFreezingDisplay中会直接返回
mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */,
mDisplayContent, overrideOriginalDisplayRotation);
// 向handler中添加一个APP_FREEZE_TIMEOUT超时消息
mWmService.mH.removeMessages(H.APP_FREEZE_TIMEOUT);
mWmService.mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 2000);
}
}
......
}
4.3.2 向需要进行relaunch的Activity发送一个Relaunch类型的生命周期事务
根据情况构造一个relaunch类型的生命周期事务,随后将该生命周期事务发送给App端对应的Activity,Activity在接收到该事务后,随即进行Relaunch操作。
void relaunchActivityLocked(boolean preserveWindow) {
// 打印相关的eventlog
if (andResume) {
EventLogTags.writeWmRelaunchResumeActivity(mUserId, System.identityHashCode(this),
task.mTaskId, shortComponentName);
} else {
EventLogTags.writeWmRelaunchActivity(mUserId, System.identityHashCode(this),
task.mTaskId, shortComponentName);
}
......
try {
ProtoLog.i(WM_DEBUG_STATES, "Moving to %s Relaunching %s callers=%s" ,
(andResume ? "RESUMED" : "PAUSED"), this, Debug.getCallers(6));
forceNewConfig = false;
// 重置ActiivtyRecord的一些标记
startRelaunching();
// 生成生命周期事务
final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(pendingResults,
pendingNewIntents, configChangeFlags,
new MergedConfiguration(getProcessGlobalConfiguration(),
getMergedOverrideConfiguration()),
preserveWindow);
final ActivityLifecycleItem lifecycleItem;
if (andResume) {
lifecycleItem = ResumeActivityItem.obtain(isTransitionForward(),
shouldSendCompatFakeFocus());
} else {
lifecycleItem = PauseActivityItem.obtain();
}
final ClientTransaction transaction = ClientTransaction.obtain(app.getThread(), token);
transaction.addCallback(callbackItem);
transaction.setLifecycleStateRequest(lifecycleItem);
//将reluanch的生命周期事务发送给app端对应的Activity
mAtmService.getLifecycleManager().scheduleTransaction(transaction);
......
}
至此,由屏幕旋转触发的屏幕冻结和新的configuration分发流程依旧走完了,此时屏幕处于冻结状态,当前屏幕上需要重绘的窗口正在app端重新绘制,需要进行Relaunch的Activity也正在App端进行Reluanch,一切都在屏幕冻结的截图后面默默进行,静待时机成熟后结束冻屏开启旋转屏幕的动画
5. 等待resize的窗口完成重绘,relaunch的activity完成relaunch
屏幕解冻依赖两个重要依据:
- RootWindowContainer.mOrientationChangeComplete
- WindowManagerService.mAppsFreezingScreen
首先简单了解下类WindowAnimator,该在system_server端的一个单例,该类在android11之前的版本里面可能用于控制android的窗口动画,但在android12及其之后的版本里,系统端的一些动画和WindowAnimator已经没有关系了,WindowAnimator主要作用就是检查并设置wms的一些关键的状态信息,收集窗口Surface的一些信息,并提交给sf。
```java
/**
* Singleton class that carries out the animations and Surface operations in a separate task
* on behalf of WindowManagerService.
*/
public class WindowAnimator {
....
}
其他类可以通过调用WindowAnimator.scheduleAnimation() 来向handler中添加一个mAnimationFrameCallback回调,最终执行animate,animate()是一个关键的方法,在屏幕旋转过程中执行该方法作用如下:
- 默认先将RootWindowContainer.mOrientationChangeComplete设置为true
- 遍历所有窗口,检查需要重绘的窗口是否已经完成绘制,如果有窗口未完成绘制就将 RootWindowContainer.mOrientationChangeComplete设置为false
- 检查reluanch的activity是否已经完成reluanch
需要了解的是在system_server端调用WindowAnimator.scheduleAnimation的位置非常多,基本上wms端有点点风吹草动都会调用到scheduleAnimation,所以可以这么认为在窗口每次进行大布局前都会先调用到scheduleAnimation,进而一步调用到WindowAnimator.animate().
WindowAnimator.java
/**
* Singleton class that carries out the animations and Surface operations in a separate task
* on behalf of WindowManagerService.
*/
public class WindowAnimator {
.....
private boolean mAnimationFrameCallbackScheduled;
.....
WindowAnimator(final WindowManagerService service) {
mService = service;
mContext = service.mContext;
mPolicy = service.mPolicy;
mTransaction = service.mTransactionFactory.get();
service.mAnimationHandler.runWithScissors(
() -> mChoreographer = Choreographer.getSfInstance(), 0 /* timeout */);
// 回调本身
mAnimationFrameCallback = frameTimeNs -> {
synchronized (mService.mGlobalLock) {
mAnimationFrameCallbackScheduled = false;
final long vsyncId = mChoreographer.getVsyncId();
// 执行animate
animate(frameTimeNs, vsyncId);
if (mNotifyWhenNoAnimation && !mLastRootAnimating) {
mService.mGlobalLock.notifyAll();
}
}
};
}
......
// 申请宁执行一次mAnimationFrameCallback
void scheduleAnimation() {
if (!mAnimationFrameCallbackScheduled) {
mAnimationFrameCallbackScheduled = true;
mChoreographer.postFrameCallback(mAnimationFrameCallback);
}
}
// 取消mAnimationFrameCallback的回调
private void cancelAnimation() {
if (mAnimationFrameCallbackScheduled) {
mAnimationFrameCallbackScheduled = false;
mChoreographer.removeFrameCallback(mAnimationFrameCallback);
}
}
.....
private void animate(long frameTimeNs, long vsyncId) {
if (!mInitialized) {
return;
}
// Schedule next frame already such that back-pressure happens continuously.
scheduleAnimation();
final RootWindowContainer root = mService.mRoot;
mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
mBulkUpdateParams = 0;
// 1. 默认先将RootWindowContainer.mOrientationChangeComplete 设置为true
root.mOrientationChangeComplete = true;
if (DEBUG_WINDOW_TRACE) {
Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
}
ProtoLog.i(WM_SHOW_TRANSACTIONS, ">>> OPEN TRANSACTION animate");
mService.openSurfaceTransaction();
try {
// Remove all deferred displays, tasks, and activities.
root.handleCompleteDeferredRemoval();
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 = root.getDisplayContent(displayId);
// Update animations of all applications, including those associated with
// exiting/removed apps.
dc.updateWindowsForAnimator();
// 2. 遍历所有窗口,检查需要重绘的窗口是否已经完成绘制
dc.prepareSurfaces();
}
for (int i = 0; i < numDisplays; i++) {
final int displayId = mDisplayContentsAnimators.keyAt(i);
final DisplayContent dc = root.getDisplayContent(displayId);
// 3. 检查reluanch的activity是否已经完成reluanch
dc.checkAppWindowsReadyToShow();
if (accessibilityController.hasCallbacks()) {
accessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId,
mTransaction);
}
}
cancelAnimation();
if (mService.mWatermark != null) {
mService.mWatermark.drawIfNeeded();
}
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
}
.......
}
/
}
继续分析WindowAnimator.animate:
5.1 默认先将RootWindowContainer.mOrientationChangeComplete设置为true
在animate函数中首先将root.mOrientationChangeComplete设置为true,但这并不意味着屏幕旋转已经完成,因为接下来的过程还会对该值进行修改。
WindowAnimator.java
root.mOrientationChangeComplete = true;
5.2 遍历所有窗口,检查需要重绘的窗口是否已经完成绘制
调用DisplayContent.prepareSurfaces()方法,该方法将会从DisplacyContent出发, 递归调用DisplacyContent所有子节点的prepareSurfaces方法,最终会调用到每一个WindowState的prepareSurfaces()方法,随后进一步调用每一个WindowState对应的WindowStateAnimator的prepareSurfaceLocked方法。
WindowState.java
@Override
void prepareSurfaces() {
mIsDimming = false;
applyDims();
updateSurfacePositionNonOrganized();
// Send information to SurfaceFlinger about the priority of the current window.
updateFrameRateSelectionPriorityIfNeeded();
updateScaleIfNeeded();
// 进一步调用WindowStateAnimator
mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
super.prepareSurfaces();
}
在WindowStateAnimator的prepareSurfaceLocked方法中,首先通过getOrientationChanging获取当前的窗口是否正处于方向旋转,上文中已经提到 需要重新绘制的窗口会被调用到WindowState.setOrientationChanging(true ),故需要重新绘制的窗口的getOrientationChanging返回true。
在上文中提到需要重绘的窗口状态会被设置为DRAW_PENDING,即在等待完成,一个窗口从创建到真正的在屏幕上显示一般会按照顺序经历如下几个状态
- NO_SURFACE:窗口初创还没有surface
- DRAW_PENDING:这是在Surface 创建之后但在窗口绘制完成之前设置的。在此期间,Surface 是隐藏的
- COMMIT_DRAW_PENDING:这是在窗口第一次绘制完成之后但在其 Surface 显示之前设置的。Surface 将在下一次布局运行时显示
- READY_TO_SHOW:这是在窗口的绘制已提交之后但其 Surface 实际显示之前设置的。它用于延迟显示 Surface,直到一个令牌中的所有窗口都准备好显示为止
- HAS_DRAWN:在窗口第一次显示在屏幕上时设置,通常处于该状态的窗口就可以被显示出来了
w.isDrawn()就是判断当前窗口的状态是否正处于 READY_TO_SHOW 或 HAS_DRAWN, 如果旋转过程中需要重新绘制的状态意味着该窗口正处于 DRAW_PENDING 或者COMMIT_DRAW_PENDING,即该窗口还未完成绘制或虽然已经完成绘制但还未经历过一次布局,因为只要经历一次窗口大布局处于COMMIT_DRAW_PENDING状态的窗口就可以变为下一个状态,主要是窗口是否完成绘制影响w.isDrawn()的结果
shouldSyncRotationChange()在上文中有提到过,shouldSyncRotationChange() 用于判断当前的窗口需要等待同步方向发生变化,除了 状态栏, 导航栏,返回手势栏等系统窗口,一般的窗口都返回true,即需要等待窗口因为屏幕方向改变引起的重新绘制。
如果w.getOrientationChanging(), !w.isDrawn(), w.mDisplayContent.shouldSyncRotationChange(w) 均为true,就表明该窗口在屏幕旋转阶段需要重绘,且窗口重绘未完成,且屏幕旋转需要等待该窗口绘制完成,如果在DisplayContent.prepareSurfaces()阶段只要有一个窗口满足以上条件,则又将RootWindowContainer.mOrientationChangeComplete设置为false,意味着转屏流程还未完,同时将该窗口记录在mLastWindowFreezeSource中,打印日志用,用于分析时那个窗口重新绘制过程过长影响到了屏幕解冻。
如果窗口重绘完成了,则会调用窗口的 w.setOrientationChanging(false);,标记该窗口已经完成重绘,不再处于旋转状态了。
WindowStateAnimator.java
void prepareSurfaceLocked(SurfaceControl.Transaction t) {
final WindowState w = mWin;
.....
// 1. 判断当前该窗口是否正处于方向旋转
if (w.getOrientationChanging()) {
// 2. 判断当前窗口状态不处于 (READY_TO_SHOW || == HAS_DRAWN)
if (!w.isDrawn()) {
// 3. 判断当前窗口 是否需要同步方法改变
if (w.mDisplayContent.shouldSyncRotationChange(w)) {
// 4. 需要重新绘制的窗口还没有完成重新绘制
w.mWmService.mRoot.mOrientationChangeComplete = false;
// 记录最后一个 没有完成重新绘制的窗口
mAnimator.mLastWindowFreezeSource = w;
}
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Orientation continue waiting for draw in %s", w);
} else {
// 意味着当前该需要重新绘制的窗口 已经完成了重新绘制
w.setOrientationChanging(false);
ProtoLog.v(WM_DEBUG_ORIENTATION, "Orientation change complete in %s", w);
}
}
}
还需要注意的是DisplayContent.prepareSurfaces()不仅仅只是在WindowAnimator.animate()会被调用,在窗口大布局的后期也会调用。
5.3 检查reluanch的activity是否已经完成reluanch
WindowAnimator.animate()总还会调用到DisplaContent.checkAppWindowsReadyToShow(),checkAppWindowsReadyToShow是窗口各个层级的父类WindowContainer的方法,DisplaContent调用该方法会递归调用到各个子节点的checkAppWindowsReadyToShow,其中ActivityRecord对父类WindowContainer的checkAppWindowsReadyToShow又重写,即调用DisplaContent.checkAppWindowsReadyToShow将会递归调用到当前DisplaContent下的所有Activity的checkAppWindowsReadyToShow:
-
如果ActivityRecord的allDrawn表明该ActivityRecord下的所有窗口状态是否已经是READY_TO_SHOW 或者 HAS_DRAWN,allDrawn == fasle意味着该ActivityRecord下还有窗口没有绘制完成,则返回。
-
如果allDrawn == true 则意味着该ActivityRecord下所有的窗口全都绘制完成,如果该ActivityRecord之前正处于relaunch阶段,则满足allDrawn == true进一步意味着该ActivityRecord已经完成relaunch。
-
接下来进一步判断该ActivityRecord的mFreezingScreen是不是等于true,在上文中有提到过屏幕旋转的过程中需要relaunch的ActivityRecord会被冻结,且会把该ActivityRecord的mFreezingScreen设置为true。在这里就意味着在屏幕旋转阶段该Activity被relaunch,且被冻结, 因为Activity已经完成reluanch,则需要开始对ActivityRecord进行解冻,将会调用到ActivityRecord的stopFreezingScreen方法
ActicirtyRecord.java
@Override
void checkAppWindowsReadyToShow() {
if (allDrawn == mLastAllDrawn) {
return;
}
mLastAllDrawn = allDrawn;
// 没有alldraw能直接返回
if (!allDrawn) {
return;
}
// 如果此时的acticirtyRecord 正在被冻结,且已经alldrawn ,则触发activityRecord的解冻
if (mFreezingScreen) {
showAllWindowsLocked();
// 对activityRecord 进行解冻
stopFreezingScreen(false, true);
ProtoLog.i(WM_DEBUG_ORIENTATION,
"Setting mOrientationChangeComplete=true because wtoken %s "
+ "numInteresting=%d numDrawn=%d",
this, mNumInterestingWindows, mNumDrawnWindows);
// This will set mOrientationChangeComplete and cause a pass through layout.
setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER,
"checkAppWindowsReadyToShow: freezingScreen");
}
.......
}
需要reluanch的Acitivity完成relaunch,接下来调用到对应ActivityRecord的stopFreezingScreen对该ActivityRecord进行解冻:
- 将WindowManagerService.mAppsFreezingScreen--
- 将当前ActivitRecord记录在WindowManagerService.mLastFinishedFreezeSource,打印日志用,用于分析时那个Activity的relaunch过程过长影响到了屏幕解冻.
上文有提到屏幕旋转时mAppsFreezingScreen == 0,每当有一个Activity需要relaunch都会使得mAppsFreezingScreen++,根据stopFreezingScreen流程可知,每当Activity真正完成relaunch都会使得mAppsFreezingScreen--, 只要有一个需要relaunch的activity未完成reluanch则mAppsFreezingScreen > 0.
ActicirtyRecord.java
void stopFreezingScreen(boolean unfreezeSurfaceNow, boolean force) {
if (!mFreezingScreen) {
return;
}
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Clear freezing of %s force=%b", this, force);
final int count = mChildren.size();
boolean unfrozeWindows = false;
for (int i = 0; i < count; i++) {
final WindowState w = mChildren.get(i);
unfrozeWindows |= w.onStopFreezingScreen();
}
if (force || unfrozeWindows) {
ProtoLog.v(WM_DEBUG_ORIENTATION, "No longer freezing: %s", this);
mFreezingScreen = false;
mWmService.unregisterAppFreezeListener(this);
// 重要,将这个数字减1
mWmService.mAppsFreezingScreen--;
// 更新 最后解冻的ActivityRecord信息
mWmService.mLastFinishedFreezeSource = this;
}
.....
}
6. 解冻条件满足,准备解冻屏幕并开始屏幕旋转动画
窗口存在性可见性等有点风吹草动都会触发窗口布局过程,在布局过程的后期会检查mOrientationChangeComplete,由上文可知影响该值主要是需要重绘的窗口是否完成重新绘制,如果所有需要重新绘制的窗口已经完成绘制该值就为true, 则首先移除移除WINDOW_FREEZE_TIMEOUT 超时类型的callback,然后再调用到WindowManagerService.stopFreezingDisplayLocked 尝试去解冻屏幕
RootWindowContainer.java
void performSurfacePlacementNoTrace() {
.....
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applySurfaceChanges");
mWmService.openSurfaceTransaction();
try {
applySurfaceChangesTransaction();
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
mWmService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (SHOW_LIGHT_TRANSACTIONS) {
Slog.i(TAG,
"<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
}
}
.....
// 屏幕正处于冻结状态的话 打印日志
if (mWmService.mDisplayFrozen) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"With display frozen, orientationChangeComplete=%b",
mOrientationChangeComplete);
}
// 1. mOrientationChangeComplete 检查窗口重绘是否完成
if (mOrientationChangeComplete) {
if (mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
mWmService.mLastFinishedFreezeSource = mLastWindowFreezeSource;
// 移除WINDOW_FREEZE_TIMEOUT 超时类型的callback
mWmService.mH.removeMessages(WINDOW_FREEZE_TIMEOUT);
}
// 尝试去解冻屏幕
mWmService.stopFreezingDisplayLocked();
}
}
如下,满足以下条件任一则当前还不能解冻,直接返回:
- waitingForConfig == true:说明还在等待遍历窗口层级树,向各个子节点分发新的configuration
- waitingForRemoteDisplayChange == ture: 说明把窗口旋转信息同步到SystemUI,SystemUI还未处理完毕
- mAppsFreezingScreen > 0: 说明还有因为屏幕旋转引起的relaunch的Activity 还未完成relaunch
- mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE
- mClientFreezingScreen == true:屏幕旋转该变量不考虑
- numOpeningApps > 0 : 屏幕旋转该变量不考虑
需要注意的是 除了不考虑的变量,像变量waitingForConfig,waitingForRemoteDisplayChange,mWindowsFreezingScreen能否满足解冻屏幕条件,都依赖system_server本地对屏幕旋转的处理流程,一般相对比较稳定,不会阻塞到屏幕解冻,当像mAppsFreezingScreen 和 numOpeningApps,前一个需要等待client端的窗口完成重新绘制,后一个依赖client端的activity完成relaunch,相对不稳定,一般要是屏幕冻结时间过长的话,重点考虑是否是由于这两个条件引起的。
WindowManagerService.java
void stopFreezingDisplayLocked() {
if (!mDisplayFrozen) {
return;
}
final DisplayContent displayContent = mRoot.getDisplayContent(mFrozenDisplayId);
final int numOpeningApps;
final boolean waitingForConfig;
final boolean waitingForRemoteDisplayChange;
if (displayContent != null) {
// 该值在一般不考虑
numOpeningApps = displayContent.mOpeningApps.size();
// 在向窗口层级树上所有子节点分发完毕新的Configuration后 该值为true
waitingForConfig = displayContent.mWaitingForConfig;
// systemui完成回调后,该值就未ture
waitingForRemoteDisplayChange = displayContent.mRemoteDisplayChangeController
.isWaitingForRemoteDisplayChange();
} else {
.....
}
// 如果不满足屏幕解冻条件则返回
if (waitingForConfig || waitingForRemoteDisplayChange || mAppsFreezingScreen > 0
|| mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE
|| mClientFreezingScreen || numOpeningApps > 0) {
ProtoLog.d(WM_DEBUG_ORIENTATION, "stopFreezingDisplayLocked: Returning "
+ "waitingForConfig=%b, waitingForRemoteDisplayChange=%b, "
+ "mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, "
+ "mClientFreezingScreen=%b, mOpeningApps.size()=%d",
waitingForConfig, waitingForRemoteDisplayChange,
mAppsFreezingScreen, mWindowsFreezingScreen,
mClientFreezingScreen, numOpeningApps);
return;
}
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.doStopFreezingDisplayLocked-"
+ mLastFinishedFreezeSource);
// 以上条件 通过 ,就说明 可以进行屏幕解冻了!!
doStopFreezingDisplayLocked(displayContent);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
当解冻的条件满足后就可以开始对屏幕进行解冻了,调用到WindowManagerService.doStopFreezingDisplayLocked():
首先调用thawInputDispatchingLw解冻输入,输入事件就又可以正常分发了,随即记录当前的时间作为解冻时间,并打印,随后移除APP_FREEZE_TIMEOUT 和 CLIENT_FREEZE_TIMEOUT类型的超时回调,随即调用到冻屏阶段创建的ScreenRotationAnimation.dismiss() 开启屏幕旋转动画。
WindowManagerService.java
private void doStopFreezingDisplayLocked(DisplayContent displayContent) {
ProtoLog.d(WM_DEBUG_ORIENTATION,
"stopFreezingDisplayLocked: Unfreezing now");
......
mFrozenDisplayId = INVALID_DISPLAY;
// 编辑屏幕冻结为false
mDisplayFrozen = false;
// 1. 解冻输入
mInputManagerCallback.thawInputDispatchingLw();
// 2. 计算屏幕冻结的时间
mLastDisplayFreezeDuration = (int)(SystemClock.elapsedRealtime() - mDisplayFreezeTime);
StringBuilder sb = new StringBuilder(128);
sb.append("Screen frozen for ");
TimeUtils.formatDuration(mLastDisplayFreezeDuration, sb);
if (mLastFinishedFreezeSource != null) {
sb.append(" due to ");
sb.append(mLastFinishedFreezeSource);
}
ProtoLog.i(WM_ERROR, "%s", sb.toString());
// 3. 移除timeout
mH.removeMessages(H.APP_FREEZE_TIMEOUT);
mH.removeMessages(H.CLIENT_FREEZE_TIMEOUT);
if (PROFILE_ORIENTATION) {
Debug.stopMethodTracing();
}
boolean updateRotation = false;
ScreenRotationAnimation screenRotationAnimation = displayContent == null ? null
: displayContent.getRotationAnimation();
// screenRotationAnimation 在屏幕冻结的时候已经创建
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;
}
// 4. 开始执行dismiss动画
if (screenRotationAnimation.dismiss(mTransaction, MAX_ANIMATION_DURATION,
getTransitionAnimationScaleLocked(), displayInfo.logicalWidth,
displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
mTransaction.apply();
}
......
}
screenRotationAnimation.java
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 (!mStarted) {
return false;
}
mFinishAnimReady = true;
return true;
}
首先 deltaRotation()通过旧的屏幕角度和新的屏幕角度计算出差值delta
screenRotationAnimation.java
private boolean startAnimation(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) {
return true;
}
mStarted = true;
// Figure out how the screen has moved from the original rotation.
// 2.正式计算出delta
int delta = deltaRotation(mCurRotation, mOriginalRotation);
final boolean customAnim;
// 一般都不是定制的 都是0
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;
// 根据detal选择对应的 旋转退出动画 和 旋转进入动画
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.rotation_animation_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;
}
}
ProtoLog.d(WM_DEBUG_ORIENTATION, "Start rotation animation. customAnim=%s, "
+ "mCurRotation=%s, mOriginalRotation=%s",
customAnim, Surface.rotationToString(mCurRotation),
Surface.rotationToString(mOriginalRotation));
// 初始化 mRotateExitAnimation 截图的界面
mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
mRotateExitAnimation.restrictDuration(maxAnimationDuration);
mRotateExitAnimation.scaleCurrentDuration(animationScale);
// 初始化 mRotateEnterAnimation 新的界面
mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
mRotateEnterAnimation.restrictDuration(maxAnimationDuration);
mRotateEnterAnimation.scaleCurrentDuration(animationScale);
mAnimRunning = false;
mFinishAnimReady = false;
mFinishAnimStartTime = -1;
.....
if (customAnim) {
mSurfaceRotationAnimationController.startCustomAnimation();
} else {
// 正式的开始动画
mSurfaceRotationAnimationController.startScreenRotationAnimation();
}
return true;
}