1. Android屏幕旋转机制概述
在Android系统中,屏幕旋转功能是用户体验的重要组成部分。Android 15对屏幕旋转机制进行了多项优化和改进,主要体现在DisplayRotation类的实现上。作为WindowManagerService的核心组件之一,DisplayRotation负责管理屏幕方向变化的全生命周期。
屏幕旋转的核心流程可以概括为:
- 传感器监听阶段:通过WindowOrientationListener实时监测设备物理方向变化
- 方向决策阶段:综合考虑传感器数据、应用偏好、用户设置等因素确定最终显示方向
- 界面更新阶段:将确定的方向信息传递到显示系统,完成界面重绘
每个物理显示屏对应一个LogicalDisplay对象,而每个LogicalDisplay又包含一个DisplayContent对象。DisplayRotation作为DisplayContent的成员变量,专门处理该显示屏的旋转逻辑。这种设计使得多显示屏设备可以独立管理各自的旋转状态。
2. 方向监听初始化与配置
2.1 WindowOrientationListener初始化
DisplayRotation在构造函数中完成方向监听器的初始化:
java复制DisplayRotation(WindowManagerService service, DisplayContent displayContent,
DisplayAddress displayAddress, DisplayPolicy displayPolicy,
DisplayWindowSettings displayWindowSettings, Context context, Object lock,
@NonNull DeviceStateController deviceStateController,
@NonNull DisplayRotationCoordinator displayRotationCoordinator) {
//...
if (isDefaultDisplay) {
final Handler uiHandler = UiThread.getHandler();
mOrientationListener =
new OrientationListener(mContext, uiHandler, defaultRotation);
mOrientationListener.setCurrentRotation(mRotation);
mSettingsObserver = new SettingsObserver(uiHandler);
mSettingsObserver.observe();
if (mSupportAutoRotation && isFoldable(mContext)) {
mFoldController = new FoldController();
}
}
//...
}
关键点解析:
- 只有主显示屏(defaultDisplay)会初始化方向监听器,副屏通常固定方向
- OrientationListener是WindowOrientationListener的子类,负责实际的方向检测
- 通过UiThread的Handler确保方向变化回调在主线程处理
- 可折叠设备有特殊的FoldController处理折叠状态下的方向逻辑
2.2 传感器选择策略
WindowOrientationListener在初始化时会优先选择最合适的传感器:
java复制private WindowOrientationListener(Context context, Handler handler,
@Surface.Rotation int defaultRotation, int rate) {
//...
List<Sensor> l = mSensorManager.getSensorList(Sensor.TYPE_DEVICE_ORIENTATION);
Sensor wakeUpDeviceOrientationSensor = null;
Sensor nonWakeUpDeviceOrientationSensor = null;
// 优先选择唤醒型传感器
for (Sensor s : l) {
if (s.isWakeUpSensor()) {
wakeUpDeviceOrientationSensor = s;
} else {
nonWakeUpDeviceOrientationSensor = s;
}
}
if (wakeUpDeviceOrientationSensor != null) {
mSensor = wakeUpDeviceOrientationSensor;
} else {
mSensor = nonWakeUpDeviceOrientationSensor;
}
if (mSensor != null) {
mOrientationJudge = new OrientationSensorJudge();
}
// 没有方向传感器则回退到加速度计
if (mOrientationJudge == null) {
mSensor = mSensorManager.getDefaultSensor(USE_GRAVITY_SENSOR
? Sensor.TYPE_GRAVITY : Sensor.TYPE_ACCELEROMETER);
if (mSensor != null) {
mOrientationJudge = new AccelSensorJudge(context);
}
}
}
传感器选择优先级:
- TYPE_DEVICE_ORIENTATION(直接提供方向数据)
- 优先选择isWakeUpSensor=true的版本(熄屏状态下仍工作)
- 回退方案:TYPE_ACCELEROMETER或TYPE_GRAVITY
- 需要复杂的算法计算方向(AccelSensorJudge实现)
提示:在Android 15中,TYPE_DEVICE_ORIENTATION传感器的精度和响应速度有显著提升,这是旋转体验改善的关键
3. 方向监听状态管理
3.1 动态启用/禁用监听器
updateOrientationListenerLw方法根据系统状态智能管理监听器的启用状态:
java复制private void updateOrientationListenerLw() {
if (mOrientationListener == null || !mOrientationListener.canDetectOrientation()) {
return; // 传感器不可用
}
final boolean screenOnEarly = mDisplayPolicy.isScreenOnEarly();
final boolean awake = mDisplayPolicy.isAwake();
final boolean keyguardDrawComplete = mDisplayPolicy.isKeyguardDrawComplete();
final boolean windowManagerDrawComplete = mDisplayPolicy.isWindowManagerDrawComplete();
boolean disable = true;
// 满足以下条件时启用监听器:
// 1. 屏幕已开启
// 2. 设备处于唤醒状态或支持AOD旋转
// 3. 锁屏和窗口管理器已完成绘制
if (screenOnEarly
&& (awake || mOrientationListener.shouldStayEnabledWhileDreaming())
&& ((keyguardDrawComplete && windowManagerDrawComplete))) {
if (needSensorRunning()) {
disable = false;
if (!mOrientationListener.mEnabled) {
mOrientationListener.enable(); // 启用传感器监听
}
}
}
if (disable) {
mOrientationListener.disable(); // 禁用传感器监听
}
}
状态管理策略:
- 屏幕关闭时:强制禁用监听器以节省电量
- 屏幕开启时:
- 当前应用请求传感器方向:启用监听器
- 当前应用固定方向:禁用监听器
- 特殊场景处理:
- 息屏显示(AOD):部分设备允许旋转
- 可折叠设备:考虑折叠状态
3.2 传感器数据处理
方向传感器的数据处理有两种实现:
- OrientationSensorJudge(TYPE_DEVICE_ORIENTATION)
java复制// 直接使用传感器提供的方向值
int reportedRotation = (int) event.values[0];
- AccelSensorJudge(TYPE_ACCELEROMETER)
java复制// 需要复杂计算确定方向
if (isTiltAngleAcceptableLocked(nearestRotation, tiltAngle) &&
isOrientationAngleAcceptableLocked(nearestRotation, orientationAngle)) {
mPredictedRotation = nearestRotation;
}
关键改进点(Android 15):
- 降低了AccelSensorJudge的误判率
- 优化了方向切换的平滑过渡效果
- 添加了对快速旋转动作的响应能力
4. 屏幕方向决策逻辑
4.1 方向决策入口
rotationForOrientation方法综合各种因素确定最终显示方向:
java复制int rotationForOrientation(@ScreenOrientation int orientation,
@Surface.Rotation int lastRotation) {
// 1. 获取传感器建议方向
@Surface.Rotation
int sensorRotation = mOrientationListener != null
? mOrientationListener.getProposedRotation() // 可能为-1
: -1;
// 2. 可折叠设备特殊处理
if (mFoldController != null && mFoldController.shouldIgnoreSensorRotation()) {
sensorRotation = -1;
}
// 3. 考虑用户锁定设置
if (isFixedToUserRotation()) {
return mUserRotation;
}
// 4. 多种场景的特殊处理(车载模式、桌面模式等)
final int lidState = mDisplayPolicy.getLidState();
final int dockMode = mDisplayPolicy.getDockMode();
// ... 其他场景判断
// 5. 综合应用请求的方向和传感器数据
@Surface.Rotation
final int preferredRotation;
if (!isDefaultDisplay) {
// 副屏直接使用用户设置
preferredRotation = mUserRotation;
} else if (lidState == LID_OPEN && mLidOpenRotation >= 0) {
// 笔记本电脑开盖状态
preferredRotation = mLidOpenRotation;
}
// ... 其他条件判断
// 6. 根据应用请求的orientation调整最终方向
switch (orientation) {
case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
if (isAnyPortrait(preferredRotation)) {
return preferredRotation;
}
return mPortraitRotation;
// ... 其他case处理
}
}
决策因素优先级:
- 用户强制锁定设置
- 特殊硬件状态(如笔记本模式、车载模式)
- 应用请求的固定方向
- 传感器数据建议方向
- 默认方向
4.2 多显示屏支持
Android 15增强了多显示屏的旋转支持:
java复制if (!isDefaultDisplay) {
// 对于副屏我们忽略传感器、对接模式等,直接使用用户设置
preferredRotation = mUserRotation;
}
多显示屏处理特点:
- 主屏:完整支持自动旋转逻辑
- 副屏:
- 默认固定为用户设置方向
- 可通过API设置独立旋转策略
- 外接显示器通常不跟随旋转
5. 方向更新与界面刷新
5.1 方向信息传递流程
确定新方向后,更新流程如下:
- 更新DisplayRotation内部状态:
java复制updateRotationUnchecked > rotationForOrientation > mRotation = rotation;
- 通过DisplayContent更新DisplayInfo:
java复制// DisplayContent.java
private void setDisplayInfoOverride() {
mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId,
mDisplayInfo);
// ...
}
- LogicalDisplay接收更新:
java复制// LogicalDisplay.java
public boolean setDisplayInfoOverrideFromWindowManagerLocked(DisplayInfo info) {
if (info != null) {
if (mOverrideDisplayInfo == null) {
mOverrideDisplayInfo = new DisplayInfo(info);
} else if (!mOverrideDisplayInfo.equals(info)) {
mOverrideDisplayInfo.copyFrom(info);
}
mInfo.set(null); // 触发刷新
return true;
}
return false;
}
5.2 SurfaceFlinger投影更新
最终方向信息通过SurfaceComposerClient传递给SurfaceFlinger:
java复制// LogicalDisplay.java
public void configureDisplayLocked(SurfaceControl.Transaction t,
DisplayDevice device, boolean isBlanked) {
// ...
int orientation = Surface.ROTATION_0;
if ((displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0
|| mAlwaysRotateDisplayDeviceEnabled) {
orientation = displayInfo.rotation; // 使用逻辑屏方向
}
// 考虑物理显示屏本身的旋转
orientation = (orientation + displayDeviceInfo.rotation) % 4;
// 计算实际显示区域
boolean rotated = (orientation == Surface.ROTATION_90
|| orientation == Surface.ROTATION_270);
int physWidth = rotated ? displayDeviceInfo.height : displayDeviceInfo.width;
int physHeight = rotated ? displayDeviceInfo.width : displayDeviceInfo.height;
// 设置投影
device.setProjectionLocked(t, orientation, mTempLayerStackRect, mTempDisplayRect);
}
关键参数说明:
- FLAG_ROTATES_WITH_CONTENT:内置显示屏通常设置此标志
- displayDeviceInfo.rotation:考虑设备物理安装方向
- mTempLayerStackRect:逻辑显示区域
- mTempDisplayRect:物理显示区域
6. 常见问题与调试技巧
6.1 旋转不生效排查步骤
- 检查传感器状态:
bash复制adb shell dumpsys sensorservice
查看方向传感器是否正常注册和数据上报
- 检查WindowManager状态:
bash复制adb shell dumpsys window
查找"DisplayRotation"相关输出,确认当前旋转策略
- 检查应用设置:
bash复制adb shell dumpsys activity top
查看顶Activity的requestedOrientation
6.2 调试日志分析
Android 15新增了更详细的旋转调试日志,可通过以下命令启用:
bash复制adb shell setprop log.tag.WM_DEBUG_ORIENTATION VERBOSE
关键日志标签:
- WM_DEBUG_ORIENTATION:方向变更决策过程
- WM_DEBUG_APP_ORIENTATION:应用方向请求处理
- WM_DEBUG_DISPLAY:显示设备状态变化
6.3 开发者选项设置
- 强制启用自动旋转:
java复制// 在开发者选项中设置
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 1);
- 模拟不同传感器方向:
bash复制adb shell settings put system accelerometer_rotation 0
adb shell settings put system user_rotation 1
- 锁定特定方向:
java复制// 在Activity中设置
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
7. 性能优化实践
7.1 减少不必要的旋转
通过合理配置避免无效的界面重绘:
java复制// 在AndroidManifest.xml中声明
<activity android:name=".MyActivity"
android:screenOrientation="portrait"
android:configChanges="orientation|screenSize"/>
7.2 平滑过渡处理
Android 15优化了旋转动画的流畅度:
- 使用新的过渡动画:
java复制// 在主题中设置
<style name="AppTheme" parent="Theme.Material3.DynamicColors.DayNight">
<item name="android:windowActivityTransitions">true</item>
<item name="android:windowContentTransitions">true</item>
<item name="android:windowAllowEnterTransitionOverlap">true</item>
</style>
- 优化布局切换:
xml复制<!-- 为不同方向提供优化布局 -->
<layout android:name="@layout/activity_main"
android:screenOrientation="portrait"/>
<layout android:name="@layout/activity_main_land"
android:screenOrientation="landscape"/>
7.3 传感器采样率优化
平衡精度和功耗:
java复制// 在WindowOrientationListener构造函数中
public WindowOrientationListener(Context context, Handler handler,
@Surface.Rotation int defaultRotation, int rate) {
// rate参数控制采样率
this(context, handler, defaultRotation,
SensorManager.SENSOR_DELAY_UI); // 常用UI级别
}
可用采样率:
- SENSOR_DELAY_FASTEST:最高频率(耗电)
- SENSOR_DELAY_GAME:游戏适用
- SENSOR_DELAY_UI:界面旋转适用(推荐)
- SENSOR_DELAY_NORMAL:默认频率
8. 兼容性考虑
8.1 处理不同设备类型
java复制// 在DisplayRotation中
private int rotationForOrientation(@ScreenOrientation int orientation,
@Surface.Rotation int lastRotation) {
// 车载设备特殊处理
if (dockMode == Intent.EXTRA_DOCK_STATE_CAR
&& (carDockEnablesAccelerometer || mCarDockRotation >= 0)) {
preferredRotation = carDockEnablesAccelerometer ? sensorRotation : mCarDockRotation;
}
// 桌面模式处理
else if ((dockMode == Intent.EXTRA_DOCK_STATE_DESK
|| dockMode == Intent.EXTRA_DOCK_STATE_LE_DESK
|| dockMode == Intent.EXTRA_DOCK_STATE_HE_DESK)
&& (deskDockEnablesAccelerometer || mDeskDockRotation >= 0)) {
preferredRotation = deskDockEnablesAccelerometer ? sensorRotation : mDeskDockRotation;
}
}
8.2 可折叠设备适配
Android 15增强了可折叠设备的旋转处理:
java复制// 在DisplayRotation中
if (mSupportAutoRotation && isFoldable(mContext)) {
mFoldController = new FoldController();
}
// FoldController处理逻辑
class FoldController {
boolean shouldIgnoreSensorRotation() {
// 在特定折叠状态下忽略传感器数据
return mDeviceStateController.getFoldedState() == FOLDED;
}
}
8.3 外接显示器处理
外接显示器通常有特殊处理:
java复制// 在LogicalDisplay.configureDisplayLocked中
if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.type == Display.TYPE_EXTERNAL
&& displayDeviceInfo.xDpi > 0 && displayDeviceInfo.yDpi > 0) {
// 处理外接显示器DPI不一致的情况
}