RecoveryUI 圆角屏安全边距配置及代码调用链

1. 背景与现象

在圆角屏设备上,Recovery 菜单和日志区域可能延伸到面板四角的不可视区域。例如,屏幕上沿的菜单首行、左上角提示文字,或底部的错误/启动日志会被圆角遮挡。

Recovery 使用 framebuffer 直接绘制界面,不会像 Android 普通应用的 Window/DecorView 那样自动获得系统窗口 Insets。Recovery UI 因此提供了自己的水平、垂直 margin:

  • margin_width_:左右安全边距。
  • margin_height_:顶部和底部安全边距。

Recovery 源码头文件对这两个成员的注释就是:

复制代码
// The margin that we don't want to use for showing texts
// (e.g. round screen, or screen with rounded corners).
const int margin_width_;
const int margin_height_;

定义位于 VENDOR RecoveryUI screen_ui.h。因此这两个配置就是为圆屏/圆角屏上的文字安全区域准备的。

2. 总体调用链

属性未定义时作为 fallback

BoardConfig 设置 TARGET_RECOVERY_UI_MARGIN_*

Makefile 生成 Recovery prop.default

Recovery ramdisk 提供 /prop.default

init 在 Recovery 模式加载 /prop.default

ScreenRecoveryUI 构造函数读取 ro.recovery.ui.margin_*

保存为 margin_width_ 和 margin_height_

菜单/标题/日志绘制函数计算坐标

kDefaultMarginWidth/Height

涉及的主要源码如下:

3. 属性读取:三种设置最终汇合的位置

ScreenRecoveryUI 构造时执行以下初始化:

复制代码
constexpr int kDefaultMarginHeight = 0;
constexpr int kDefaultMarginWidth = 0;

ScreenRecoveryUI::ScreenRecoveryUI(bool scrollable_menu)
    : margin_width_(
          android::base::GetIntProperty("ro.recovery.ui.margin_width", kDefaultMarginWidth)),
      margin_height_(
          android::base::GetIntProperty("ro.recovery.ui.margin_height", kDefaultMarginHeight)),
      ...

源码位置:screen_ui.cpp

关键点是 GetIntProperty(property, default):

  1. 如果对应属性已经定义,使用属性值。
  2. 如果属性没有定义,使用第二个参数作为默认值,也就是 kDefaultMarginWidth 或 kDefaultMarginHeight。
  3. 读取发生在 ScreenRecoveryUI 对象构造时,结果保存到 const int margin_width_ 和 const int margin_height_。
  4. 绘制时使用这些成员,不会每一帧重新读取属性。

因此三种方式会生效,是因为它们分别改变了同一个初始化过程的输入:

配置方式 改变的内容 何时生效
改 kDefaultMarginWidth/Height 属性不存在时使用的编译期 fallback 编译并部署包含新 RecoveryUI 代码的镜像后
设置 ro.recovery.ui.margin_width/height 构造函数实际读取的属性值 属性在 RecoveryUI 构造前已加载时
设置 TARGET_RECOVERY_UI_MARGIN_WIDTH/HEIGHT 构建时生成到 Recovery prop.default 的属性值 使用包含该配置的 Recovery ramdisk 启动后

配置优先级

通常可以按以下优先关系理解:

复制代码
Recovery 启动时加载到的 ro.recovery.ui.margin_* 属性
    优先于
kDefaultMarginWidth / kDefaultMarginHeight 编译期默认值

例如,代码默认值是 0,但 Recovery 的 /prop.default 已包含:

复制代码
ro.recovery.ui.margin_height=40
ro.recovery.ui.margin_width=3

那么构造函数得到的是 40 和 3,而不是 0。反过来,如果没有这些属性,构造函数就采用 kDefaultMargin*。

由于 margin 是构造时读取且保存在成员变量中的,Recovery UI 创建以后再修改属性不会自动触发重新布局。要让运行中的 UI 更新,必须重启到使用新属性/代码的 Recovery。

4. 三种配置方式详解

4.1 方式一:修改 screen_ui.cpp 的默认值

示例:

复制代码
constexpr int kDefaultMarginWidth = 3;
constexpr int kDefaultMarginHeight = 40;

这是直接改变 GetIntProperty() 的 fallback。它适合把某个 RecoveryUI 实现的默认布局永久改掉。

**作用条件:**对应的 ro.recovery.ui.margin_width 或 ro.recovery.ui.margin_height 没有定义。属性如果存在,属性值覆盖这个默认值。

**生效步骤:**编译 Recovery 相关目标,并确保最终刷入或打包的镜像包含新的 librecovery_ui/Recovery ramdisk。

**特点:**只改 C++ 默认值,不需要属性构建配置;但设备或产品若提供了同名属性,改默认值可能看不到效果。

4.2 方式二:设置 Recovery 专用只读属性

属性名是:

复制代码
ro.recovery.ui.margin_width
ro.recovery.ui.margin_height

示例值:

复制代码
ro.recovery.ui.margin_width=3
ro.recovery.ui.margin_height=40

属性必须在 Recovery UI 构造之前可用。可靠做法是把它们放进 Recovery ramdisk 使用的属性文件 /prop.default,而不是只写普通 Android 系统启动时使用的某个 build.prop。

在 Recovery 模式下,init 的 PropertyLoadBootDefaults() 会加载 /prop.default,见 property_service.cpp。Recovery ramdisk 构建过程中 default.prop 链接到 prop.default,见 Makefile。

**特点:**属性值直接进入 Recovery UI 的初始化;适合把配置和 Recovery 的启动属性关联起来。ro.* 是只读属性,而且 Recovery UI 只在构造时读取,所以不要依赖进入界面后再用 setprop 改值。

4.3 方式三:设置 TARGET_RECOVERY_UI_MARGIN_* 构建变量

在产品实际使用的 BoardConfig 中设置:

复制代码
TARGET_RECOVERY_UI_MARGIN_WIDTH := 3
TARGET_RECOVERY_UI_MARGIN_HEIGHT := 40

Recovery 构建 Makefile 中有显式映射:

复制代码
TARGET_RECOVERY_UI_MARGIN_HEIGHT:margin_height
TARGET_RECOVERY_UI_MARGIN_WIDTH:margin_width

并把变量名转换成属性名和值,写入 Recovery 的 prop.default:

复制代码
ro.recovery.ui.margin_height=<TARGET_RECOVERY_UI_MARGIN_HEIGHT 的值>
ro.recovery.ui.margin_width=<TARGET_RECOVERY_UI_MARGIN_WIDTH 的值>

映射和写入函数见 VENDOR Makefile;SSI 树也有对应映射,见 SSI Makefile。

这条路径把前两种方式串起来了:构建变量在编译期间生成 Recovery 属性;Recovery init 启动时读取属性;ScreenRecoveryUI 构造函数读取属性并保存 margin。通常这是产品配置最清楚、最易维护的做法。

另外,TARGET_RECOVERY_UI_MARGIN_WIDTH 还可能参与 Recovery 本地化图片宽度计算。若构建配置定义了 TARGET_RECOVERY_UI_SCREEN_WIDTH,Makefile 会从屏幕宽度中减去 margin 和菜单缩进,计算 Recovery 文本图片宽度,见 recovery_image_width 计算。所以该构建变量除运行时 margin 属性外,还能影响部分 Recovery 资源的生成尺寸。

5. Margin 如何改变屏幕坐标

单位是 Recovery framebuffer 像素 ,不是 dp。Recovery 使用 ro.sf.lcd_density 计算字体密度,但这两个 margin 作为整数直接参与屏幕坐标运算,没有经过 PixelsFromDp() 换算。

5.1 顶部菜单

菜单绘制函数开始时:

复制代码
int y = margin_height_;

随后标题、帮助信息、菜单 header 和菜单项都继续从这个 y 坐标向下绘制。因此,margin_height_=40 时,菜单内容整体从距 framebuffer 顶端 40 像素的位置起画。

菜单文字的 x 坐标是:

复制代码
int x = margin_width_ + kMenuIndent;

当前 kMenuIndent 为 4,所以 margin_width_=3 时,菜单文字从 x=7 开始。

代码位置:draw_menu_and_text_buffer_locked()

5.2 底部日志和错误信息

Recovery 的文本日志从屏幕底部向上绘制:

复制代码
for (int ty = ScreenHeight() - margin_height_ - char_height_;
     ty >= y && count < text_rows_;
     ty -= char_height_, ++count) {
  DrawTextLine(margin_width_, ty, text_[row], false);
}

因此,底部第一行日志的绘制基线会比原先向上收 margin_height_ 像素;margin_width_ 则使日志文字从左边向内缩进。照片中底部的 ERROR: logwrapper... 属于 Recovery 文本日志绘制区域,因此上下、左右 margin 都可能影响其是否落入圆角遮挡区。

代码位置:底部文本日志循环

5.3 可用文本区域

初始化字体参数时,Recovery 根据 margin 限制可使用的最大行列:

复制代码
text_rows_ = (ScreenHeight() - margin_height_ * 2) / char_height_;
text_cols_ = (ScreenWidth() - margin_width_ * 2) / char_width_;

即上下各扣除一次 margin_height_,左右各扣除一次 margin_width_。边距越大,可用文字行列越少;屏幕内容不会无限向中间挤而保持原有行数。

代码位置:InitTextParams()

5.4 注意:不是所有图形都按 margin 平移

这些 margin 主要保护文本布局。Recovery 代码中的居中图标、进度动画、进度条或 PCBA 彩色测试块有各自的坐标计算,不能假设它们都会按 margin_width_/margin_height_ 一起平移。当前照片所示菜单文字和底部文本日志使用了 margin,因此适用这套配置。

6. 示例值与调试方法

曾验证的示例值是:

复制代码
TARGET_RECOVERY_UI_MARGIN_WIDTH := 3
TARGET_RECOVERY_UI_MARGIN_HEIGHT := 40

按当前坐标代码,其几何效果大致是:

  • 菜单顶部起点:屏幕 y=40。
  • 底部日志绘制基线:从 屏幕高度 - 40 - 字体行高 开始。
  • 左侧日志起点:x=3。
  • 菜单文字起点:x=3 + 4,即 x=7。
  • 可用文本高度减少 80 px,可用文本宽度减少 6 px。

这些值是 framebuffer 像素。不同面板分辨率、Recovery framebuffer 方向和圆角半径不同,建议以实际 Recovery 图像为准逐步调整:

  1. 先加 margin_height,确认顶部菜单和底部日志都离开圆角区域。
  2. 再加 margin_width,确认左右首列文字安全。
  3. 检查菜单项、错误日志和长文本是否仍能显示;margin 过大可能减少可显示行数。
  4. 如果宽度 margin 很大,同时检查由它生成的本地化 Recovery 文本图片是否仍能完整显示。

7. SSI 与 VENDOR 源码树

当前工作区存在两份 Android 源码树:SSI 和 VENDOR。本次核对发现两份 recovery_ui/screen_ui.cpp 中以下逻辑一致:

  • kDefaultMarginHeight 和 kDefaultMarginWidth 默认值。
  • GetIntProperty() 对属性和默认值的读取。
  • 菜单顶部、底部日志和可用行列对 margin 的使用。

两份 build/make Makefile 也都包含 TARGET_RECOVERY_UI_MARGIN_* 到 ro.recovery.ui.margin_* 的映射。修改要真正生效,必须修改当前产品构建脚本实际使用的源码树和产品 BoardConfig;修改未参与本次构建的另一份树不会进入设备镜像。排查时可检查 build 命令的工作目录、TARGET_PRODUCT/TARGET_DEVICE,以及生成的 Recovery prop.default 中是否有目标属性。

8. 推荐配置与验证

对于产品级的圆角屏适配,建议优先在实际产品的 BoardConfig 设置构建变量,而不是只改 C++ 默认值:

复制代码
TARGET_RECOVERY_UI_MARGIN_WIDTH := 3
TARGET_RECOVERY_UI_MARGIN_HEIGHT := 40

这样构建系统会自动生成相应 Recovery 属性,不必手工维护 C++ fallback 和属性文件两套配置。以上仅为已测试值示例;若当前面板要求更多安全区,按实际遮挡范围调整。

构建后可检查 Recovery ramdisk 中生成的 prop.default 是否包含:

复制代码
ro.recovery.ui.margin_width=3
ro.recovery.ui.margin_height=40

如果值没有出现,通常表示变量没有进入实际产品构建配置,或这次生成的镜像没有使用预期的 BoardConfig/源码树。确认属性进入 Recovery ramdisk 后,再启动 Recovery 检查菜单顶部、底部错误日志和左右文本是否都避开圆角。

9. 配置作用范围小结

复制代码
修改 constexpr 默认值
    -> 只改变属性缺失时的 fallback

设置 ro.recovery.ui.margin_*
    -> 直接改变 RecoveryUI 构造时读到的 margin

设置 TARGET_RECOVERY_UI_MARGIN_*
    -> 构建生成 ro.recovery.ui.margin_* 到 Recovery prop.default
    -> Recovery init 加载属性
    -> RecoveryUI 构造时读取并用于绘制

三种方式都能生效,是因为最终都影响同一对成员变量;差别在于配置发生在 C++ 编译期默认值、Recovery 启动属性,还是产品构建生成属性的阶段。

相关推荐
泡海椒1 小时前
JQuick-Excel JQuickRow 数据准备实战:JObjectConverter、JQuickRow.toRows 与 XML 导出链路
xml·windows·excel
rest10241 小时前
对主从复的理解
linux
sunshine22 girl1 小时前
Java学习五 面向对象高级5 内部类4-匿名内部类(重点)
java·学习
wdfk_prog1 小时前
Wi-Fi Direct 源码分析(09):从 P2P_CONNECT 到 GO Negotiation 完成
运维·服务器·ubuntu·golang·asp.net·p2p·wifi-direct
忆挽篱笙歌1 小时前
linux开发工具
linux·运维·ubuntu
java_nnnn1 小时前
Spring Boot 快速上手:从 Maven 到第一个 Controller
java·spring·java-ee·maven
夕除1 小时前
redis--集群
java·redis
蜗牛互联网1 小时前
OLMo-core 3的token gerrymandering提醒:MoE路由要按时间窗验收
java·人工智能·wpf
奇牙coding1 小时前
Codex C接 配置教程:的 字段从迁移时必须写完整后缀,填旧值或省略 会静默回退默认模型
java·c语言·数据库·ai