前两篇一直在解决“重建开始之前”的问题。

01 先把 HMS_SpatialRecon_DataFrame 的输入规格做稳:1440×1920 统一到 1080×1440,内参同步缩放,48 帧完整推入 Session;02 又在采集链上加了 Frame Gate,把 72 帧原始输入筛成 54 个有效视角,重复、模糊、低基线和位姿跳变都有单独原因。

第三篇终于进入 StartSession 之后。

真正跑端侧 3DGS 时,我发现最难的并不是“进度条怎么写”,而是 Session 在重建阶段会遇到三种现实状态:

应用从前台切到后台;
设备热状态升高,需要主动暂停;
用户回到前台以后继续任务。

Spatial Recon Kit 当前 C API 已经给出了对应能力:HMS_SpatialRecon_SetRunningMode 用来切换前后台运行模式,HMS_SpatialRecon_PauseSession / ResumeSession 用来真正暂停和恢复 Session,HMS_SpatialRecon_GetProgress 同时返回 0~1 的进度与 HMS_SpatialReconStage 阶段。官方说明还明确要求 SetRunningMode 在 StartSession 之后、重建完成之前使用。

所以 SceneForge 03 不把这三个接口混成一个“暂停按钮”,而是分别处理:

运行模式变化、业务暂停、进度阶段。

本轮统一数据:

taskId:
recon_20261003_03

sessionId:
sf_session_20261003_03

scene:
tabletop_tea_set_02

acceptedFrames:
54

startProgress:
7.6%

backgroundAt:
31.4%

runningMode:
FOREGROUND → BACKGROUND → FOREGROUND

thermalGuardLevel:
5
(SceneForge 项目策略)

pauseAt:
46.2%

pauseDuration:
18s

pauseCount:
1

resumeCount:
1

foregroundReturnAt:
68.5%

modeSwitches:
2

getProgressFailures:
0

totalBuildCost:
94.8s

finalProgress:
100%

finalStage:
FINISHED

status:
RECON_FINISHED

一、SetRunningMode 和 PauseSession 不是一回事

这两个接口非常容易被写成一个概念。

应用切到后台时,我最初的处理是:

onBackground
→ PauseSession

功能上没错,但并不符合 SceneForge 的产品需求。

用户只是回了一条消息,不代表一定要把模型重建完全停掉。

HMS_SpatialReconRunningMode 当前区分:

FOREGROUND
系统为重建分配更积极的资源;

BACKGROUND
系统优先前台应用,重建速度相对慢。

所以 SceneForge 的默认策略改成:

普通前后台切换
→ SetRunningMode

真正需要停止计算
→ PauseSession

这样 Session 生命周期会清楚很多。

二、onBackground 只切运行模式,不重新创建 Session

当前任务在:

31.4%

时进入后台。

这段代码解决的问题是:Session 已经进入 BUILDING 后,应用生命周期发生变化,如何让 Native Session 延续同一任务。

void SpatialReconLifecycleController::OnBackground()
{
    if (session_ == nullptr ||
        stage_ == SPATIAL_RECON_STAGE_FINISHED) {
        return;
    }

    HMS_SpatialReconStatus ret =
        HMS_SpatialRecon_SetRunningMode(
            session_,
            SPATIAL_RECON_RUNNING_BACKGROUND_MODE);

    if (ret == SPATIAL_RECON_STATUS_SUCCESS) {
        mode_ = SPATIAL_RECON_RUNNING_BACKGROUND_MODE;
        modeSwitches_++;
    }
}

这里没有:

DestroySession
CreateSession
重新 PushFrame

因为 54 个 Accepted Frame 已经完成输入。

03 只管理同一个正在重建的 Session。

三、返回前台时同样只是切回 FOREGROUND

应用恢复到前台时:

progress=68.5%

项目层调用:

void SpatialReconLifecycleController::OnForeground()
{
    if (session_ == nullptr ||
        stage_ == SPATIAL_RECON_STAGE_FINISHED) {
        return;
    }

    HMS_SpatialReconStatus ret =
        HMS_SpatialRecon_SetRunningMode(
            session_,
            SPATIAL_RECON_RUNNING_FOREGROUND_MODE);

    if (ret == SPATIAL_RECON_STATUS_SUCCESS) {
        mode_ = SPATIAL_RECON_RUNNING_FOREGROUND_MODE;
        modeSwitches_++;
    }
}

本轮:

modeSwitches=2

刚好对应:

Foreground → Background
Background → Foreground

运行模式变化没有生成新 sessionId,也没有改变 taskId。

四、热保护才真正触发 PauseSession

SceneForge 还订阅设备热状态。

本轮项目策略是:

thermalGuardLevel >= 5
→ PauseSession

这里的 5 是当前 Demo 的工程阈值,不是 Spatial Recon Kit 官方固定值。

重建进度到:

46.2%

时触发热保护:

void SpatialReconLifecycleController::OnThermalLevelChanged(
    int level)
{
    if (level < thermalGuardLevel_) {
        return;
    }

    if (stage_ != SPATIAL_RECON_STAGE_BUILDING) {
        return;
    }

    HMS_SpatialReconStatus ret =
        HMS_SpatialRecon_PauseSession(session_);

    if (ret == SPATIAL_RECON_STATUS_SUCCESS) {
        stage_ = SPATIAL_RECON_STAGE_PAUSED;
        pauseCount_++;
        pauseStartedAt_ = NowMs();
    }
}

这一步和后台模式不同。

进入 PAUSED 后,重建真正停止推进。

五、18 秒以后 Resume,不重走 StartSession

设备冷却以后:

pauseDuration=18s

SceneForge 调用:

ResumeSession

而不是再次 StartSession。

因为当前 Session 已经有完整的内部重建状态。

如果重新 Start,会让业务层无法判断是“恢复”还是“重新开始”。

本轮:

pauseCount=1
resumeCount=1

两边严格成对。

六、GetProgress 必须同时看 progress 和 stage

我一开始只给 UI 一个百分比:

46.2%

然后发现暂停以后,百分比保持不动,用户会误以为:

应用卡死了。

所以 03 的 Snapshot 从:

progress

升级成:

progress + stage + runningMode

官方当前 HMS_SpatialReconStage 至少包含:

INIT
BUILDING
PAUSED
FINISHED
SAVING
UNKNOWN

于是页面可以准确展示:

46.2%
PAUSED
热保护暂停 18s

而不是“46.2% 一直不动”。

七、进度采样器不要在页面里直接调用 Native API

SceneForge 让 Native Manager 自己轮询:

void ProgressSampler::Sample()
{
    float progress = 0.0f;
    HMS_SpatialReconStage stage =
        SPATIAL_RECON_STAGE_UNKNOWN;

    HMS_SpatialReconStatus ret =
        HMS_SpatialRecon_GetProgress(
            session_,
            &progress,
            &stage);

    if (ret != SPATIAL_RECON_STATUS_SUCCESS) {
        getProgressFailures_++;
        return;
    }

    snapshot_.progress = progress * 100.0f;
    snapshot_.stage = stage;
}

ArkTS 页面只消费 Snapshot。

这样页面切换、重组、重绘都不会直接干预 Session。

本轮:

getProgressFailures=0

八、PAUSED 状态下不要继续假装倒计时

产品里经常会写:

预计剩余 48 秒

但重建一旦进入 PAUSED,剩余时间估算立刻失效。

所以 SceneForge 的 UI 规则:

BUILDING
→ 可以显示趋势估算

PAUSED
→ 只显示暂停原因与暂停时长

SAVING
→ 改成保存阶段进度

03 当前停在重建阶段,不把 04 的保存进度混进来。

九、前后台切换时不要修改 stage

SetRunningMode(BACKGROUND) 成功以后,Session 仍可能处于:

SPATIAL_RECON_STAGE_BUILDING

所以:

BACKGROUND

是运行模式,不是 HMS_SpatialReconStage。

这个区分非常重要。

SceneForge 快照里分别保存:

stage=BUILDING
runningMode=BACKGROUND

而不是发明一个:

stage=BACKGROUND

这样后续 Save 阶段也不会乱。

十、为什么热保护暂停后仍然允许后台模式

如果热状态是在后台发生:

runningMode=BACKGROUND
stage=PAUSED

两者仍然可以同时成立。

Resume 以后会继续沿用当前 runningMode。

也就是说:

Pause / Resume
控制任务是否推进;

RunningMode
控制系统按前台还是后台方式调度。

这两个维度真正拆开以后,生命周期代码反而更简单。

十一、最终 FINISHED 以后不再允许 Pause / Resume / SetRunningMode

本轮总重建耗时:

94.8s

最终:

progress=100%
stage=FINISHED
status=RECON_FINISHED

SceneForge 状态机一旦进入 FINISHED:

Pause
Resume
SetRunningMode

项目层都直接拒绝。

后面唯一允许的主动作变成:

SaveResultToFile

这正好进入 04。

十二、DevEco 图里把“运行模式”和“暂停”分开看

开发图:

HiLog:

taskId=
recon_20261003_03

start session
acceptedFrames=54

progress=
7.6%
stage=BUILDING

app background

mode=BACKGROUND

progress=
31.4%

thermal level=5

pause at=
46.2%

pauseCount=
1

resume after=
18s

foreground

progress=
68.5%

modeSwitches=
2

progress=
100%

stage=
FINISHED

totalBuildCost=
94.8s

status=
RECON_FINISHED

这条日志真正证明的是:

同一个 Native Session 经历了前后台、暂停、恢复,最终仍然完整结束。

十三、手机运行图不再展示采集帧,而是展示 Session 时间线

最终运行图:

顶部是同一个:

tabletop_tea_set_02

中间生命周期:

BUILDING 7.6%

BACKGROUND 31.4%

PAUSED 46.2%

RESUME

FOREGROUND 68.5%

FINISHED 100%

右侧还有:

thermalGuardLevel=5
modeSwitches=2
totalBuildCost=94.8s

最终:

RECON_FINISHED

这张图和 01 / 02 的“输入帧质量”已经是完全不同的工程阶段。

十四、GetProgress 在 04 还会继续用,但口径会变

官方文档当前明确说明:

当调用 HMS_SpatialRecon_SaveResultToFile 以后,GetProgress 返回的进度反映的是文件保存进度,不再是整个重建进度。

所以 03 在 FINISHED 停止构建进度统计。

04 开始保存时,会重新记录:

saveProgress
stage=SAVING

两个阶段不能共用一条“总进度曲线”。

十五、热策略只是项目策略,不能写成系统固定推荐值

SceneForge 当前:

level >= 5
pause

这来自本轮设备和 Demo 的取舍。

真正产品应该结合:

设备型号
壳温
电量
用户场景
重建耗时
系统热事件

重新验证。

文章里只保留“热状态升高时主动 Pause”的工程思路,不把 5 级写成官方阈值。

十六、Session 生命周期必须由一个 Owner 管

如果:

页面 A
可以 Pause

页面 B
可以 Destroy

后台事件
可以 SetRunningMode

热事件
又可以 Resume

这些动作分散在多个对象里,状态会非常难控。

SceneForge 统一由:

SpatialReconLifecycleController

持有 Session。

其他模块只能发送:

Intent

例如:

APP_BACKGROUND
THERMAL_HIGH
THERMAL_RECOVERED
APP_FOREGROUND

Controller 根据当前 stage 决定能不能执行。

十七、03 最后固定八组状态测试

第一组,BUILDING 时切后台,只切 RunningMode。

第二组,后台返回前台,仍然同一个 sessionId。

第三组,热阈值触发 Pause,stage 进入 PAUSED。

第四组,PAUSED 再 Pause,项目层不重复调用。

第五组,Resume 成功,pause / resume 成对。

第六组,FINISHED 后 SetRunningMode 不再执行。

第七组,GetProgress 失败只记录 DFX,不擅自 Destroy Session。

第八组,最终 100% / FINISHED 与回调状态一致。

全部通过以后:

RECON_FINISHED

才有意义。

十八、下一篇终于进入“把模型真正写出来”

03 完成以后,SceneForge 手里已经有:

一个 FINISHED 的 3DGS Session

但用户还没有可分享、可加载的文件。

04 会继续同一个 sf_session_20261003_03,分别保存:

PLY
3DGS 模型

MP4
环绕预览视频

并处理:

ModelWriteInfo
保存进度
并发保存阻断
文件清单
Session Destroy

整个系列会从“重建成功”正式进入“结果落盘”。

十九、业务暂停和热保护暂停必须带上 pauseReason

如果只保存:

stage=PAUSED

UI 根本不知道该显示:

用户暂停
还是设备热保护
还是系统资源策略

SceneForge 因此在项目快照里增加:

pauseReason

当前枚举:

USER
THERMAL_GUARD
SYSTEM_POLICY
UNKNOWN

本轮:

pauseReason=THERMAL_GUARD

这条信息不会传给 Spatial Recon Kit,它只是 SceneForge 自己的 DFX 语义。

真正调用的仍然是同一个:

HMS_SpatialRecon_PauseSession

但产品体验完全不同。

用户主动暂停时,页面应该显示:

已手动暂停

热保护暂停时则显示:

设备温度较高,已暂缓重建

把底层状态和业务原因拆开,后面统计“为什么任务耗时变长”时也更容易。

二十、热事件需要去抖,不能 level 上下一跳就反复 Pause/Resume

设备温度不是一个永远单调的值。

如果:

5 → 4 → 5 → 4

每次变化都立刻调用:

Pause
Resume
Pause
Resume

Session 会被频繁打断。

SceneForge 当前策略是:

触发 Pause 后
至少保持一个冷却窗口

连续满足恢复条件
再 Resume

本轮为了让测试数据可重复,冷却窗口固定:

18s

这 18 秒不是系统要求,只是当前 Demo 的测试策略。

真正产品可以结合:

热等级
温度趋势
电量状态
任务剩余进度

动态调整。

二十一、进度采样不能太频繁

GetProgress 是 Native API。

如果 ArkTS 页面每一帧都请求:

60 次 / 秒

没有必要。

SceneForge 当前 ProgressSampler 使用低频采样,例如:

500ms~1000ms

区间内刷新一次 UI。

真正状态变化,例如:

BUILDING → PAUSED
PAUSED → BUILDING
BUILDING → FINISHED

则立即推送。

这样可以做到:

阶段切换及时
百分比更新平滑
Native 调用不过度

页面也不会因为百分比频繁变化导致大量重组。

二十二、进度回退需要记录异常,不直接修正成“最大值”

重建进度正常情况下应该整体向前。

但如果某次 GetProgress 读到:

48.1%
下一次 47.6%

SceneForge 不会直接写:

progress = max(old, new)

因为那样虽然 UI 看起来更顺,却把异常隐藏了。

项目策略是:

保留原始 progress
记录 PROGRESS_REGRESSION
UI 仍可做视觉平滑

这样 DFX 能看到真实系统返回值。

“显示平滑”和“数据真实”不能用一个变量解决。

二十三、最终回调和 GetProgress FINISHED 要互相验证

StartSession 有完成回调。

GetProgress 又能返回:

stage=FINISHED
progress=100%

SceneForge 不把其中任意一个当成唯一真相。

当前验收要求:

完成回调 SUCCESS

并且
最近一次 stage=FINISHED

并且
progress 接近 100%

三者一致以后:

status=RECON_FINISHED

如果回调成功但 stage 仍然 BUILDING:

CALLBACK_STAGE_MISMATCH

先记录异常,不马上进入保存阶段。

这种“双证据”设计会让 04 的 SaveResultToFile 更安全。

二十四、前台返回时不要无条件 Resume

一种常见写法是:

onForeground
→ ResumeSession
→ SetRunningMode(FOREGROUND)

但如果 Session 从来没有 Pause:

ResumeSession

反而会得到错误。

SceneForge 返回前台时先看:

stage
pauseReason

只有:

stage=PAUSED
并且
pauseReason 属于可自动恢复类型

才 Resume。

普通后台模式返回前台只调用:

SetRunningMode(FOREGROUND)

这也是为什么本轮:

resumeCount=1

而不是每次 foreground 都 +1。

二十五、用户手动 Pause 后,热保护恢复不能替用户 Resume

假设用户自己点:

暂停

与此同时设备温度也升高。

过一会温度恢复,SceneForge 不应该:

自动 Resume

因为用户暂停意图仍然存在。

所以项目快照还要保存:

userPauseLatch

恢复策略优先级:

USER pause
> THERMAL pause
> background mode

只有阻塞原因全部解除,Session 才真正继续。

本轮没有用户手动暂停,因此:

userPauseLatch=false

这条边界留到后续真实产品时直接可用。

二十六、Session 运行期间不能启动第二个重建任务

官方社区实践已经明确提醒:

同一时刻只允许一个重建 Session 运行

SceneForge 项目层也做同样的互斥。

如果当前:

sf_session_20261003_03
stage=BUILDING

用户又创建新任务:

RECON_SESSION_BUSY

直接阻止。

这不是 UI 上把按钮置灰就结束。

Native Manager 自己也要有:

activeSessionId

作为最终防线。

二十七、FINISHED 后不马上 Destroy,是因为 04 还需要 Session

这个生命周期很关键。

重建完成:

FINISHED

不等于:

DestroySession

因为 04 还要调用:

SaveResultToFile

所以 03 的终态是:

RECON_FINISHED
Session 仍然存在

真正 Destroy 会延后到:

PLY / MP4 保存完成
Manifest 写入完成

之后。

这样两篇文章虽然分开,Session 生命周期仍然是同一条工程主线。

二十八、03 最终形成的是一张二维状态表

SceneForge 到这一篇已经不再用一个字符串描述所有状态。

真正决定行为的是:

Stage
×
RunningMode
×
PauseReason

例如:

BUILDING + FOREGROUND + NONE

BUILDING + BACKGROUND + NONE

PAUSED + BACKGROUND + THERMAL_GUARD

FINISHED + FOREGROUND + NONE

每一种组合允许的操作不同。

这比“status=RUNNING / STOPPED”更适合长期维护。

下一篇保存阶段再增加:

operation=SAVING_PLY / SAVING_MP4

仍然不会破坏这套模型。

二十九、生命周期日志要保存“动作前状态”和“动作后状态”

像 Pause / Resume 这种操作,如果日志只写:

pause success

排查价值很有限。

SceneForge 最终日志结构会同时记录:

beforeStage
beforeMode
action
result
afterStage
afterMode
progress
reason

例如本轮热保护事件能完整还原成:

BUILDING / BACKGROUND
→ THERMAL_PAUSE
→ PAUSED / BACKGROUND
→ 46.2%

18 秒后:

PAUSED / BACKGROUND
→ RESUME
→ BUILDING / BACKGROUND

这种日志非常适合后面做自动回归。即使用户反馈“切后台以后任务不动了”,也能直接判断到底是运行模式切换、热策略暂停,还是 Progress API 本身异常。

三十、RECON_FINISHED 只代表“重建阶段收口”,不是整个业务任务结束

SceneForge 的总任务状态继续细分:

RECON_RUNNING
RECON_FINISHED
RESULT_SAVING
RESULT_READY

03 最终的 RECON_FINISHED 只是说明模型计算已经完成。

用户真正可以离开任务页面、分享或浏览模型,还要等 04 的结果保存。

把“算法完成”和“业务完成”分开以后,UI 就不会在 100% 时立刻弹出“全部完成”,却随后又等几秒写文件。

这个细节在长任务体验里很重要,也让后面的 PLY/MP4 保存成为自然延续,而不是突然增加的一段流程。

参考资料

  • spatial_recon_interface.h
    https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-spatial-recon-interface-h
  • Spatial Recon Kit 空间计算能力
    https://developer.huawei.com/consumer/cn/features/spatialization
  • HMS_SpatialRecon_ModelWriteInfo
    https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/api/capi-spatialrecon-hms-spatialrecon-modelwriteinfo
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐