忽然有一天,我想要做一件事:去代码中去验证那些曾经被“灌输”的理论。
-- 服装学院的IT男【Android 13源码分析】
黑屏的场景为:锁屏解锁,屏幕旋转,开机流程。
最近也是遇到一个旋转黑屏的问题,借此梳理一下屏幕旋转的主要流程。
旋转很多窗口要重绘,这个过程如果没有处理好,就会出现黑屏。为了避免这一现象,在执行旋转操作之前,会把当前屏幕几个图,然后创建一个层级很高的图层,直接挂载到 DisplayContent 下面,这样下面的图层无论怎么操作,都会被挡住,也不会出现黑屏的情况。
google 的理想设计是这样,但是实际上旋转还是会有很多黑屏问题,根据个人经验,一般优先考虑是不是这个截图的图层或者动旋转动画移除的时机不对,底下的内容还没绘制完成就移除了所以导致黑屏。
关于黑屏问题,具体情况具体分析,但是需要对旋转的主流程有一定了解。
个人将旋转流程分为以下4步:
-
- 传感器发现设备旋转,会回调通知到上层(Framework)
-
- Framework 收到旋转回调会做以下处理
- 2.1 计算旋转角度
- 2.2 为了避免黑屏等异常显示,会创建一个层级很高的图层在屏幕上面,内容是旋转前的截图
-
- 计算出一个新的 Configuration ,做以下处理
- 3.1 并且分发到各个模块,各个模块做相应处理
- 3.2 可见性处理
-
- 一切准备就绪开始执行旋转动画相关
其中第一部分是驱动底层相关,暂时不管, 主要按序分析后面3个步骤。
1. 概览
本篇先看 Framework 收到旋转回调的相关处理,主要调用链如下:
WindowOrientationListener$OrientationSensorJudge::onSensorChanged
WindowOrientationListener$OrientationSensorJudge::finalizeRotation
DisplayRotation$OrientationListener::onProposedRotationChanged -- 判断用户是否锁定旋转
WindowManagerService::updateRotation
WindowManagerService::updateRotationUnchecked
DisplayRotation::updateRotationUnchecked
DisplayRotation::rotationForOrientation -- 计算目标角度
DisplayRotation::prepareNormalRotationAnimation
WindowManagerService::startFreezingDisplay
WindowManagerService::doStartFreezingDisplay
ScreenRotationAnimation::init -- 创建旋转动画 RotationLayer
DisplayContent.setRotationAnimation -- 旋转画保存到变量
DisplayRotation::startRemoteRotation -- 开始远端旋转 (这个是干啥)
mRemoteRotationCallback::continueRotateDisplay -- 主流程,回调处理
这个阶段的调用链就是这样,这个过程一般没什么问题,可以打开 WM_DEBUG_ORIENTATION 日志来跟踪定位哪一步。
下面也会看代码的执行,可以留意以下几点:
-
- 在 DisplayRotation::onProposedRotationChanged 方法如果用户下来状态设置忽略旋转,则会在这进行判断
-
- WindowManagerService::updateRotationUnchecked 对每个屏幕执行操作是会有 trace
-
- DisplayRotation::rotationForOrientation 这个方法很重要,内部有一套规则来计算旋转的最终角度,比如用户锁定旋转或Activity限制了竖屏,那么横竖切换的时候,最终角度也还是竖屏
-
- WindowManagerService::doStartFreezingDisplay 会做3件事;
- 4.1 冻屏
- 4.2 创建旋转动画
- 4.3 把旋转画保存 DisplayContent 下的 mScreenRotationAnimation
2. 代码流程
屏幕旋转的触发肯定是来着传感器的,上层接收事件是在 WindowOrientationListener::onSensorChanged 回调中
# WindowOrientationListener$OrientationSensorJudge
private int mDesiredRotation = -1;
private int mLastRotationResolution = ROTATION_UNSET;
private long mLastRotationResolutionTimeStamp;
@Override
public void onSensorChanged(SensorEvent event) {
int reportedRotation = (int) event.values[0];
if (reportedRotation < 0 || reportedRotation > 3) {
return;
}
......
if (isRotationResolverEnabled()) {
......
} else {
finalizeRotation(reportedRotation);
}
}
private void finalizeRotation(int reportedRotation) {
// 新的选择角度
int newRotation;
synchronized (mLock) {
mDesiredRotation = reportedRotation;
// 根据规则计算出新的选择角度
newRotation = evaluateRotationChangeLocked();
}
// 检查计算得到的新旋转角度 newRotation 是否有效(大于等于 0)
if (newRotation >= 0) {
mLastRotationResolution = newRotation;
mLastRotationResolutionTimeStamp = SystemClock.uptimeMillis();
onProposedRotationChanged(newRotation);
}
}
onProposedRotationChanged 是个接口方法,真正的实现在 DisplayRotation::onProposedRotationChanged
# DisplayRotation
@Override
public void onProposedRotationChanged(int rotation) {
// 屏幕旋转比较重要的一个日志,传感器回调来的旋转角度
ProtoLog.v(WM_DEBUG_ORIENTATION, "onProposedRotationChanged, rotation=%d", rotation);
// Send interaction power boost to improve redraw performance.
mService.mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);
if (isRotationChoicePossible(mCurrentAppOrientation)) {
// 下拉状态栏如果锁定了选中则走这
final boolean isValid = isValidRotationChoice(rotation);
sendProposedRotationChangeToStatusBarInternal(rotation, isValid);
} else {
// 正常走这
mService.updateRotation(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
}
}
用户不手动锁定旋转的话,走 else 逻辑。
# WindowManagerService
@Override
public void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout) {
updateRotationUnchecked(alwaysSendConfiguration, forceRelayout);
}
private void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
// log
ProtoLog.v(WM_DEBUG_ORIENTATION, "updateRotationUnchecked:"
+ " alwaysSendConfiguration=%b forceRelayout=%b",
alwaysSendConfiguration, forceRelayout);
// Trace
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation");
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
boolean layoutNeeded = false;
final int displayCount = mRoot.mChildren.size();
// 遍历屏幕
for (int i = 0; i < displayCount; ++i) {
// Trace
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation: display");
// 重点* 屏幕旋转
final boolean rotationChanged = displayContent.updateRotationUnchecked();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
......// 判断是否需要 layout
}
// 看是否需要layout
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);
}
}
逻辑来到具体的屏幕,正常情况也就一个屏幕。
# DisplayContent
private final DisplayRotation mDisplayRotation;
boolean updateRotationUnchecked() {
// 传递false
return mDisplayRotation.updateRotationUnchecked(false /* forceUpdate */);
}
# DisplayRotation
// 当前屏幕的旋转角度
private int mRotation;
boolean updateRotationUnchecked(boolean forceUpdate) {
final int displayId = mDisplayContent.getDisplayId();
......
final int oldRotation = mRotation;
final int lastOrientation = mLastOrientation;
// 根据之前的和现在的得到最终计算出的rotation (比如屏幕锁定,等等条件)
final int rotation = rotationForOrientation(lastOrientation, oldRotation);
// 输出2个关键log
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);
// 新旧角度一样,则不处理 (比如应用限制了竖屏)
if (oldRotation == rotation) {
// No change.
return false;
}
......
// 继续处理的log
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;
}
// 重点* 更新屏幕角度
mRotation = rotation;
// 需要layout
mDisplayContent.setLayoutNeeded();
......
// 是否无缝连接(正常false)
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;
}
这个方法还是很多的, 具体旋转如果有问题,可以根据这边的日志打印来定位, 最重要的就是 rotationForOrientation 方法中定义了一套计算规则,来计算新的选择角度。 然后会根据新的角度进行后续代码执行,比如当前显示的 Activity 如果显示了竖屏,竖屏转横屏,角度也一样,就不会执行后面的逻辑了。
-
- 计算旋转角度,设置给 mRotation, 这边变量还是很重要的,最新的屏幕角度保存在这,后面肯定要用的
-
- 准备动画
-
- 开始远端旋转
2.1 准备动画 prepareNormalRotationAnimation
# DisplayRotation
void prepareNormalRotationAnimation() {
cancelSeamlessRotation();
final RotationAnimationPair anim = selectRotationAnimation();
// 根据debug,这里 anim 的2个值都是 0
mService.startFreezingDisplay(anim.mExit, anim.mEnter, mDisplayContent);
}
# WindowManagerService
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) {
if (mDisplayFrozen || displayContent.getDisplayRotation().isRotatingSeamlessly()) {
return;
}
if (!displayContent.isReady() || !displayContent.getDisplayPolicy().isScreenOnFully()
|| displayContent.getDisplayInfo().state == Display.STATE_OFF
|| !displayContent.okToAnimate()) {
// No need to freeze the screen before the display is ready, if the screen is off,
// or we can't currently animate.
return;
}
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.doStartFreezingDisplay");
// 冻屏
doStartFreezingDisplay(exitAnim, enterAnim, displayContent, overrideOriginalRotation);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
# WindowManagerService
private final PowerManager.WakeLock mScreenFrozenLock;
private void doStartFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent,
int overrideOriginalRotation) {
// 1. 打印冻屏日志及堆栈
ProtoLog.d(WM_DEBUG_ORIENTATION,
"startFreezingDisplayLocked: exitAnim=%d enterAnim=%d called by %s",
exitAnim, enterAnim, Debug.getCallers(8));
// 2. 开始冻屏
mScreenFrozenLock.acquire();
......
// App事务
if (displayContent.mAppTransition.isTransitionSet()) {
displayContent.mAppTransition.freeze();
}
......
displayContent.updateDisplayInfo();
// 由于没有更新 DisplayInfo 的配置,所以这个时候角度还是原来的
final int originalRotation = overrideOriginalRotation != ROTATION_UNDEFINED
? overrideOriginalRotation
: displayContent.getDisplayInfo().rotation;
// 3.4. 创建并保存旋转动画
displayContent.setRotationAnimation(new ScreenRotationAnimation(displayContent,
originalRotation));
/// M: MTK Power: Enable rotation boost
mPowerHalManager.setRotationBoost(true);
}
-
- 打印了堆栈,可以通过这里看是哪边触发的选择动画创建
-
- 请求冻屏
-
- 创建旋转动画
-
- 将旋转动画设置到 DisplayContent 下的 mScreenRotationAnimation 变量
2.2 创建旋转动画
注意是2个参数的,是server.wm目录下的,不是 shell 路径下的那个 ScreenRotationAnimation 。 这个 ScreenRotationAnimation 也是控制动画开始和结束回调的,不是真正执行动画的地方。
# ScreenRotationAnimation
private final int mOriginalRotation;
ScreenRotationAnimation(DisplayContent displayContent, @Surface.Rotation int originalRotation) {
......
// 从displayInfo里拿的角度,目前和传进来的是一样的
final int realOriginalRotation = displayInfo.rotation;
// 传递过来的其实还是之前的角度
mOriginalRotation = originalRotation;
// 2个值其实是一样的,所以计算结果是没有差异的
final int delta = deltaRotation(originalRotation, realOriginalRotation);
......
// 拿到一个事务
final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
try {
// 截图相关
SurfaceControl.LayerCaptureArgs args =
new SurfaceControl.LayerCaptureArgs.Builder(displayContent.getSurfaceControl())
.setCaptureSecureLayers(true)
.setAllowProtected(true)
.setSourceCrop(new Rect(0, 0, width, height))
// Exclude rounded corner overlay from screenshot buffer. Rounded
// corner overlay windows are un-rotated during rotation animation
// for a seamless transition.
.setExcludeLayers(displayContent.findRoundedCornerOverlays())
.build();
// 截图的buff
SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
SurfaceControl.captureLayers(args);
if (screenshotBuffer == null) {
Slog.w(TAG, "Unable to take screenshot of display " + displayId);
return;
}
......
// 重点* 1. 创建截图的图层
String name = "RotationLayer";
mScreenshotLayer = displayContent.makeOverlay()
.setName(name)
.setOpaque(true)
.setSecure(isSecure)
.setCallsite("ScreenRotationAnimation")
.setBLASTLayer()
.build();
......
// 获取截图buff
GraphicBuffer buffer = GraphicBuffer.createFromHardwareBuffer(
screenshotBuffer.getHardwareBuffer());
......
t.setAlpha(mBackColorSurface, 1);
t.setBuffer(mScreenshotLayer, buffer);// 设置截图buff给截图的图层
t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());
t.show(mScreenshotLayer);
t.show(mBackColorSurface);
} 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) {
// 重点* 2. 设置图层位置
setRotation(t, realOriginalRotation);
} else {
......
}
t.apply();
}
这里主要做了2件事:
-
- 创建一个截图图层挂载屏幕下。
主要就是创建了一个截图的 buff ,和屏幕旋转动画的图层,而且是在 DisplayContent 最上面, 用当前屏幕的截图做内容做旋转,这样底下的 Activity 该重启重启,该黑屏黑屏,都不会影响用户体验。
- 2. 设置图层位置 截图的这个图层横竖屏切换的话,图层的位置也要改变的,就是这个 setRotation 处理的。 不过这个时候的 2 个变量 originalRotation 和 realOriginalRotation ,根据分析,值都是从 DisplayInfo 取的,而这个时候还没有修改配置,所以值是一样的。
但是后面 DisplayInfo 下的信息改变后在执行这个方法时,就会有实际的处理了, 可以先看一下这个方法。
2.2.1 setRotation
首先要介绍一下这个 setRotation 是干啥的。
手机屏幕上显示的内容是通过一个 摄像机(Camera)来捕捉的,如果屏幕旋转了,但是 Surface 就需要移动坐标到摄像头下,这样才会显示到屏幕上。
这个方法做的就是对截图的图层Surface进行坐标移动。
不过这一次执行到这个方法时 setRotation 其实没做什么,因为刚提到 DisplayInfo 的角度还没更新。 不过可以先看一下这个方法,因为后面还会再看。
# ScreenRotationAnimation
private void setRotation(SurfaceControl.Transaction t) {
// Compute the transformation matrix that must be applied
// to the snapshot to make it stay in the same original position
// with the current screen rotation.
// 根据开始和结束的角度,计算出 delta
int delta = deltaRotation(mEndRotation, mStartRotation);
// 根据 delta 计算surface的矩阵变化(旋转肯定要移动Surface)
createRotationMatrix(delta, mStartWidth, mStartHeight, mSnapshotInitialMatrix);
setRotationTransform(t, mSnapshotInitialMatrix);
}
// surface举证设置
private void setRotationTransform(SurfaceControl.Transaction t, Matrix matrix) {
if (mScreenshotLayer == null) {
return;
}
matrix.getValues(mTmpFloats);
float x = mTmpFloats[Matrix.MTRANS_X];
float y = mTmpFloats[Matrix.MTRANS_Y];
t.setPosition(mScreenshotLayer, x, y);
t.setMatrix(mScreenshotLayer,
mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
t.setAlpha(mScreenshotLayer, (float) 1.0);
t.show(mScreenshotLayer);
}
不过目前执行到这的时候这里 deltaRotation 穿进去的2个参数是一样的,所以计算出来是 0 。
# RotationUtils
/** @return the rotation needed to rotate from oldRotation to newRotation. */
public static int deltaRotation(int oldRotation, int newRotation) {
int delta = newRotation - oldRotation;
if (delta < 0) delta += 4;
return delta;
}
就是根据之前和选择后的角度,计算出一个差值,然后根据这个值再计算需要对Surface的位置做计算,最终的目的就是让屏幕显示正确位置的Surface
比如竖屏转横屏,那就是 0-1 = -1 ,然后 -1+4 = 3 。
2.2.2 设置旋转动画
# DisplayContent
// 旋转动画
private ScreenRotationAnimation mScreenRotationAnimation;
public void setRotationAnimation(ScreenRotationAnimation screenRotationAnimation) {
if (mScreenRotationAnimation != null) {
mScreenRotationAnimation.kill();
}
// 赋值
mScreenRotationAnimation = screenRotationAnimation;
// Hide the windows which are not significant in rotation animation. So that the windows
// don't need to block the unfreeze time.
if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
startAsyncRotationIfNeeded();
}
}
就是把动画保存到变量中,后面看什么时候执行动画,肯定会使用这个变量。
2.3 开始远端旋转 startRemoteRotation
下一步是执行了 DisplayRotation::startRemoteRotation 也就是开始“远端”旋转,这一块的实际目的,我也没有理解。
# DisplayRotation
// 是否在等待远端旋转
private boolean mIsWaitingForRemoteRotation = false;
private void startRemoteRotation(int fromRotation, int toRotation) {
if (mService.mDisplayRotationController == null) {
return;
}
// 等待远端旋转
mIsWaitingForRemoteRotation = true;
try {
// 跨进程调用到 systemui 进程,传递了角度,以及一个回调(回调的重点)
mService.mDisplayRotationController.onRotateDisplay(mDisplayContent.getDisplayId(),
fromRotation, toRotation, mRemoteRotationCallback);
mService.mH.removeCallbacks(mDisplayRotationHandlerTimeout);
// 设置回调超时
mService.mH.postDelayed(mDisplayRotationHandlerTimeout, REMOTE_ROTATION_TIMEOUT_MS);
} catch (RemoteException e) {
// 异常则设置为 false
mIsWaitingForRemoteRotation = false;
return;
}
}
这里是个跨进程通信,“远端”其实是 systemui ,不过具体做的事情也不太理解,目前关心的主要流程还是在远端结束后,执行的 mRemoteRotationCallback 回调。
2.3.1 SystemUI处理
# DisplayChangeController
@BinderThread
private class DisplayWindowRotationControllerImpl
extends IDisplayWindowRotationController.Stub {
@Override
public void onRotateDisplay(int displayId, final int fromRotation,
final int toRotation, IDisplayWindowRotationCallback callback) {
mMainExecutor.execute(() -> {
DisplayChangeController.this.onRotateDisplay(displayId, fromRotation, toRotation,
callback);
});
}
}
private void onRotateDisplay(int displayId, final int fromRotation, final int toRotation,
IDisplayWindowRotationCallback callback) {
WindowContainerTransaction t = new WindowContainerTransaction();
// 分发屏幕旋转的事件到各个监听
dispatchOnRotateDisplay(t, displayId, fromRotation, toRotation);
try {
// 回调 system_server
callback.continueRotateDisplay(toRotation, t);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to continue rotation", e);
}
}
public void dispatchOnRotateDisplay(WindowContainerTransaction outWct, int displayId,
final int fromRotation, final int toRotation) {
for (OnDisplayChangingListener c : mRotationListener) {
android.util.Log.d("biubiubiu", " dispatchOnRotateDisplay: "+c);
c.onRotateDisplay(displayId, fromRotation, toRotation, outWct);
}
}
这里的打印如下
Line 143: 04-19 18:36:38.539 30405 30405 D biubiubiu: dispatchOnRotateDisplay: com.android.wm.shell.onehanded.OneHandedController@15a7622
Line 147: 04-19 18:36:38.540 30405 30405 D biubiubiu: dispatchOnRotateDisplay: com.android.wm.shell.splitscreen.StageCoordinator$$ExternalSyntheticLambda0@3ced8b3
Line 149: 04-19 18:36:38.540 30405 30405 D biubiubiu: dispatchOnRotateDisplay: com.android.wm.shell.bubbles.BubbleController$4@28afe70
Line 151: 04-19 18:36:38.540 30405 30405 D biubiubiu: dispatchOnRotateDisplay: com.android.wm.shell.pip.phone.PipController$$ExternalSyntheticLambda0@954a5e9
感觉也没啥,所以还是继续看 system_server 的回调是怎么处理的,回调才是主流程
2.3.2 mRemoteRotationCallback 处理
# DisplayRotation
private final IDisplayWindowRotationCallback mRemoteRotationCallback =
new IDisplayWindowRotationCallback.Stub() {
@Override
public void continueRotateDisplay(int targetRotation,
WindowContainerTransaction t) {
synchronized (mService.getWindowManagerLock()) {
mService.mH.sendMessage(PooledLambda.obtainMessage(
DisplayRotation::continueRotation, DisplayRotation.this,
targetRotation, t));
}
}
};
看样子还是在逻辑还是在 DisplayRotation::continueRotation
# DisplayRotation
private void continueRotation(int targetRotation, WindowContainerTransaction t) {
synchronized (mService.mGlobalLock) {
// 如果角度不对,或者不处理等待远端选择的状态,则返回
if (targetRotation != mRotation || !mIsWaitingForRemoteRotation) {
// Drop it, this is either coming from an outdated remote rotation; or, we've
// already moved on.
return;
}
mService.mH.removeCallbacks(mDisplayRotationHandlerTimeout);
// 设置为false
mIsWaitingForRemoteRotation = false;
......
// 延迟layout
mService.mAtmService.deferWindowLayout();
try {
// 设置新的配置
mDisplayContent.sendNewConfiguration();
if (t != null) {
mService.mAtmService.mWindowOrganizerController.applyTransaction(t);
}
} finally {
// 执行layout
mService.mAtmService.continueWindowLayout();
}
}
}
-
- 判断 mIsWaitingForRemoteRotation 的值,为 false 才进入
-
- DisplayContent::sendNewConfiguration 设置新的配置
-
- 执行一次layout(配置更新肯定要layout)
3. 总结
本篇分析结束,主要就是创建了一个图层盖在屏幕上面,这样下面的各个窗口就可以做羞羞的事,不会影响用户体验。
最后就是去 SystemUI 做了一些处理,当然最重要的还是触发回调 DisplayRotation::continueRotation
这部分逻辑一般没啥问题,打开相关的 proto 看日志就能定位哪一步执行的逻辑有问题。