Android 确定废弃「屏幕方向锁定」等 API ,如何让 App 适配大屏和 PC/XR 等场景

122 阅读4分钟

对于 Android 开发者而言,在过去声明一个 Activity 时,大多第一件事就是添加一个 android:screenOrientation="portrait",而其实自 targetSdkVersion ≥ 31(Android 12),在 2020 年的 Android Studio 3.6 就开始有相关警告:

image.png

这是因为从 Android 12 开始,某些可折叠设备会无视用户配置的 screenOrientation ,而是强制 Activity 采用 Letterboxing 模式:

image-20250330084837308

关于这个我们在过去的 《Android 折叠屏适配详解》 聊过,是否进入 Letterboxing 模式和 TargetSDK 版本、 App 配置和屏幕分辨率都有关系,并且不同 OS 版本上 Letterboxing 模式的呈现方式也可能有所不同。

而从 Android 16 开始,Google 将逐步淘汰用于限制应用屏幕方向和大小调整的清单属性及运行时 API ,而最初生效的设备类型为 "大屏"场景,即显示区域的最小宽度大于或等于 600dp 的情况(sw >= 600dp) ,也就是:

  • 大屏可折叠设备的内屏

  • 平板电脑,包括桌面窗口模式

  • 桌面环境,包括 Chromebook

具体为 TargetSDK >= 36 之后,在 sw >= 600dp 时,以下配置和 API 将被忽略:

基于 android:appCategory 标志的游戏暂时可不受这些变更的影响

也就是,大屏场景下,之前 App 在设置了 screenOrientation 的情况下,App 会是 letterboxed 状态 ,而 Android 16 开始会直接拉成充满:

当然,不升级 targetSDK 就不用适配的想法现在是行不通的,因为现在上架都会开始要求你升级 targetSDK ,另外:

  • Android 16 (2025):在 API 36 下可以配置不适配
  • 2026 年的 Android 版本:API 37 开始,开发者必须适配

比如在 API 36 时,如果你还是想「摆烂」,那么可以通过配置 PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY 来继续「拖延」适配:

<activity ...>
  <property android:name="android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY" android:value="true" />
  ...
</activity>
<application ...>
  <property android:name="android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY" android:value="true" />
</application>

当然,在 API 37 的时候,开发者将无法选择退出,而针对 Google Play 上架场景, 2026 年 8 月前应用需以 API 36 为目标,在 2027 年 8 月前应用需以 API 级别 37 为目标

另外,在 API 36 target 上, R.attr#windowOptOutEdgeToEdgeEnforcement 也会被弃用,也就是 App 无法再强制退出 edge-to-edge 模式,如果不做适配,横屏拉伸+ edge-to-edge 的效果看起来应该会很酸爽。

那么,开发者应该如何适配?最简单就是使用响应式布局如 Compose、Flutter 等。

如果是原生端肯定首选 Compose ,使用 material3-window-size-class 库,然后利用 calculateWindowSizeClass() 计算当前窗口的 WindowSizeClass ,从而改变 UI 的布局:

import androidx.activity.compose.setContent
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass

class MyActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            // Calculate the window size class for the activity's current window. If the window
            // size changes, for example when the device is rotated, the value returned by
            // calculateSizeClass will also change.
            val windowSizeClass = calculateWindowSizeClass(this)
            // Perform logic on the window size class to decide whether to use a nav rail.
            val useNavRail = windowSizeClass.widthSizeClass > WindowWidthSizeClass.Compact

            // MyScreen knows nothing about window size classes, and performs logic based on a
            // Boolean flag.
            MyScreen(useNavRail = useNavRail)
        }
    }
}

另外还可以通过 com.google.accompanist:accompanist-adaptive 的 TwoPane 进行适配,TwoPane 提供了两个固定的槽位,两个槽位的默认位置由 TwoPaneStrategy 驱动,它可以决定将两个槽位水平或垂直排列,并可配置它们之间的间隔:

不同场景 Compose 还可以使用 FlowLayout 适配折叠变化 ,FlowLayout 包含 FlowRowFlowColumn ,当一行(或一列)放不下里边的内容时,会自动换行,这在折叠屏展开和收缩场景也非常实用。

而传统 View 场景,可以使用 Activity Embedding ,理论上 Activity Embedding 不需要代码重构,可以通过创建 XML 配置文件或进行 Jetpack WindowManager API 调用来确定 App 如何显示其 Activity(并排或堆叠) :

Jetpack WindowManager 管理和配置 Activity Embedding 其实相当灵活,另外 SlidingPaneLayout 也是一种兼容方式:

当然,你也可以直接使用 Jetpack WindowManager 的 FoldingFeature 等相关信息去自定义适配。

更多可见:juejin.cn/post/727030…

而现在 Android Studio 的模拟器也已经提供了相应场景支持,对于大多数没有折叠屏设备的开发者来说,模拟器适配是最合适不过的场景:

另外,针对 Flutter 场景,responsive_sizerflutter_flexible_uiResponsiveFramework 等框架,在大屏幕设备下提供不错的动态设备支持:

最后,其实不难看出,在前面官方提及的 「桌面窗口模式」等场景,也看出来该操作是在为 Android PC 铺路,对于 Android PC,在集齐了「Linux 终端控制台支持」、「桌面模式」、「外部显示器支持」、「窗口多任务」,「最小化」,「多实例支持」、「Desktop View」、「外部显示器排列和切换」等场景后,在 App 端也终于开始迎来强制性的 UI 适配需求,看起来 Android 团队也重新开始重视 PC 场景,另外还有 Android XR 中的窗口场景,所以针对 Android 的大屏需求,未来只会会越来越多。

那么,接下来你会开始适配,还是选择能拖就拖

参考链接: