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
涉及的主要源码如下:
- 构造函数和默认值:screen_ui.cpp
- 成员声明及圆角屏用途注释:screen_ui.h
- Recovery 构建变量转属性:Makefile
- Recovery ramdisk 的
default.prop链接:Makefile - Recovery 模式加载
/prop.default:property_service.cpp - 菜单和底部日志绘制:screen_ui.cpp
- 文本显示行列数:screen_ui.cpp
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):
- 如果对应属性已经定义,使用属性值。
- 如果属性没有定义,使用第二个参数作为默认值,也就是
kDefaultMarginWidth或kDefaultMarginHeight。 - 读取发生在
ScreenRecoveryUI对象构造时,结果保存到const int margin_width_和const int margin_height_。 - 绘制时使用这些成员,不会每一帧重新读取属性。
因此三种方式会生效,是因为它们分别改变了同一个初始化过程的输入:
| 配置方式 | 改变的内容 | 何时生效 |
|---|---|---|
改kDefaultMarginWidth/Height | 属性不存在时使用的编译期 fallback | 编译并部署包含新 RecoveryUI 代码的镜像后 |
设置ro.recovery.ui.margin_width/height | 构造函数实际读取的属性值 | 属性在 RecoveryUI 构造前已加载时 |
设置TARGET_RECOVERY_UI_MARGIN_WIDTH/HEIGHT | 构建时生成到 Recoveryprop.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 := 40Recovery 构建 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 图像为准逐步调整:
- 先加
margin_height,确认顶部菜单和底部日志都离开圆角区域。 - 再加
margin_width,确认左右首列文字安全。 - 检查菜单项、错误日志和长文本是否仍能显示;margin 过大可能减少可显示行数。
- 如果宽度 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,以及生成的 Recoveryprop.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 启动属性,还是产品构建生成属性的阶段。