尧图精选

Android 13强制完全横屏改机指南:从DisplayRotation到SystemUI适配

🕒 发布时间:2026/9/7 22:56:40 📁 来源:尧图网络
做车机、广告机、收银机这类 Android 定制项目的兄弟肯定都遇到过这个需求设备一上电就得横屏不管是桌面、设置、视频、游戏统统给我横过来。这个需求听起来简单但真正做起来如果你只是在某个 App 里调了个setRequestedOrientation(landscape)过两天测试就会来找你——桌面怎么还是竖的设置界面一半是黑的锁屏一亮屏闪了一下竖屏说白了普通应用层的横屏方案根本撑不起强制完全横屏这几个字。这个需求要动的地方从来不是某个 Activity而是系统层面的方向决策链路。尤其是现在很多项目已经切到 Android 13 了方向限制机制和 Android 11 以前相比又改了不少逻辑老的改法直接搬过来未必好使。这篇文章我就把自己在 Android 13 上做强制完全横屏的完整思路、核心改动点和踩过的坑整理出来给做 AOSP 定制、ROM 开发、行业平板方案的兄弟们一个可以直接参考的落地方案。1. 为什么要做完全横屏从应用横屏到系统横屏的差距1.1 普通横屏方案的三个局限先说清楚一个概念我们说的完全横屏不是让某一个 App 横屏而是让整个系统从开机到锁屏、从桌面到每个第三方应用全部都按横屏方向显示。很多人的第一反应是直接用ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE去锁应用方向或者用 ADB 命令把自动旋转关掉、把默认方向设成横屏。这些做法不是不行只是有三个绕不开的局限只管到应用层系统界面、锁屏、开机动画、Recovery 模式这些不归某个 Activity 管应用层的设置对它们完全无效。管不住不听话的应用从 Android 11API 30开始应用可以在 Manifest 里声明android:ignoreOrientationRequesttrue表示我才不管系统让我转哪个方向我就要竖屏。你越是想全局横屏越会遇到这种刺头。方向状态不稳定单纯用settings put system user_rotation 1这种方式只是设置了一个用户偏好。一旦有应用请求了特殊方向或者传感器触发了一次旋转事件整体方向状态很容易被带走表现出来的症状就是大部分时间是横屏偶尔某个页面突然竖了。所以要做到完全横屏必须往系统服务层走让所有方向请求到达决策点的时候被统一改写成横屏方向。那这个决策点在哪这就是 Android 13 上需要重新确认的问题。1.2 Android 13 方向机制的关键变化Android 13 在窗口管理和方向决策这块和早期的 Android 版本相比最大的变化是方向计算逻辑从PhoneWindowManager慢慢收拢到了DisplayRotation这个类里。在 Android 10、11 的年代大家改强制横屏基本上是去PhoneWindowManager.java里找setRotationForOrientation()在入口处把返回值直接改成横屏 rotation这招在很多 ROM 方案里非常流行。但从 Android 11 到 13DisplayRotation.java逐步成为方向计算的真正入口DisplayContent在旋转时主要就是通过它来得到目标 rotation。也就是说如果你还按照老教程去翻PhoneWindowManager可能在 Android 13 上找不到原来的方法或者在那边改了代码但完全不生效。这也是我写这篇文章的一个直接原因很多网上流传的横屏方案是基于 Android 9、10 的拿到 Android 13 上直接翻车。2. 屏幕方向到底是怎么转起来的核心链路拆解2.1 一次方向请求的完整流转要改系统方向你得先知道一次方向请求是怎么走到最终旋转的。在 Android 13 的 AOSP 代码里链路大概是这样的应用调用setRequestedOrientation()最终会通过 Binder 通知到WindowManagerService。WindowManagerService拿到请求后把方向值交给DisplayContent每个逻辑显示器对应一个DisplayContent它内部会记录当前 display 的方向偏好。DisplayContent在合适的时机比如有窗口变化、传感器变化、请求变化调用DisplayRotation.rotationForOrientation()来重新计算目标 rotation。计算出来的 rotation 值0/90/180/270会被发到SurfaceFlinger真正把画面转过来。整个链路里rotationForOrientation()就是那个一夫当关的方法。它接收应用传进来的 orientation 类型结合当前传感器建议、用户偏好、系统 policy输出一个最终的物理旋转角度。我们做强制完全横屏在这里下手是最直接、最稳定的。2.2 真正的决策者DisplayRotation 和 WindowManagerService在 Android 13 里DisplayRotation承担了核心的方向决策逻辑文件路径一般在frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java对应的还有一个比较关键的依赖类WindowOrientationListener负责从传感器读取设备姿态给DisplayRotation提供一个建议方向。当然我们强制横屏的时候传感器建议基本可以忽略因为无论传感器说什么我们都直接返回横屏。而WindowManagerService则负责把这些方向变化同步到所有窗口和显示区域包括 SystemUI 所在的主窗口。WMS里的mRotation、mLastOrientation这些字段就是当前系统全局方向的状态源头。2.3 为什么关闭自动旋转不等于强制横屏这里有个认知误区需要纠正。很多方案商工程师习惯用这样的命令去实现横屏adb shell settings put system accelerometer_rotation 0 adb shell settings put system user_rotation 1accelerometer_rotation设为 0确实关闭了传感器自动旋转user_rotation设为 1确实把用户基准方向设成了ROTATION_90横屏。但请记住这只是设置了两个 Settings 键值它代表的是用户的显示偏好。当系统里的某个窗口请求了SCREEN_ORIENTATION_PORTRAIT或者某个游戏声明了ignoreOrientationRequestDisplayRotation在计算时依然有可能会优先响应这些窗口请求结果就是你守着这两个 settings屏幕还是会莫名其妙回到竖屏。真正要完全强制必须在DisplayRotation.rotationForOrientation()这个决策点加一道硬闸门把所有输入的方向值统一转换成横屏 rotation。3. 实操三层改动实现强制完全横屏3.1 第一层用 settings 把系统基准方向钉死虽然是系统层强制但第一层基础的 settings 设置不能省这是兜底也是减少后续改动量的前提。我会在首次开机或者恢复出厂设置后通过overlay或者SettingsProvider的默认值机制把下面两个键值写进系统# 关闭自动旋转 adb shell settings put system accelerometer_rotation 0 # 固定用户旋转方向为 ROTATION_90横屏顺时针90度 adb shell settings put system user_rotation 1注意user_rotation的取值0 对应ROTATION_0竖屏1 对应ROTATION_90右横屏2 对应ROTATION_1803 对应ROTATION_270左横屏。大多数手持设备的横屏采集的是ROTATION_90也就是设备逆时针旋转 90 度后的形态。如果你不想每次手动敲 ADB 命令更规范的做法是在 AOSP 的frameworks/base/packages/SettingsProvider/res/values/defaults.xml里直接修改默认值或者通过 runtime resource overlayRRO覆盖config_defaultRotation之类的配置。具体到 Android 13我习惯在SettingsProvider里增加一个System.ACCELEROMETER_ROTATION和System.USER_ROTATION的默认值注入保证恢复出厂设置后依然是强制横屏。3.2 第二层在 DisplayRotation 里强制所有请求走横屏这一层是整个方案的核心也是最容易踩坑的地方。打开DisplayRotation.java找到rotationForOrientation(int orientation, int lastRotation, boolean displayEnabled)方法。这个方法在 Android 13 里其实是DisplayRotation内部一个比较长的方法它内部会根据 orientation 的类型走不同分支比如先判断有没有Surface.ROTATION_270之类的候选方向最后汇总输出。我们的改动思路很简单在这个方法的一开始加一个针对横屏属性的判断一旦系统要求强制横屏就直接返回横屏方向不再走后面的复杂逻辑。我实际用的大致伪代码如下// 建议放在 rotationForOrientation() 方法入口附近 if (SystemProperties.getBoolean(persist.sys.force_land, false)) { // 统一返回顺时针90度横屏 return Surface.ROTATION_90; }这里注意几个细节属性名可以自定义我用的是persist.sys.force_land你可以改成自己项目里的统一命名比如persist.vendor.landscape关键是你在SystemProperties里能控制它。横屏方向要和你设备形态匹配绝大多数行业设备是ROTATION_90但如果你的产品物理方向比较特殊可能要用ROTATION_270这个必须在真机上确认。为什么放在方法入口因为只有放在最前面才能做到完全强制。如果你放在后面中间有很多return分支可能已经提前返回了竖屏方向。除了这个方法DisplayRotation里还有几个和updateRotation有关的入口比如setRotation()如果遇到某些异常情况导致旋转状态被外部重置可以在setRotation里也做一层保护。但我的习惯是尽量集中在一个入口改避免改动面太大导致后面升级代码时冲突。改完这段大部分应用和系统界面的方向请求都会在决策点被统一成横屏。但这还不够SystemUI 是单独一块还要单独确认它的布局逻辑。3.3 第三层SystemUI 横屏布局检查与微调SystemUI 的显示方向其实由它所在窗口的 configuration 决定理论上前面DisplayRotation强制横屏后SystemUI 也自然会横屏。但实际项目里SystemUI 在横屏方向下的布局经常有各种显示问题比如状态栏时间跑到屏幕中间去了锁屏的大时钟和通知卡片错位下拉通知栏的快捷开关只有两列挤在屏幕一侧挖孔屏的摄像头区域挡住了状态栏图标。所以在 Android 13 上做强制横屏SystemUI 相关的适配工作一点都不能省。我的做法是先把 SystemUI 拉起来在横屏模式下逐个界面截图重点检查三个地方状态栏StatusBarframeworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java里的onConfigurationChanged逻辑看状态栏高度和 padding 在横屏下是否正确。Android 13 的状态栏在横屏时默认会变窄但如果强制横屏后某个布局文件没有加载对应的land资源就可能出现图标下沉或者时钟被挤掉的情况。锁屏界面Keyguard主要看时钟位置和通知布局。横屏锁屏和竖屏锁屏是完全不同的两套布局资源如果资源没补齐系统会强行拉伸竖屏布局效果就是文字被拉扁或者大面积空白。通知面板 / 快捷设置Notification Shade / Quick SettingsAndroid 12 之后 SystemUI 引入了 Shade 的概念通知面板和快捷设置面板共用一套NotificationShadeWindowView布局。横屏下快捷开关的列数、通知卡片的最大宽度、面板的左右边距都需要确认。如果只是少量布局问题我一般不直接改 SystemUI 的 Java/Kotlin 代码而是优先加 values-land 资源目录把横屏专属的尺寸和布局覆盖掉。这样改动小、风险低、升级好维护。只有遇到逻辑层问题比如某个 View 在横屏时被代码强制隐藏才去动 Java 代码。4. 编译验证与兼容性检查清单4.1 编译产物验证改完DisplayRotation和相关资源后要重新编译系统镜像。这里有个比较容易忽略的点如果之前是增量编译DisplayRotation.java改动后一定要确认services.jar真的编进去了。我遇到过一次编译成功但旧逻辑还在的情况最后发现是增量编译缓存导致根本没触发重编。最稳的验证方式是把新的services.jar解包dex后 grep 一下自己加的字符串比如persist.sys.force_land能看到就说明代码确实打进了产物dexdump -d services.jar | grep persist.sys.force_land如果你用的是make完整整编那一般不需要这一步但整编耗时长很多项目还是习惯增量编译所以这个检查值得做。4.2 开机后的验证清单刷机完成后我建议按下面这份清单逐项过一遍防止有漏网之鱼开机动画是不是横屏播放如果开机动画还是竖屏说明你改的只是 Android 上层方向bootanimation 走的是另一条链路这个我在后面踩坑记录里会细说。桌面是否始终横屏桌面进程重启后方向是否保持锁屏界面是否横屏按电源键亮屏的一瞬间是否有横竖屏闪烁下拉通知栏、快捷开关面板是否横屏显示设置、相册、视频播放器等系统应用是否横屏安装几个常见的第三方应用微信、抖音、浏览器、游戏看它们启动后是否横屏。分屏模式下两个应用的方向表现是否正常。重启一次设备确认开机到进入桌面的整个过程中方向没有跳变。这份清单我每次做横屏项目都会用基本上跑完一遍功能上有没有问题就清楚了。4.3 第三方App兼容性检查第三方 App 是强制横屏方案里最容易出幺蛾子的部分。尤其是那些在 Manifest 里声明了android:ignoreOrientationRequesttrue的应用比如某些短视频 App、银行 App、游戏引擎应用它们会主动忽略系统级的方向限制。遇到这种应用如果你的产品要求连它都必须横屏单靠改DisplayRotation可能还不够。原因在于DisplayRotation计算的是最终物理旋转但应用拿到onConfigurationChanged里的 orientation 信息后依然可以根据自己的清单行为来决定加载横屏还是竖屏资源。这种情况下我通常还会在ActivityStarter或者ActivityTaskManagerService里做一层拦截在启动 Activity 的时候把它的 requested orientation 直接覆盖为横屏方向。但这属于定向消灭刺头每个异常应用都要单独分析别指望一套代码自动解决所有应用。如果项目里允许更好的做法是给某个应用单独做兼容策略而不是全局一刀切。5. 实战踩坑记录我在这套方案里被绊倒的四个地方5.1 开机动画和 Recovery 仍然竖屏第一次做完强制横屏刷机后满心欢喜结果开机动画一出来还是竖的那一刻的心情相信很多人懂。原因很简单bootanimation开机动画在 boot 阶段绘制它读的是SurfaceFlinger的显示参数和 Android 上层的DisplayRotation是两条路。要让开机动画横屏有两个办法直接改开机动画资源把bootanimation.zip里的图片/视频改成横屏分辨率比如你的屏幕是 1080x1920 竖屏那开机动画就按 1920x1080 来做。这个办法简单粗暴适合固定分辨率的产品。在SurfaceFlinger/BootAnimation里做旋转修改 bootanim 进程的初始化方向或者通过系统属性让SurfaceFlinger在 boot 阶段直接设置旋转。这个办法更干净但改动位置深需要小心 Regression。对于行业定制设备我一般推荐直接改资源的方式因为产品分辨率固定改资源最可控。Recovery 模式的方向同理需要在 Recovery 的minui图形库里处理别再指望上层方向逻辑能覆盖到。5.2 个别App无视强制方向ignoreOrientationRequest前面提到的ignoreOrientationRequestAndroid 13 里已经成为不少应用的标配尤其是海外版的视频和社交通讯应用。系统层面强制横屏后你打开这类应用它依然能摆出竖屏的姿势。排查思路是先用adb shell dumpsys activity top去看当前 Activity 的 actual orientation 和 requested orientationadb shell dumpsys activity top | grep -E ACTIVITY|orientation如果看到requestedOrientationSCREEN_ORIENTATION_PORTRAIT而屏幕还能保持横屏说明你的DisplayRotation强制生效了但如果应用渲染出来的页面还是竖的那就是应用在资源加载时主动选了竖屏资源需要走ActivityStarter覆盖的方向。还有一种更简单粗暴的兼容手段在frameworks/base/core/res/res/values/config.xml里把支持的方向列表调成只有横屏。这个配置会影响所有应用但副作用是如果某个应用确实需要竖屏做特殊交互比如扫码、身份证识别它也会被强制横过来这时候就得靠第三层针对白名单应用放行了。5.3 锁屏瞬间闪竖屏这个坑很经典系统整体已经横屏了但你按电源键点亮屏幕的一瞬间会看到画面以竖屏的宽高比闪了一下然后马上切回横屏。原因是锁屏界面在亮屏前的首帧渲染时拿到的 configuration 还没有被强制成横屏方向等Keyguard真正加载完方向才被纠正过来。这个过程在 UI 感触上就是闪了一下。我的解决思路是在KeyguardViewMediator里设置锁屏启动方向让它强制以横屏参数初始化布局而不是等系统方向通知。具体改动位置是KeyguardViewMediator里锁屏窗口的 LayoutParams 设置给WindowManager.LayoutParams加上screenOrientation ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE。如果你们的项目锁屏用的是独立的Activity用Activity做锁屏那就简单得多直接在 Manifest 里给这个锁屏 Activity 设置screenOrientationlandscape即可。如果是 SystemUI 里的Keyguard才需要从KeyguardViewMediator入手。5.4 分屏与多窗口的横屏错乱Android 13 对分屏、多窗口的支持越来越强很多行业设备也开始做分屏应用。在强制横屏的大前提下分屏却可能带来小麻烦分屏后上下两个窗口的方向可能不一致或者某个窗口为了迁就另一个窗口突然切成了竖屏。这个问题的根源是DisplayContent.getOrientation()在汇总所有窗口方向时会取一个综合最优方向。如果某个窗口强制竖屏在分屏场景下它可能会把整体方向往竖屏拉。解决办法是在DisplayRotation强制横屏的基础上再把分屏相关的窗口方向决策过滤一遍让所有窗口的方向请求在合并时也统一映射成横屏。一般来说产品需求里如果不是特别强调分屏可以直接在DevicePolicyManager或ActivityTaskManager里禁用分屏功能省掉这些兼容性麻烦。Adobe 订制的很多行业设备都是这么干的毕竟分屏在纵竖混杂方向下的体验确实不好保证。写在最后的一点实际体会这套强制横屏方案我在 Android 13 的行业平板上跑过完整流程从改DisplayRotation、微调 SystemUI 资源到处理开机动画、锁屏闪烁和个别刺头应用前后花了两三天时间才把所有细节磨干净。最大的体会就是别指望改一个入口就万事大吉Android 的方向机制是分层的每一层都有自己的默认行为和资源策略只有层层都处理到位才能做到真正的完全横屏。如果你正在做类似的横屏项目建议先按文章里的三层改动搭起框架再对照验证清单逐项排查。遇到具体的坑欢迎在评论里把你的现象和日志一起发出来我看到会尽量帮大家判断问题出在哪个环节。做系统定制就是这样一个人踩坑一群人避坑能帮一个是一个。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →