
一、前言
AR 会话是 BodyAR 应用的"发动机"------配置不当,轻则追踪不准,重则直接崩溃。然而在实际开发中,许多开发者往往只关注如何拿到骨骼数据,而忽略了会话配置和生命周期管理的重要性。
本文将逐项解析 ARConfig 的每个字段,深入 AR 会话的 init→pause→resume→destroy 全生命周期,并结合实战经验总结最佳实践。
二、ARViewContext:比 ARSession 更高级的抽象
2.1 架构层次
ARView (ArkUI 声明式组件)
↓ 绑定
ARViewContext (上层封装:场景 + 配置 + 回调 + 会话)
↓ 管理
ARSession (底层引擎:相机流 + IMU + NPU 推理)
↓ 驱动
HAL 层 (Camera + IMU + NPU Driver)
ARViewContext 封装了 AR 会话的完整上下文,不仅包含 ARSession,还关联了 3D 渲染场景、配置参数和视图回调。它是一个门面(Facade)------对外暴露简洁的配置接口,对内协调多个底层子系统的协同工作。
2.2 创建序列
typescript
// 步骤 1:加载 3D 场景(AR 渲染的基础)
const scene = await Scene.load();
// 步骤 2:创建上下文
const vc = new arViewController.ARViewContext();
vc.scene = scene;
// 步骤 3:配置参数
vc.config = { /* ARConfig */ };
// 步骤 4:设置回调
vc.callback = new MyCallback();
// 步骤 5:初始化和启动
await vc.init();
步骤顺序至关重要------config 和 callback 必须在 init() 之前设置 ,且 Scene.load() 和 vc.scene 的赋值也是 init() 的前置条件。顺序错误会导致 init() 抛出异常。
三、ARConfig 字段逐项详解
3.1 type:追踪模式
typescript
type: arEngine.ARType.BODY // 人体骨骼追踪模式
可选值:
| 枚举 | 模式 | 返回数据类型 |
|---|---|---|
ARType.BODY |
人体骨骼追踪 | ARBody[] |
ARType.FACE |
人脸识别 | ARFace[] |
ARType.WORLD |
世界追踪(平面检测) | ARPlane[] |
ARType.IMAGE |
图像识别 | ARImage[] |
重要限制:一个 AR 会话只能使用一种追踪模式。如果需要同时识别平面和人体,目前只能通过两个独立会话实现,但这对设备性能是巨大挑战,实际生产中不推荐。
3.2 maxDetectedBodyNum:最大追踪人数
typescript
maxDetectedBodyNum: 1 // 1 或 2
取 1 时 NPU 全算力聚焦单一目标,追踪质量和帧率最优。取 2 时支持同时追踪两人,但每人的追踪精度会略有下降。在非竞技场景下,推荐保持 1。
3.3 powerMode:功耗模式
| 模式 | 枚举值 | 帧率 | NPU 负载 | 电池消耗 | 适用场景 |
|---|---|---|---|---|---|
| 标准 | NORMAL |
~30fps | 高 | 高 | 实时交互应用(体感游戏) |
| 省电 | POWER_SAVING |
~20fps | 中 | 中 | 持续监测(健身跟踪) |
| 超级省电 | ULTRA_POWER_SAVING |
~10fps | 低 | 低 | 后台姿态识别(跌倒检测) |
功耗模式的选择直接关系到用户体验------对于体感游戏,30fps 的流畅感至关重要;对于健身计数器,20fps 完全满足动作检测需求,同时能显著延长续航。
3.4 cameraLensFacing:摄像头方向
typescript
cameraLensFacing: 0 // 0 = 后置摄像头(推荐用于 Body Tracking)
cameraLensFacing: 1 // 1 = 前置摄像头
前置 vs 后置的选择策略:
| 维度 | 后置摄像头 (0) | 前置摄像头 (1) |
|---|---|---|
| Body Tracking 支持 | 全设备支持 | 部分设备不支持 |
| 追踪精度 | 高 | 中(受限于距离和视角) |
| 用户可见性 | 需要他人辅助 | 用户本人可见 |
| 推荐场景 | 健身教学、动作分析 | 自拍模式、单人确认 |
强烈推荐默认使用后置摄像头------在多个麒麟 9000 系列设备上的实测中,后置追踪的精度和稳定性均显著优于前置。
3.5 focusMode:对焦模式
typescript
focusMode: arEngine.ARFocusMode.AUTO // 自动对焦
focusMode: arEngine.ARFocusMode.FIXED // 固定焦点
Body Tracking 场景下推荐使用 AUTO,因为用户会前后移动,自动对焦能适应变化的距离。FIXED 适用于物体追踪等固定距离场景。
3.6 不需要的字段
以下字段在 Body 模式下不需要开启 ,设为 DISABLED 或 NONE 可节省功耗:
typescript
{
planeFindingMode: arEngine.ARPlaneFindingMode.DISABLED, // 不检测平面
semanticMode: arEngine.ARSemanticMode.NONE, // 不做语义分割
depthMode: arEngine.ARDepthMode.DISABLED, // 不计算深度
meshMode: arEngine.ARMeshMode.DISABLED, // 不重建网格
poseMode: arEngine.ARPoseMode.GRAVITY, // 仅重力方向
}
3.7 完整配置模板
typescript
const bodyConfig: arEngine.ARConfig = {
// === 核心 ===
type: arEngine.ARType.BODY,
maxDetectedBodyNum: 1,
// === 相机 ===
cameraLensFacing: 0, // 后置
focusMode: arEngine.ARFocusMode.AUTO,
// === 功耗 ===
powerMode: arEngine.ARPowerMode.NORMAL,
// === 不需要的功能(全部关闭)===
planeFindingMode: arEngine.ARPlaneFindingMode.DISABLED,
semanticMode: arEngine.ARSemanticMode.NONE,
depthMode: arEngine.ARDepthMode.DISABLED,
meshMode: arEngine.ARMeshMode.DISABLED,
poseMode: arEngine.ARPoseMode.GRAVITY,
};
四、生命周期管理
4.1 完整生命周期图
Scene.load() // 加载 3D 场景
↓
new ARViewContext() // 创建上下文
↓
config + callback // 配置参数 + 回调
↓
init() // 启动 AR 会话 ──┐
↓ │
[运行中] ← 30fps 帧回调 │
↓ │
pause() ─────────→ resume() │ 前后台切换
↓ │
destroy() // 销毁会话 ←───┘
4.2 初始化阶段
typescript
async initializeSession(): Promise<arViewController.ARViewContext> {
// 前置条件检查
if (this.viewContext) {
await this.destroySession(); // 先清理旧会话
}
this.scene = await Scene.load();
const vc = new arViewController.ARViewContext();
vc.scene = this.scene;
vc.config = this.buildConfig();
vc.callback = this.buildCallback();
await vc.init(); // 异步启动,可能抛异常
this.viewContext = vc;
return vc;
}
init() 阶段的常见异常码:
| 错误码 | 名称 | 根因 | 修复方向 |
|---|---|---|---|
| 201 | AR_ERROR_SESSION_NOT_CONFIGURED |
权限不足 | 检查 3 个权限的声明和授权状态 |
| 301 | AR_ERROR_SESSION_PAUSED |
AR Engine 未安装 | AppGallery 安装/更新 AR Engine |
| 401 | AR_ERROR_TEXTURE_NOT_SET |
设备不支持 | 芯片不满足最低要求 |
| 402 | AR_ERROR_UNSUPPORTED_CONFIGURATION |
配置非法 | 检查 ARConfig 字段组合 |
4.3 前后台切换
应用切换到后台时,AR 会话应该暂停以释放相机和 NPU 资源。回到前台时再恢复。
最佳实践是将 pause/resume 绑定到页面的生命周期事件:
typescript
@Entry
@Component
struct BodyARPage {
private engine = new BodyAREngine();
// 页面每次显示时
onPageShow(): void {
this.engine.resume();
}
// 页面每次隐藏时
onPageHide(): void {
this.engine.pause();
}
// 页面销毁时(只调用一次)
aboutToDisappear(): void {
this.engine.stop(); // == destroy
}
}
暂停和恢复的实现:
typescript
pause(): void {
if (!this.viewContext) return;
try {
this.viewContext.pause();
// 相机和 NPU 资源被释放,但会话上下文保留
} catch (error) {
const err = error as BusinessError;
console.error(`pause: ${err.code} ${err.message}`);
}
}
resume(): void {
if (!this.viewContext) return;
try {
this.viewContext.resume();
// 恢复相机和 NPU,从上次状态继续追踪
} catch (error) {
const err = error as BusinessError;
console.error(`resume: ${err.code} ${err.message}`);
}
}
4.4 销毁阶段
应用退出时必须彻底释放 AR 会话资源:
typescript
async stop(): Promise<void> {
if (!this.viewContext) return;
try {
this.viewContext.destroy();
// destroy() 释放:
// - ARSession(相机 + IMU + NPU)
// - 帧缓冲区
// - 内部线程和回调注册
} catch (error) {
const err = error as BusinessError;
console.error(`destroy: ${err.code} ${err.message}`);
}
this.viewContext = null;
this.scene = null; // Scene 引用也置空
}
4.5 切换摄像头
切换摄像头必须完整重启会话------因为摄像头方向和分辨率的变化会影响 AR 引擎的初始化参数:
typescript
async switchCamera(): Promise<void> {
// 步骤 1:先卸载 ARView 组件
this.arContext = null; // ★ 关键:让 UI 先卸载
// 步骤 2:销毁旧会话
this.engine.stop();
// 步骤 3:切换标志
this.frontCamera = !this.frontCamera;
// 步骤 4:启动新会话
this.arContext = await this.engine.start();
}
为什么必须先 this.arContext = null?因为 destroy() 会释放 ARView 引用的底层资源。如果 ARView 还挂着旧 context 引用就去 destroy,可能在渲染线程产生空指针访问。先置空确保 ARView 组件先卸载,再安全销毁会话。
五、异常处理策略
5.1 分层错误处理
用户操作 → BodyARPage(UI 层)
→ BodyAREngine(逻辑层)
→ ARViewContext(系统 API 层)
→ AR Engine Service(系统服务层)
每一层都要有 try-catch
UI 层 :捕获异常后展示用户可读的提示
逻辑层 :捕获异常后判断是否可恢复(如权限问题可引导用户,设备不支持则需要降级)
API 层:捕获系统异常,记录详细错误码和堆栈
六、性能监控
6.1 帧率监控
在 onFrameUpdate 中记录时间戳计算实际帧率:
typescript
private lastFrameTs: number = 0;
private frameCount: number = 0;
onFrameUpdate(ctx, sysBootTs): void {
if (this.lastFrameTs > 0) {
const delta = sysBootTs - this.lastFrameTs;
// delta 约 33ms 对应 30fps
}
this.lastFrameTs = sysBootTs;
this.frameCount++;
}
6.2 内存监控
Scene.load() 会分配 3D 渲染资源(纹理、shader 编译等),在切换摄像头或重启会话时,旧 Scene 的引用应及时置空以触发 GC。
Scene 对象没有显式的
release()方法,依赖 JS 垃圾回收。在频繁创建/销毁会话的场景下,注意控制频率。
七、小结
AR 会话管理是 BodyAR 开发中最容易被忽视但最容易出问题的环节。核心要点:
- init 前置条件:Scene 加载 → 3 个权限就绪 → config 和 callback 已设置
- 生命周期绑定:pause/resume 绑定页面显隐,destroy 绑定页面销毁
- 摄像头切换:先卸载 ARView → 销毁旧会话 → 创建新会话,三步必须严格顺序
- 异常处理:分层 try-catch,UI 层展示用户提示,逻辑层判断可恢复性
- 性能意识:NORMAL 模式适合交互应用,POWER_SAVING 适合持续监测
会话管理就绪后,最核心的工作就是从每帧的 ARFrame 中提取原始骨骼数据,经过校验和坐标转换,变成可用的结构化数据。