OpenHarmony 窗口管理屏幕适配操作教程
以 RK3588 + MIPI DSI 2560×1600 屏幕为案例
零、前置条件
需完成底软驱动屏幕适配,开机点亮屏幕
一、背景
OpenHarmony 窗口管理服务(WMS)内置了一个浮动窗口最大尺寸限制,默认值针对 1080p 等常见分辨率设计。当设备接入更高分辨率的屏幕(如 2560×1600)时,浮动类型的系统窗口(弹窗、输入法、对话框等)会被裁剪,导致无法铺满屏幕。
本文档说明适配过程中涉及的关键参数、计算方法和具体修改方案。
二、关键概念
2.1 三个核心参数
| 参数 | 含义 | 单位 |
|---|---|---|
| 物理分辨率 | 屏幕实际的像素数量 | px(物理像素) |
| dpi | 每英寸像素密度,在设备配置文件中人为设定 | dots per inch |
| vpr | 虚拟像素比(Virtual Pixel Ratio),由 dpi 计算得出 | 无量纲 |
| dp | 密度无关像素(Density-independent Pixel),逻辑像素 | dp |
2.2 参数之间的换算关系
vpr = dpi ÷ 160
dp = px ÷ vpr
px = dp × vpr
其中 160 是 OHOS 系统中硬编码的基准密度常量,定义在 window_manager 的头文件中。
2.3 基准密度 160 的代码出处
160 这个数字不是经验值,而是在系统源码中以常量形式定义的。
定义位置 :window_manager/interfaces/innerkits/dm/dm_common.h
cpp
// 第 32 行
constexpr int DOT_PER_INCH = 160;
// 第 36 行
constexpr uint32_t BASELINE_DENSITY = 160;
使用位置 :window_manager/dmserver/src/abstract_display.cpp
系统在初始化 Display 时,读取 display_manager_config.xml 中配置的 dpi 值,然后除以 BASELINE_DENSITY(即 160)得到 virtualPixelRatio_(即 vpr):
cpp
// abstract_display.cpp 第 78~81 行
uint32_t densityDpi = static_cast<uint32_t>(numbersConfig["dpi"][0]);
if (densityDpi >= DOT_PER_INCH_MINIMUM_VALUE && densityDpi <= DOT_PER_INCH_MAXIMUM_VALUE) {
virtualPixelRatio_ = static_cast<float>(densityDpi) / BASELINE_DENSITY;
absScreen->SetVirtualPixelRatio(virtualPixelRatio_);
同样的逻辑在 window_scene/session_manager/src/screen_session_manager.cpp 第 443 行也有:
cpp
densityDpi_ = static_cast<float>(densityDpi) / BASELINE_DENSITY;
完整的调用链:
display_manager_config.xml 中 <dpi>200</dpi>
↓ 系统启动时读取
abstract_display.cpp / screen_session_manager.cpp
↓ densityDpi / BASELINE_DENSITY
virtualPixelRatio_ = 200 / 160 = 1.25
↓ SetVirtualPixelRatio()
存入 Screen/Display 对象,后续所有窗口布局使用
因此 vpr = dpi ÷ 160 这个公式的 160 来自 dm_common.h 中的 BASELINE_DENSITY = 160。
三、查找设备的 dpi 配置
3.1 配置文件位置
dpi 配置在产品级别的 display_manager_config.xml 中:
vendor/<厂商>/<产品名>/window_config/display_manager_config.xml
本案例的路径:
vendor/kaihong/633_rk3588j_isdt-2/window_config/display_manager_config.xml
3.2 配置内容
xml
<Configs>
<!--Window display dpi, valid range is 80~640, use 0 if no configuration is required-->
<dpi>200</dpi>
</Configs>
当前设备的 dpi = 200。
3.3 dpi 配置的生效路径
这个 xml 文件在编译时会被打包到设备的 /system/etc/window/resources/ 目录下。系统启动时,AbstractDisplay 或 ScreenSessionManager 读取此配置,按照 2.3 节的逻辑计算出 vpr 并设置到 Screen/Display 对象中。
后续 WMS 中所有涉及 vpr 的计算(包括 GetSystemSizeLimits() 中的 maxFloatingWindowSize_ * vpr)都使用这个值。
四、计算适配参数
已知信息:
- 屏幕物理分辨率:2560 × 1600 px
- dpi 配置:200 (来自
display_manager_config.xml)
第 1 步:计算 vpr
根据 dm_common.h 中 BASELINE_DENSITY = 160 和 abstract_display.cpp 中的计算逻辑:
vpr = dpi ÷ BASELINE_DENSITY = 200 ÷ 160 = 1.25
第 2 步:计算屏幕的 dp 尺寸
宽度 dp = 2560px ÷ 1.25 = 2048 dp
高度 dp = 1600px ÷ 1.25 = 1280 dp
第 3 步:计算原始浮动窗口最大尺寸(修改前)
WMS 中 maxFloatingWindowSize_ 默认值为 1920(单位 dp)。
原始 maxWidth = 1920dp × 1.25 = 2400 px
2400px < 屏幕宽度 2560px,差了 160px,所以浮动窗口会被裁剪。
第 4 步:确定新的 maxFloatingWindowSize_ 值
要让浮动窗口能铺满屏幕宽度:
maxFloatingWindowSize_ ≥ 屏幕宽度(px) ÷ vpr = 2560 ÷ 1.25 = 2048 dp
设为 2048 即可。验算:
修改后 maxWidth = 2048dp × 1.25 = 2560 px = 屏幕宽度 ✓
计算速查公式
对于任意屏幕,只需要知道物理分辨率和 dpi,一步到位:
maxFloatingWindowSize_ = 屏幕宽度(px) ÷ (dpi ÷ 160)
五、修改方案
需要修改 2 个文件,共 2 处核心改动 + 1 处系统栏宽度适配。
5.1 修改浮动窗口尺寸上限
文件 :wmserver/src/window_layout_policy.cpp
位置 :maxFloatingWindowSize_ 的定义处(约第 35 行)
修改内容:
cpp
// 修改前(默认值,适用于 1080p 等屏幕)
uint32_t WindowLayoutPolicy::maxFloatingWindowSize_ = 1920;
// 修改后(适用于 2560x1600@dpi200 屏幕)
uint32_t WindowLayoutPolicy::maxFloatingWindowSize_ = 2048;
作用 :此值在 GetSystemSizeLimits() 函数中被使用:
cpp
// wmserver/src/window_layout_policy.cpp 约第 475 行
systemLimits.maxWidth_ = static_cast<uint32_t>(maxFloatingWindowSize_ * vpr);
systemLimits.maxHeight_ = static_cast<uint32_t>(maxFloatingWindowSize_ * vpr);
修改后,所有浮动窗口(Dialog、Float、SystemAlarm、Screenshot 等)的最大宽度从 2400px 提升到 2560px,不再被裁剪。
5.2 系统栏宽度适配(StatusBar / NavigationBar / InputMethod)
文件 :wmserver/src/window_layout_policy.cpp
位置 :FixWindowRectWithinDisplay() 函数中(约第 795 行)
StatusBar、NavigationBar、InputMethodFloat 是系统级窗口,它们的宽度由 SystemUI 应用请求决定。SystemUI 可能按照默认屏幕宽度(如 720dp → 900px)来请求,不会自动适配 2560 宽屏。因此需要在 WMS 端针对目标分辨率强制修正宽度。
修改内容:
在 FixWindowRectWithinDisplay() 中增加目标屏幕判断和系统栏宽度修正:
cpp
void WindowLayoutPolicy::FixWindowRectWithinDisplay(const sptr<WindowNode>& node) const
{
// ... 原有逻辑 ...
Rect rect = node->GetRequestRect();
// 判断是否为目标屏幕
bool isTargetDisplay = (displayRect.width_ == 2560 && displayRect.height_ == 1600) ||
(displayRect.width_ == 1600 && displayRect.height_ == 2560);
switch (type) {
case WindowType::WINDOW_TYPE_STATUS_BAR:
rect.posY_ = displayRect.posY_;
if (isTargetDisplay) {
rect.posX_ = displayRect.posX_;
rect.width_ = displayRect.width_; // 强制状态栏铺满屏幕宽度
}
break;
case WindowType::WINDOW_TYPE_NAVIGATION_BAR:
rect.posY_ = static_cast<int32_t>(displayRect.height_) + displayRect.posY_ -
static_cast<int32_t>(rect.height_);
if (isTargetDisplay) {
rect.posX_ = displayRect.posX_;
rect.width_ = displayRect.width_; // 强制导航栏铺满屏幕宽度
}
break;
case WindowType::WINDOW_TYPE_INPUT_METHOD_FLOAT:
if (isTargetDisplay) {
rect.posX_ = displayRect.posX_;
rect.width_ = displayRect.width_; // 强制输入法铺满屏幕宽度
}
break;
default:
// ... 原有逻辑 ...
}
node->SetRequestRect(rect);
}
为什么这个修改和 5.1 是独立的?
| 修改 | 解决的问题 | 针对的窗口 |
|---|---|---|
| 5.1 修改 maxFloatingWindowSize_ | 浮动窗口尺寸被裁剪到 2400px | Dialog、Float、Alarm 等浮动窗口 |
| 5.2 修改 FixWindowRectWithinDisplay | 系统栏宽度不够 2560 | StatusBar、NavigationBar、InputMethod |
系统栏不走浮动窗口的尺寸限制逻辑(它们不是 WINDOW_MODE_FLOATING),所以必须单独处理。
5.3 确认 Cascade 文件无需额外修改
文件 :wmserver/src/window_layout_policy_cascade.cpp
由于 5.1 已经从源头解决了浮动窗口尺寸上限问题,UpdateFloatingWindowSizeBySizeLimits() 函数中不需要任何额外的类型豁免逻辑。
六、编译
修改完成后,仅需编译 WMS 的 libwms 库:
bash
./build.sh --product-name <产品名> -f foundation/window/window_manager/wmserver:libwms
本案例:
bash
./build.sh --product-name 633_rk3588j_isdt-2 -f foundation/window/window_manager/wmserver:libwms
编译产物路径(参考):
out/<产品名>/packages/phone/system/lib/platformsdk/libwms.z.so
将 libwms.z.so 推送到设备的 /system/lib/platformsdk/ 目录并重启即可生效。
七、验证
7.1 窗口尺寸验证
在设备上执行:
bash
hidumper -s WindowManagerService -a -a
检查输出中各窗口的 Rect 信息:
WindowName DisplayId WinId Type Mode ZOrd [ x y w h ]
SystemUI_StatusBar 0 3 2108 102 2 [ 0 0 2560 60 ] ← 宽度 2560 ✓
SystemUI_NavigationB 0 2 2112 102 3 [ 0 1490 2560 110 ] ← 宽度 2560 ✓
SystemDialog1 0 39 2106 102 1 [ 0 0 2560 1600 ] ← 全屏 ✓
7.2 日志验证
过滤 WMS 布局日志:
bash
hilog | grep -E "Layout|SizeLimits"
关键日志:
target display hit, winId: 36, type: 2106, displayRect: [0, 0, 2560, 1600]
UpdateWindowSizeLimits: [Update SizeLimits] winId: 36, Width: [max:2560, min:400]
SetBounds: Name:SystemDialog1 id:36 winRect: [0, 0, 2560, 1600]
确认 max:2560(而非修改前的 max:2400)即说明修改生效。
八、适配其他分辨率的参考
| 屏幕分辨率 | dpi | vpr | 屏幕宽度(dp) | maxFloatingWindowSize_ |
|---|---|---|---|---|
| 1920×1080 | 240 | 1.5 | 1280 | 1920(默认值即可) |
| 2560×1600 | 200 | 1.25 | 2048 | 2048 |
| 2560×1600 | 320 | 2.0 | 1280 | 1920(默认值即可) |
| 1920×1200 | 160 | 1.0 | 1920 | 1920(默认值即可) |
| 2880×1800 | 240 | 1.5 | 1920 | 1920(默认值即可) |
| 3840×2160 | 320 | 2.0 | 1920 | 1920(默认值即可) |
规律 :只有当 屏幕宽度(px) ÷ vpr > 1920 时,才需要修改 maxFloatingWindowSize_。大多数高分屏因为 dpi 也高,换算后的 dp 宽度仍在 1920 以内,不需要改。本案例之所以需要改,是因为 dpi=200 相对较低,导致 dp 宽度(2048)超过了默认上限(1920)。
九、涉及文件清单
| 文件 | 修改内容 |
|---|---|
vendor/<厂商>/<产品>/window_config/display_manager_config.xml |
dpi 配置(只读参考,不需要修改) |
wmserver/src/window_layout_policy.cpp |
maxFloatingWindowSize_ 值 + FixWindowRectWithinDisplay 系统栏适配 |
wmserver/src/window_layout_policy_cascade.cpp |
确认无需额外修改 |