如何为不同屏幕尺寸(small, normal, large, xlarge)提供不同的布局?

解读

面试官真正想考察的是:

  1. 你是否理解 Android 资源匹配机制(Configuration Qualifier)的优先级与回退策略;
  2. 你是否能在国内“无 GMS、屏幕碎片化严重”的现实场景下,给出兼顾维护成本与性能的可落地方案;
  3. 你是否知道官方已废弃“small/normal/large/xlarge”分类后,如何平滑过渡到“sw<N>dp”体系,同时又能向下兼容老项目;
  4. 你是否能结合 Jetpack WindowManager、Compose 等新 API,说明未来方向。
    一句话:不是简单放几个 layout 文件夹,而是“为什么放、放什么、怎么放、放完以后怎么测、怎么灰度、怎么回滚”。

知识点

  1. 资源匹配优先级:MCC→语言→sw<N>dp→w<N>dp→h<N>dp→small/normal/large/xlarge→屏幕方向→屏幕密度→平台版本……
  2. 官方废弃史:API 13 引入 sw<N>dp,API 34 完全移除 xlarge 标记,studio 新建项目默认不再生成。
  3. 国内典型机型:
    small(≈3.3″ 480×320)已绝迹;normal(≈5″ 720×1280)千元机;large(≈7″ 1920×1200)小平板;xlarge(≈10.5″ 2560×1600)学习平板。
  4. 适配维度:
    • 布局:layout-sw360dp、layout-sw600dp、layout-sw720dp
    • 值:values-sw360dp/dimens.xml,保证字号、间距随屏宽变化
    • 可绘制:drawable-sw600dp-ldpi 避免 7 寸平板加载 2× 图浪费内存
  5. 构建脚本:gradle 3.0+ 支持分包(split apk),国内多渠道(华为、OPPO、VIVO、小米、应用宝)需在 packagePlugin 里把 sw600dp 资源打独立 split,否则渠道包体积+18% 会被商店拒审。
  6. 运行时检测:WindowMetricsCalculator.computeCurrentWindowMetrics() 替代 deprecated Display.getSize(),结合 Jetpack WindowManager 监听折叠态。
  7. 测试闭环:
    • 本地:AVD 创建 320×480、480×854、600×1024、1200×1920 四档,打开“强制 RTL”与“最大字体”双开关,跑 espresso 截图比对;
    • 云端:华为云测、腾讯 WeTest、阿里 MQC 提供 200+ 真机,脚本里用 adb shell wm size 重置分辨率,跑 monkey 10000 次无崩溃;
    • 灰度:小米开放平台上架“设备维度灰度”,先投 5% 的 sw720dp 设备,观察崩溃率 <0.1% 再全量。
  8. 回滚策略:layout 文件使用版本限定符 layout-v34,一旦线上崩溃率异常,通过 Firebase Remote Config(国内用腾讯 TConfig)下发“force_default_layout=true”,让客户端回退到默认 layout。

答案

“国内项目已全面转向 sw<N>dp 方案,但面试时我会分三步回答:
第一步,资源目录:
res/
├─layout-sw360dp/ // 手机 5-6.3″
├─layout-sw600dp/ // 小平板 7-8″
├─layout-sw720dp/ // 大平板 10-13″
├─layout/ // 默认回退
└─values-sw360dp/dimens.xml
这样无论厂商怎么改 dpi,系统都能用最小宽度匹配,避免 xlarge 被废弃后匹配失败。

第二步,构建与体积:
在 build.gradle 里启用
android {
splits {
density { … }
abi { … }
}
}
把 sw600dp 与 sw720dp 的 layout、drawable 打入独立 split,国内商店审核时上传 base+split,用户下载只拉取对应 split,包体积平均减少 12%。

第三步,运行时兜底:
使用 WindowMetrics 计算实际可用区域,若发现短边 <360dp 且长边 ≥600dp(外接折叠键盘的奇葩机型),手动通过 Activity.recreate() 重新加载 layout-sw600dp,防止用户卡在‘手机布局’。

最后测试:本地四档 AVD + 云测 200 真机,灰度到 5% 大平板用户,崩溃率 <0.1% 再全量,异常时通过远程配置开关回退默认布局。”

拓展思考

  1. Compose 时代还有必要做多 layout 吗?
    答:有必要,但维度从“屏幕尺寸”升级为“窗口尺寸”。使用 WindowSizeClass (Compact/Medium/Expanded) 在 setContent 里直接选择 NavRail 或 BottomBar,一套代码即可,但底层仍需 values-sw360dp/dimens.xml 保证字体不溢出。

  2. 国内折叠屏井喷,如何与 sw<N>dp 并存?
    答:把折叠态看作“动态 sw”,在 Activity 的 onConfigurationChanged 里监听 WindowSizeClass 变化,通过 ViewModel 保存 UI 状态,再调用 NavController 重新设置 startDestination,避免整页重建;同时用 Jetpack WindowManager 获取 FoldingFeature.isSeparating,判断是否需要双栏布局。

  3. 车载与 TV 的 1080P 大屏直接复用 sw720dp 吗?
    答:不行。车载屏幕 dpi 低但观看距离远,字体需要 1.3× 放大;TV 有 overscan 区域。需在 values-car/dimens.xml 里再覆盖一份,并通过 uiMode 限定符 drawable-car-xxhdpi 单独切图,否则会在比亚迪车机上出现按钮被导航栏截断的客诉。