HarmonyOS 7 FlexDesk:Metrics多尺寸多设备回归验收
FlexDesk 做到 05,功能层已经不缺东西了。
当前同一个项目已经跑通:
手机单栏
平板两栏
大屏三栏
底部 Tabs / 侧栏 Tabs
分屏 / 悬浮窗
连续窗口缩放
折叠 / 展开 / HALF_FOLDED
鼠标 Hover
右键菜单
Tab 焦点
键盘快捷键
如果最后一篇继续加一个新组件,反而会破坏整个系列的工程主线。
所以 06 固定做最终验收:五种输入 / 窗口 Profile,各跑 8 轮,共 40 次。
五个 Profile:
PHONE_TOUCH
430 × 932
TOUCH
TABLET_TOUCH
720 × 900
TOUCH
FLOATING_MOUSE
480 × 610
MOUSE
FOLDABLE_HOVER
720 × 720
TOUCH + KEYBOARD
HALF_FOLDED
PC_MOUSE_KEYBOARD
920 × 760
MOUSE_KEYBOARD
最终要验收的也不只是“页面能打开”。
而是:
布局
导航
折叠状态
焦点
快捷键
编辑状态
监听器
定时器
内存
都能在反复切换以后收口。
本轮统一数据:
taskId:
adapt_accept_20261003_06
profiles:
5
cycles:
8
totalRuns:
40
passed:
40 / 40
layoutStateMismatch:
0
navigationMismatch:
0
foldStateMismatch:
0
focusMismatch:
0
shortcutMismatch:
0
contentRebuildMismatch:
0
windowListenerLeak:
0
foldListenerLeak:
0
activeTimersAfterFinish:
0
retainedStates:
40 / 40
avgLayoutCommit:
2.2ms
p95LayoutCommit:
4.6ms
avgNavSwitch:
2.6ms
p95NavSwitch:
4.1ms
avgFoldTransition:
4.3ms
p95FoldTransition:
6.8ms
avgInputDispatch:
0.9ms
p95InputDispatch:
1.7ms
baselineMemory:
128.6MB
after8Cycles:
129.8MB
memoryDelta:
+1.2MB
maxMemory:
186.4MB
status:
PASS

一、06 不是随机拖 40 次窗口,而是固定 Profile
随机测试适合探索。
版本回归更适合固定输入。
第一段代码把五种形态写成可重复的 Profile:
export interface AdaptProfile {
name: string
widthVp: number
heightVp: number
input:
'TOUCH' |
'MOUSE' |
'TOUCH_KEYBOARD' |
'MOUSE_KEYBOARD'
fold:
'NONE' |
'HALF_FOLDED'
}
export const profiles:
AdaptProfile[] = [
{
name:
'PHONE_TOUCH',
widthVp:
430,
heightVp:
932,
input:
'TOUCH',
fold:
'NONE'
},
{
name:
'TABLET_TOUCH',
widthVp:
720,
heightVp:
900,
input:
'TOUCH',
fold:
'NONE'
},
{
name:
'FLOATING_MOUSE',
widthVp:
480,
heightVp:
610,
input:
'MOUSE',
fold:
'NONE'
},
{
name:
'FOLDABLE_HOVER',
widthVp:
720,
heightVp:
720,
input:
'TOUCH_KEYBOARD',
fold:
'HALF_FOLDED'
},
{
name:
'PC_MOUSE_KEYBOARD',
widthVp:
920,
heightVp:
760,
input:
'MOUSE_KEYBOARD',
fold:
'NONE'
}
]
5 个 Profile × 8 轮:
40
最终:
40 / 40
全部通过。
二、每个 Profile 都必须走自己的“真实主矛盾”
PHONE_TOUCH 不需要验证鼠标右键。
PC_MOUSE_KEYBOARD 也不应该只验证触摸点击。
所以 Runner 针对 Profile 开启不同断言:
PHONE_TOUCH
底部导航 / 单栏 / 触控点击
TABLET_TOUCH
侧栏 / 双栏 / 触控
FLOATING_MOUSE
窄窗口 / Hover / 信息密度降级
FOLDABLE_HOVER
FoldStatus / HOVER Layout / 状态连续
PC_MOUSE_KEYBOARD
侧栏 / 焦点 / 快捷键 / 右键菜单
这样“40/40”不是把同一个测试重复四十遍。
三、布局错误不能只看最终页面截图
假设窗口从:
920
→ 480
→ 920
最终又回到正常。
中间如果发生一次:
selectedTaskId 被清空
随后又从缓存恢复,
最终截图可能完全看不出异常。
所以 Regression Runner 每个阶段都采集:
NavigationSnapshot
TaskEditSnapshot
WindowMetrics
FoldWindowSnapshot
InputSnapshot
状态不一致立即记:
layoutStateMismatch
navigationMismatch
...
本轮所有 mismatch 都是:
0
四、第二段代码把“状态连续”变成硬断言
export class AdaptiveStateAssert {
verify(
before:
FlexDeskState,
after:
FlexDeskState
): boolean {
return (
before.selectedIndex ===
after.selectedIndex &&
before.projectId ===
after.projectId &&
before.taskId ===
after.taskId &&
before.draftChars ===
after.draftChars &&
before.cursorOffset ===
after.cursorOffset &&
before.scrollOffsetVp ===
after.scrollOffsetVp
)
}
}
40 次运行里:
retainedStates=40/40
没有一轮把:
pj_harmony_26
task_1042
draft=128
cursor=96
scroll=384
弄丢。
五、contentRebuildMismatch=0 比“页面没崩”更严格
FlexDesk 前五篇一直强调:
形态变化
不等于内容重建
所以最终回归还统计:
contentRebuildCount
期望是:
0
如果某一轮窗口变化导致 ProjectsPage 被重新创建,即使所有状态后来都恢复成功,仍然记为:
contentRebuildMismatch++
本轮:
0
六、导航、折叠、输入三类状态分别验收
最终 mismatch 拆开:
navigationMismatch=0
foldStateMismatch=0
focusMismatch=0
shortcutMismatch=0
不合并成一个:
uiMismatch=0
因为如果未来某版只有快捷键回退,开发者应该第一眼就知道问题属于输入层,而不是重新排查所有窗口策略。
七、Layout Commit 平均 2.2ms,P95 4.6ms
布局类操作包括:
BreakpointResolver
DensityPolicy
Tabs 方向切换
DynamicLayout Algorithm
Detail Pane 显隐
最终:
avgLayoutCommit=
2.2ms
p95LayoutCommit=
4.6ms
这只是当前测试环境的项目基线。
它的用途不是宣传“布局只要 2.2ms”,而是下一版本可以直接对照。
八、导航切换和折叠切换必须单独计时
导航:
avg=
2.6ms
p95=
4.1ms
折叠状态提交:
avg=
4.3ms
p95=
6.8ms
两者不混。
Fold Transition 会同时处理:
FoldStatus
WindowMetrics
FoldLayoutPolicy
编辑状态连续
比普通 Tabs 方向变化稍重,很正常。
九、输入事件平均 0.9ms,P95 1.7ms
05 已经建立:
Pointer
Hover
Focus
Shortcut
Key Event
的输入分发基线。
06 在 PC_MOUSE_KEYBOARD 和 FLOATING_MOUSE Profile 里继续采样。
最终:
avgInputDispatch=
0.9ms
p95InputDispatch=
1.7ms
HarmonyOS 当前全局输入监听文档也提醒:输入监听发生在 UI 线程,不应该做耗时操作。
FlexDesk 的 Command Dispatcher 仍然只做:
轻量判断
命令分发
十、监听器泄漏是多形态 App 很容易漏掉的一类资源问题
整个系列注册过:
windowSizeChange
foldStatusChange
hover / key / focus callbacks
节流 timer
单次运行没有问题,不代表反复进入页面没问题。
06 最后明确统计:
windowListenerLeak=0
foldListenerLeak=0
activeTimersAfterFinish=0
这比只看 JavaScript 堆内存更贴近本系列真正的资源风险。
十一、第三段代码专门做资源收口断言
export class AdaptiveResourceProbe {
verifyReleased(
state:
RuntimeResourceState
): boolean {
return (
state.windowListeners ===
0 &&
state.foldListeners ===
0 &&
state.pendingTimers ===
0 &&
state.transientMenus ===
0
)
}
}
每一轮测试结束都执行。
如果第 5 轮开始多一个 Window Listener:
第 5 轮直接 FAIL
不会等 40 次全部跑完才发现日志越来越多。
十二、内存回归看“稳定窗口”,不看瞬时峰值
当前基线:
128.6MB
8 轮结束:
129.8MB
变化:
+1.2MB
最大瞬时:
186.4MB
这组数据属于当前 FlexDesk 测试环境。
关键不是“186.4MB 很低”。
而是:
反复窗口 / 折叠 / 菜单 / 输入以后
稳定基线没有阶梯上升。
十三、五种 Profile 为什么不包含“所有设备”
因为回归矩阵必须可执行。
当前五种 Profile 覆盖的是本系列真正实现过的主路径:
phone
tablet
floating
foldable
PC/2in1
并不代表:
TV
wearable
car
也已经适配。
多设备适配最忌讳“同一套 ArkUI 就默认支持所有终端”。
06 只对自己真正验证过的能力负责。
十四、Code Linter 用来防止静态规则回退
除了运行时回归,FlexDesk 还会保留:
@cross-device-app-dev/sidebar-navigation
window-size-change-listener-check
one-multi-breakpoint-check
grid-columns-span
font-size
size-unit
等跨设备规则。
例如官方 sidebar-navigation 明确建议 2in1 / tablet 的 Tabs 使用侧边导航。
Linter 是静态防线。
40 次 Runtime Regression 是动态防线。
两者互补。
十五、DevEco 图只展示最终回归矩阵
开发图:

HiLog:
taskId=
adapt_accept_20261003_06
profiles=
5
cycles=
8
totalRuns=
40
passed=
40
layoutMismatch=
0
navMismatch=
0
foldMismatch=
0
focusMismatch=
0
shortcutMismatch=
0
rebuildMismatch=
0
avgLayout=
2.2ms
p95Layout=
4.6ms
avgNav=
2.6ms
p95Nav=
4.1ms
avgFold=
4.3ms
p95Fold=
6.8ms
avgInput=
0.9ms
p95Input=
1.7ms
memory=
128.6 → 129.8MB
windowListenerLeak=
0
foldListenerLeak=
0
activeTimers=
0
RESULT PASS
如果失败,会同时记录:
profile
cycle
window
fold
input
selectedTaskId
currentFocus
active listeners
而不是只输出一个 FAIL。
十六、运行图最终只回答“这五种形态现在是否一致”
最终运行图:

五个 Profile:
PHONE_TOUCH
PASS
TABLET_TOUCH
PASS
FLOATING_MOUSE
PASS
FOLDABLE_HOVER
PASS
PC_MOUSE_KEYBOARD
PASS
一致性检查:
布局 0
导航 0
折叠 0
焦点 0
快捷键 0
内容重建 0
最终:
PASS
十七、40/40 不代表“任何窗口都永远正确”
这个边界必须说清楚。
PASS 只代表:
当前 FlexDesk 版本
当前 5 个 Profile
当前 8 轮
当前项目断点与密度策略
当前测试设备
全部符合预期。
它不代表:
任意尺寸
任意外设
任意未来设备
任意语言
都已经覆盖。
工程回归真正有价值的是:
下一次修改以后,能够重复执行同一套证据链。
十八、FlexDesk 六篇真正形成的是一条“状态连续”主线
回头看整个系列:
01
窗口变化
但 TaskCard 状态不重建
02
导航变形
但选中态不重建
03
多窗口连续缩放
但内容不重建
04
折叠形态变化
但编辑状态不重建
05
输入设备变化
但业务状态不重建
06
固定矩阵
反复验证状态连续
所以这个系列真正解决的并不是:
如何做三栏布局
而是:
同一个业务对象,能不能跨窗口、跨形态、跨输入设备一直保持“还是它自己”。
十九、FlexDesk 到 06 正式结束
这个系列固定 X=6,到这里完成。
继续写 07,大概率只会继续追加:
另一个尺寸
另一个鼠标动作
另一个折叠角度
新的工程主矛盾已经不在这里。
下一轮应该切换到明显不同的技术方向和新 Demo,从新的 01 开始。
优先可以考虑:
平行视界 / EasyGo
精准碰一碰 / 跨设备投递
三方框架适配
不再继续扩写 FlexDesk。
二十、Profile 切换时还要重新验证“输入提示”有没有跟着环境变化
05 里已经把输入提示从设备类型里拆出来。
所以 06 切换:
PHONE_TOUCH
→ PC_MOUSE_KEYBOARD
时,除了业务状态不变,还要验证 UI 提示本身发生正确变化。
手机触摸模式:
不展示 Ctrl+K 提示;
不强调右键菜单。
PC 模式:
显示快捷键提示;
Hover 反馈启用;
右键菜单可达。
如果状态保持正确,但提示模式错误,也记作 Profile 失败。
多端一致不是“所有设备长得一模一样”,而是每个设备都给当前输入方式最合适的交互提示。
二十一、Focus 回归必须在每种宽度里检查“可见目标”
窗口从 920vp 缩到 480vp 后,详情面板消失。
这时详情里的保存按钮如果仍然留在焦点链里,键盘用户按 Tab 会进入一个“看不见的按钮”。
所以回归断言不仅检查:
focusMoves 能走
还要检查:
focused target 当前可见;
不可见区域不参与 tabStop。
FOLDABLE_HOVER 也一样。
上半区和下半区变化以后,焦点顺序必须匹配当前可见布局。
这类问题截图通常看不出来,必须靠运行时断言。
二十二、窗口监听和折叠监听要按页面 / Ability Owner 对账
FlexDesk 既有:
windowSizeChange
又有:
foldStatusChange
它们的 Owner 不一样。
Window Listener 属于窗口 / Ability 生命周期。
Fold Listener 属于折叠协调器生命周期。
06 不只统计:
总监听器数量=0
还保存:
windowListenerOwner
foldListenerOwner
防止一种更隐蔽的问题:
A 页面把 B 页面的监听器 off 掉。
资源释放正确,还必须释放“自己拥有的那一个”。
二十三、定时器回归要覆盖节流器被页面中途销毁的场景
03 的 WindowEventCoalescer 使用:
48ms timer
正常拖窗结束以后 Timer 会自然执行。
但真实用户可能在拖动窗口时立即:
关闭页面
切换项目
返回上一级
06 专门加入:
pending timer
→ 页面 dispose
→ timer 被 clear
的 case。
最终:
activeTimersAfterFinish=0
才能说明节流逻辑自己的生命周期也闭环。
二十四、折叠 Profile 要同时覆盖 HALF_FOLDED 和恢复后的宽屏状态
FOLDABLE_HOVER 不只是:
720×720
HALF_FOLDED
一次截图。
完整 case 是:
FOLDED
→ EXPANDED
→ HALF_FOLDED
→ EXPANDED
→ FOLDED
然后再进入下一轮。
这样才能验证:
draft
cursor
scroll
detail state
不是只在进入 HOVER 时没丢,而是在来回切换后仍然一致。
06 仍然沿用 04 的 5/5 retained 思路,只是把它放进自动矩阵。
二十五、回归矩阵也要有“环境能力不支持”的 SKIP 语义
未来某台测试设备不支持折叠状态或没有鼠标键盘环境时,不能把:
FOLDABLE_HOVER
直接判成产品 FAIL。
更合理的是:
SUPPORTED
→ 必须 PASS
UNSUPPORTED
→ SKIPPED_CAPABILITY
ERROR
→ FAIL
本轮五种 Profile 的测试环境都准备完毕,所以:
5 profiles
40 runs
40/40 passed
没有出现 Skip。
但 Runner 的状态模型已经把能力缺失和真正失败分开。
二十六、静态 Linter 通过也不能替代运行时状态验证
Code Linter 能告诉我们:
有没有监听 windowSizeChange;
tablet Tabs 是否考虑侧栏;
单位和断点写法是否合理。
但它看不到:
task_1042 有没有丢;
cursor=96 有没有归零;
右键菜单关闭后焦点去了哪里;
折叠后 Timer 有没有泄漏。
所以 FlexDesk 最终保留两道门:
Code Linter
检查实现规则;
Runtime Regression
检查真实行为。
两边都通过,才是“多形态适配完成”。
二十七、内存曲线 +1.2MB 也要放到下一版本继续观察
从:
128.6MB
到:
129.8MB
本轮没有阶梯式增长。
但 +1.2MB 也不会直接被描述成“绝对没有任何泄漏”。
系统缓存、GC 时机、图片资源都可能造成轻微变化。
真正有价值的是下一次继续按同样协议采样。
如果变成:
128.6
→ 136
→ 145
→ 154
即使单轮功能还是 40/40,也必须进入资源回归失败。
二十八、最终 PASS 由四类 Gate 共同组成
FlexDesk 不把:
40/40
单独作为最终 PASS。
真实门禁拆成:
FUNCTION_GATE
布局 / 导航 / 折叠 / 输入行为正确
STATE_GATE
project / task / draft / cursor / scroll 连续
PERFORMANCE_GATE
关键提交延迟在项目基线内
RESOURCE_GATE
listener / timer / 稳定内存可收口
四类都通过才:
status=PASS
这样后续哪一类出问题,报告能直接告诉开发者从哪里开始查。
二十九、每次 FAIL 都必须保存可复现的窗口和输入上下文
多形态 Bug 最大的问题是:
“刚才在某个大小的时候闪了一下”
很难复现。
所以 Runner 对失败项必须保存:
profile
cycle
widthVp
heightVp
foldStatus
inputProfile
selectedIndex
taskId
focusId
draftChars
activeListeners
pendingTimers
下一次开发者可以直接把窗口恢复到相同状态。
真正好的回归系统,不只是告诉你“失败了”,还要告诉你“怎么回到失败现场”。
三十、FlexDesk 六篇最终留下的是一个可持续演进的适配底座
系列收口以后,真正能复用的不是六张截图。
而是六个长期模块:
WindowSizeStore
BreakpointResolver
DensityPolicy
NavSelectionStore
FoldContinuityCoordinator
DesktopInputCoordinator
以及最后的:
FlexDeskRegressionRunner
以后新增另一个业务页面,只要接入这些状态源和策略层,不需要重新从“手机 / 平板 / PC 各写一份页面”开始。
这才是多形态工程真正能够规模化的地方。
三十一、系列结束后,回归基线仍然需要版本化
这次的 40/40 不是一份永远不变的答案。FlexDesk 会把断点策略、输入 Profile、测试数据和阈值一起写入 acceptanceVersion。以后如果侧栏宽度、折叠策略或快捷键方案发生调整,新版本应该生成新的基线,而不是硬拿旧数字继续比较。这样“性能变化”和“设计策略变化”不会混在一起,也能明确知道某个回归结果对应哪一代多形态方案。
因此最终验收产物不仅是一张 PASS 截图,还应该包含可重复执行的配置、指标与状态快照,让下一次修改之后能用同一套方法重新证明:FlexDesk 在手机、平板、浮窗、折叠屏和 PC/2in1 上仍然是同一个应用、同一份状态、同一套业务逻辑。
参考资料
- Keyboard and Mouse Event:
https://developer.huawei.com/consumer/en/doc/harmonyos-guides-V13/arkts-common-events-device-input-event-V13 - 组件快捷键:
https://developer.huawei.com/consumer/cn/doc/harmonyos-references-v5/ts-universal-events-keyboardshortcut-V5 - 焦点控制:
https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V14/ts-universal-attributes-focus-V14 - sidebar-navigation Code Linter:
https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/ide_sidebar-navigation - 全局基础输入事件监听:
https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/api/ts-inputeventmonitor
更多推荐
所有评论(0)