企业项目实施工程师视角:本文从现场采集系统上线前的验收需求出发,讲解如何在 HarmonyOS 6.1.1 中建立完整的预检体系,通过权限、设备、预览Surface与会话条件的多层检查,确保相机功能真实可用,并为现场故障诊断和远程支持奠定基础。


一、企业场景与挑战

1.1 相机系统上线前的验收困局

在实际部署中,相机功能的上线验收往往面临以下困境:

  • 现场条件差异大:不同型号手机、不同Android版本、不同定制系统的相机能力不一致
  • 权限管理复杂:用户可能在运行时撤销权限,系统设置中禁用相机,或企业MDM策略限制相机使用
  • 设备识别困难:某些设备无后摄,某些设备的预览Profile不完整,某些设备的焦点模式受限
  • 会话启动隐蔽失败:相机会话看起来启动了,但实际上焦点、自动构图等能力可能不生效
  • 远程支持困难:现场人员说"相机不能用",但无法提供诊断信息,导致远程定位问题困难

表面现象:“现场验收时相机功能可用,上线后却出现间歇性故障"或"A地能用,B地不能用”

根本原因:缺少系统化的预检体系,无法区分"硬件/系统不支持"和"应用配置有误"

1.2 为什么"能启动预览"不等于"功能可用"

许多项目采用简化的验收策略:

启动预览 → 画面出现 → 验收通过

这种方案的根本缺陷:

  1. 只验证了表面:预览画面能显示,不代表焦点、自动构图等核心功能可用
  2. 无诊断依据:出问题时无法回溯"当初检查了什么"
  3. 无分级策略:权限问题、硬件限制、系统配置这些不同层级的问题无法区分
  4. 无恢复路径:无法判断是否可以通过配置调整来解决问题
  5. 无交接记录:现场验收的结论无法成为后续故障排查的基础

1.3 企业实施的真实需求

相机系统的预检必须满足以下要求:

  1. 多层检查的完整性:权限 → 设备 → 预览能力 → 会话启动 → 焦点与构图能力,逐层递进
  2. 真实状态的记录:每个检查节点的真实结果都被记录,不伪造、不猜测
  3. 分级的诊断结论:能明确判断"哪一层出问题了",为后续处理提供方向
  4. 人工与自动的结合:自动检查提供基础结论,人工确认补充运行时的真实感受
  5. 远程支持的可追溯性:现场人员通过检查报告,远程支持可以快速定位问题
  6. 故障恢复的备选方案:检查结果能够指导"如果某项不可用,如何调整策略继续工作"

二、核心技术概念与设计思路

2.1 相机预检的五层递进模型

┌─ 第一层:权限检查 ─────────────────┐
│ CAMERA 权限是否授予?              │
│ ├─ 未请求 → 发起请求              │
│ ├─ 用户拒绝 → 无法继续            │
│ └─ 已授予 → 继续                   │
└─────────────────────────────────────┘
        ↓
┌─ 第二层:设备检查 ─────────────────┐
│ 后摄设备是否存在?                  │
│ ├─ 未发现 → 硬件限制,无法继续    │
│ └─ 已发现 → 记录设备ID             │
└─────────────────────────────────────┘
        ↓
┌─ 第三层:预览能力检查 ─────────────┐
│ 设备是否支持预览 Profile?         │
│ ├─ 无预览Profile → 输出能力缺失   │
│ └─ 有预览Profile → 记录分辨率      │
└─────────────────────────────────────┘
        ↓
┌─ 第四层:Surface 与会话启动 ───────┐
│ XComponent Surface 是否就绪?      │
│ ├─ 未就绪 → 等待                   │
│ └─ 已就绪 → 创建会话               │
│                                    │
│ VideoSession 是否成功创建和启动?  │
│ ├─ 创建失败 → 会话层异常           │
│ ├─ 启动失败 → 运行时异常           │
│ └─ 启动成功 → 继续检查能力         │
└─────────────────────────────────────┘
        ↓
┌─ 第五层:焦点与构图能力检查 ───────┐
│ 连续自动对焦是否支持?             │
│ ├─ 不支持 → 记录为限制             │
│ └─ 支持 → 已设置                   │
│                                    │
│ AUTO_FRAMING 能力是否可用?       │
│ ├─ 控制中心不支持 → 回退           │
│ ├─ AUTO_FRAMING 不声明 → 回退     │
│ ├─ 启用失败 → 回退                 │
│ └─ 启用成功 → 能力可用             │
└─────────────────────────────────────┘
        ↓
     预检完成
  生成诊断报告

2.2 预检状态的完整记录结构

interface CameraPreflightCheck {
  // ===== 第一层:权限 =====
  permission: {
    status: 'not_requested' | 'requesting' | 'granted' | 'denied';
    timestamp: string;
    errorMessage?: string;
  };

  // ===== 第二层:设备 =====
  device: {
    status: 'querying' | 'found' | 'not_found';
    deviceId?: string;
    cameraPosition?: 'BACK' | 'FRONT';
    timestamp: string;
  };

  // ===== 第三层:预览能力 =====
  previewCapability: {
    status: 'querying' | 'found' | 'not_found';
    profile?: {
      width: number;
      height: number;
      format: string;
    };
    timestamp: string;
  };

  // ===== 第四层:会话 =====
  session: {
    surfaceStatus: 'waiting' | 'ready';
    creationStatus: 'not_started' | 'creating' | 'created' | 'failed';
    launchStatus: 'not_started' | 'launching' | 'launched' | 'failed';
    errorMessage?: string;
    timestamp: string;
  };

  // ===== 第五层:能力 =====
  capabilities: {
    focus: {
      continuousAutoFocusSupported: boolean;
      status: 'checking' | 'supported' | 'not_supported';
    };
    autoFraming: {
      controlCenterSupported: boolean;
      effectTypeSupported: boolean;
      enableStatus: 'checking' | 'enabled' | 'not_enabled' | 'failed';
      fallbackReason?: string;
    };
    timestamp: string;
  };

  // ===== 总体结论 =====
  conclusion: {
    overallStatus: 'ready' | 'degraded' | 'unavailable';
    readyForCapture: boolean;
    diagnosticMessage: string;
    recoveryOptions: string[];
  };
}

2.3 预检的阻断点与继续策略

enum PreflightBlockingLevel {
  // ===== 阻断性问题(不能继续)=====
  BLOCKING_PERMISSION = 'permission_denied',        // 权限被拒,无法继续
  BLOCKING_NO_DEVICE = 'no_back_camera',            // 无后摄设备,无法继续
  BLOCKING_NO_OUTPUT = 'no_preview_output',         // 无预览输出能力,无法继续
  BLOCKING_SESSION = 'session_failed',              // 会话启动失败,无法继续

  // ===== 降级问题(可以继续,但功能受限)=====
  DEGRADED_NO_FOCUS = 'focus_not_supported',        // 无连续自动对焦,但预览可用
  DEGRADED_NO_FRAMING = 'auto_framing_not_supported', // 无自动构图,但预览可用

  // ===== 正常通过 =====
  PASSED = 'all_checks_passed'                      // 所有检查通过
}

三、完整的预检流程实现

3.1 权限检查的三状态管理

private async requestCameraPermission(): Promise<boolean> {
  // 第一步:标记检查开始
  this.phase = 'requesting_permission';
  this.permissionState = '请求中';
  this.markRuntime('发起 ohos.permission.CAMERA 授权请求');

  try {
    // 第二步:发起权限请求
    const atManager = abilityAccessCtrl.createAtManager();
    const result = await atManager.requestPermissionsFromUser(
      getContext(this),
      ['ohos.permission.CAMERA']
    );

    // 第三步:判断结果
    const granted = result.authResults.length > 0 && result.authResults[0] === 0;
    this.permissionState = granted ? '已授予' : '被拒绝';
    
    // 第四步:记录日志
    this.markRuntime(
      granted
        ? '相机权限已授予,可继续设备检查'
        : '相机权限被用户拒绝,请在系统设置中允许后重试'
    );

    return granted;
  } catch (error) {
    // 异常处理:权限请求本身失败(通常是系统问题)
    this.permissionState = '授权请求失败';
    this.fail('相机权限请求失败', error as Error);
    return false;
  }
}

private fail(message: string, error: Error): void {
  this.phase = 'failed';
  this.previewState = '运行失败';
  this.markRuntime(
    `${message};不能进入后续检查\n` +
    `错误:${formatRuntimeError(error)}`
  );
}

关键原则

  1. 权限请求异常(如requestPermissionsFromUser抛出异常)与权限被拒绝(返回值表示拒绝)是不同的情况
  2. 异常应该导致检查中止,被拒绝应该提供恢复建议
  3. 记录权限请求的时间戳,用于后续审计

3.2 设备与输出能力的分层查询

private async startCameraSession(): Promise<void> {
  // ===== 前置检查 =====
  if (!this.surfaceReady) {
    this.previewState = 'Surface 尚未就绪';
    this.markRuntime('未启动相机:XComponent Surface 未就绪');
    return;
  }

  // ===== 权限检查 =====
  const granted = await this.requestCameraPermission();
  if (!granted) {
    this.phase = 'unavailable';
    this.previewState = '权限未授予,无法启动';
    return;
  }

  // ===== 第一层:设备检查 =====
  this.phase = 'querying_device';
  this.deviceState = '查询真实后摄设备';
  this.markRuntime('读取 CameraManager 支持的设备列表');

  try {
    const manager = camera.getCameraManager(getContext(this));
    
    // 第1.1步:获取设备列表
    const supportedDevices = manager.getSupportedCameras();
    if (supportedDevices.length === 0) {
      this.phase = 'unavailable';
      this.deviceState = '设备列表为空';
      this.markRuntime('CameraManager 未返回任何设备');
      return;
    }

    // 第1.2步:筛选后摄设备
    const device = this.selectBackCamera(supportedDevices);
    if (device === undefined) {
      this.phase = 'unavailable';
      this.deviceState = '后摄设备未找到';
      this.previewState = '设备不支持后摄预览';
      this.markRuntime('设备列表中无后摄设备(可能只有前摄)');
      return;
    }

    // 第1.3步:记录设备信息
    this.deviceState = `后摄 ${device.cameraId} · ${device.cameraType}`;

    // ===== 第二层:输出能力检查 =====
    this.markRuntime(`查询后摄 ${device.cameraId} 的输出能力`);
    const capability = manager.getSupportedOutputCapability(
      device,
      camera.SceneMode.NORMAL_VIDEO
    );

    // 第2.1步:检查预览 Profile
    const previewProfile = capability.previewProfiles.length > 0
      ? capability.previewProfiles[0]
      : undefined;

    if (previewProfile === undefined) {
      this.phase = 'unavailable';
      this.deviceState = `后摄 ${device.cameraId} 未返回预览 Profile`;
      this.previewState = '设备未提供视频预览输出能力';
      this.markRuntime('getSupportedOutputCapability 返回空的 previewProfiles');
      return;
    }

    // 第2.2步:记录能力信息
    this.deviceState = `后摄 ${device.cameraId}${previewProfile.size.width}x${previewProfile.size.height}`;

    // ===== 第三层:会话创建与启动 =====
    this.phase = 'starting_preview';
    this.previewState = '创建真实视频预览会话';
    this.markRuntime('创建 CameraInput、PreviewOutput 与 VideoSession');

    // 第3.1步:创建输入输出
    this.cameraInput = manager.createCameraInput(device);
    await this.cameraInput.open();

    this.previewOutput = manager.createPreviewOutput(
      previewProfile,
      this.previewController.getXComponentSurfaceId()
    );

    // 第3.2步:创建会话
    this.videoSession = manager.createSession<camera.VideoSession>(
      camera.SceneMode.NORMAL_VIDEO
    );
    this.videoSession.on('error', this.sessionErrorCallback);

    // 第3.3步:配置会话
    this.videoSession.beginConfig();
    this.videoSession.addInput(this.cameraInput);
    this.videoSession.addOutput(this.previewOutput);
    await this.videoSession.commitConfig();

    // ===== 第四层:焦点与构图能力检查 =====
    this.setupFocusAndFraming(this.videoSession);

    // 第3.4步:启动会话
    await this.videoSession.start();
    this.phase = 'previewing';
    this.previewState = '真实后摄预览运行中';
    this.markRuntime('VideoSession 已启动,进入预览状态');

  } catch (error) {
    // 异常处理
    this.fail('相机预览启动失败', error as Error);
    await this.releaseCameraSession();
  }
}

private selectBackCamera(
  devices: camera.CameraDevice[]
): camera.CameraDevice | undefined {
  // 遍历设备列表,找到第一个后摄设备
  for (let index = 0; index < devices.length; index += 1) {
    if (devices[index].cameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK) {
      return devices[index];
    }
  }
  return undefined;
}

3.3 焦点与自动构图能力的检查与回退

private setupFocusAndFraming(session: camera.VideoSession): void {
  // ===== 焦点能力检查 =====
  const focusSupported = session.isFocusModeSupported(
    camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO
  );

  if (focusSupported) {
    session.setFocusMode(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO);
    this.focusState = '已设置连续自动对焦';
    this.markRuntime('连续自动对焦已启用');
  } else {
    this.focusState = '设备不支持连续自动对焦';
    this.markRuntime('当前设备不支持 FOCUS_MODE_CONTINUOUS_AUTO');
  }

  // ===== 自动构图能力检查 =====
  this.framingMode = 'checking';
  this.framingFallbackReason = '等待能力查询';
  this.markRuntime('开始检查 AUTO_FRAMING 能力');

  // 第一步:检查控制中心支持
  if (!session.isControlCenterSupported()) {
    this.enterManualFramingFallback('控制中心不支持,转人工构图');
    return;
  }

  // 第二步:检查 AUTO_FRAMING 效果类型声明
  const supportedEffects = session.getSupportedEffectTypes();
  if (!supportedEffects.includes(camera.ControlCenterEffectType.AUTO_FRAMING)) {
    this.enterManualFramingFallback('AUTO_FRAMING 未声明,转人工构图');
    return;
  }

  // 第三步:尝试启用 AUTO_FRAMING
  try {
    session.enableControlCenter(true);
    this.framingMode = 'system_available';
    this.framingFallbackReason = '不适用:系统能力可用';
    this.autoFramingState = 'AUTO_FRAMING 已启用,由系统控制中心接管';
    this.markRuntime('AUTO_FRAMING 启用成功');
  } catch (error) {
    this.enterManualFramingFallback(
      `AUTO_FRAMING 启用失败:${formatRuntimeError(error as Error)}`
    );
  }
}

private enterManualFramingFallback(reason: string): void {
  // 记录回退的原因,不中止预览
  this.framingMode = 'manual_pending';
  this.framingFallbackReason = reason;
  this.autoFramingState = `${reason};预览继续,但需人工构图确认`;
  this.markRuntime(`已进入人工构图回退:${reason}`);
}

四、预检结论的分级与指导

4.1 预检通过率的三级结论

enum PreflightConclusion {
  // ===== GO:完全就绪 =====
  GO = 'go',
  // 所有检查通过,相机功能完全可用
  // 现场人员可以直接进行采集作业

  // ===== GO_DEGRADED:有限制的就绪 =====
  GO_DEGRADED = 'go_degraded',
  // 某些能力不可用(如无自动构图),但核心预览功能正常
  // 现场人员需要采用人工构图或降级工作流程

  // ===== NO_GO:不就绪 =====
  NO_GO = 'no_go'
  // 权限、设备或会话出问题,无法启动预览
  // 需要远程支持或现场排查
}

interface PreflightReport {
  conclusion: PreflightConclusion;
  gateSummary: string;
  diagnosticItems: {
    permission: string;
    device: string;
    previewCapability: string;
    session: string;
    focus: string;
    autoFraming: string;
  };
  recoveryOptions: string[];
  timestamp: string;
}

private generateReport(): PreflightReport {
  // 判断总体结论
  if (this.phase === 'failed' || this.phase === 'unavailable') {
    return {
      conclusion: PreflightConclusion.NO_GO,
      gateSummary: '暂不可启用 · 需远程支持',
      diagnosticItems: {
        permission: this.permissionState,
        device: this.deviceState,
        previewCapability: this.previewState,
        session: this.phase,
        focus: this.focusState,
        autoFraming: 'N/A'
      },
      recoveryOptions: [
        '检查系统设置中的相机权限',
        '确认设备是否支持后摄',
        '尝试重启应用或设备',
        '联系技术支持获取远程诊断'
      ],
      timestamp: this.lastUpdatedAt
    };
  }

  if (this.phase === 'previewing' && this.framingMode === 'manual_pending') {
    return {
      conclusion: PreflightConclusion.GO_DEGRADED,
      gateSummary: '预览可用 · 人工构图',
      diagnosticItems: {
        permission: this.permissionState,
        device: this.deviceState,
        previewCapability: '可用',
        session: '已启动',
        focus: this.focusState,
        autoFraming: `降级:${this.framingFallbackReason}`
      },
      recoveryOptions: [
        '使用人工构图指导完成采集',
        '升级系统版本后重新检查 AUTO_FRAMING 支持',
        '尝试不同的硬件设备'
      ],
      timestamp: this.lastUpdatedAt
    };
  }

  if (this.phase === 'previewing') {
    return {
      conclusion: PreflightConclusion.GO,
      gateSummary: '预览运行 · 完全就绪',
      diagnosticItems: {
        permission: this.permissionState,
        device: this.deviceState,
        previewCapability: '可用',
        session: '已启动',
        focus: this.focusState,
        autoFraming: '可用'
      },
      recoveryOptions: [],
      timestamp: this.lastUpdatedAt
    };
  }

  return {
    conclusion: PreflightConclusion.NO_GO,
    gateSummary: '检查进行中',
    diagnosticItems: {
      permission: this.permissionState,
      device: this.deviceState,
      previewCapability: this.previewState,
      session: this.phase,
      focus: this.focusState,
      autoFraming: this.autoFramingState
    },
    recoveryOptions: [],
    timestamp: this.lastUpdatedAt
  };
}

五、远程支持与故障诊断指南

5.1 基于预检报告的快速诊断

预检结果 可能原因 建议处理
权限被拒 用户拒绝授权,或企业MDM禁用 在设置中重新授权,或联系企业管理员
无后摄 设备仅有前摄,或硬件故障 使用不同设备,或检查硬件
无预览Profile 驱动问题或系统配置 更新系统,或尝试重启设备
会话启动失败 系统级问题,如相机服务异常 重启相机应用,或重启设备
焦点不支持 硬件限制,可能是入门级设备 使用自动曝光替代,或使用不同设备
AUTO_FRAMING不支持 系统版本旧,或硬件不支持 使用人工构图工作流,或升级系统

5.2 远程协作的信息采集

现场人员应该向远程支持提供的信息:

设备型号:_____________
系统版本:_____________
应用版本:_____________

预检报告:
- 权限状态:_____________
- 设备识别:_____________
- 预览能力:_____________
- 会话状态:_____________
- 焦点能力:_____________
- 构图能力:_____________

最后事件:_____________
时间戳:_____________

六、实施验收与测试清单

6.1 预检功能的验收标准

  • 权限检查

    • 首次启动弹出权限请求
    • 授权后继续检查
    • 拒绝后提供恢复建议
  • 设备检查

    • 成功识别后摄设备
    • 记录设备ID和型号
    • 无后摄时明确提示
  • 预览能力检查

    • 查询并记录预览Profile分辨率
    • 无预览能力时明确提示
    • 支持多个Profile时记录首选项
  • 会话启动

    • Surface就绪后能启动预览
    • 异常时清晰展示错误信息
    • 支持重试
  • 焦点检查

    • 检查连续自动对焦支持
    • 支持时自动设置
    • 不支持时记录为限制
  • 构图能力检查

    • 检查AUTO_FRAMING支持
    • 支持时启用控制中心
    • 不支持时切换人工构图

6.2 远程验收的检查项

  • 在至少3种不同设备上验证
  • 在不同系统版本上验证
  • 权限被拒的场景下能正确处理
  • 支持人工构图回退
  • 预检报告能够清晰指导故障诊断

七、常见问题与应急处理

Q1: 预览画面出现但焦点不工作,为什么?

A: 焦点与预览是独立的能力。焦点问题可能的原因:

  1. 硬件不支持连续自动对焦
  2. 焦点设置失败(异常捕获)
  3. 焦点模式与具体场景不匹配

建议:使用人工对焦或依赖自动曝光,预检报告应该清楚地记录焦点状态,而不是只看预览画面。

Q2: 控制中心启用成功,但AUTO_FRAMING实际不生效?

A: 系统控制中心的"启用成功"只表示API调用成功,不表示实际的构图逻辑已激活。原因可能包括:

  1. 系统控制中心有自己的条件判断(如需要足够的特征点)
  2. 特定场景下系统禁用了AUTO_FRAMING
  3. 系统版本的实现差异

建议:采用"预检只检查API支持,运行时通过用户反馈判断实际效果"的策略。

Q3: 某些设备预检全部通过,但现场仍无法正常采集?

A: 预检只能检查相机系统层的能力,无法检查应用层的完整流程。需要补充检查:

  1. 拍摄后的图像保存是否成功
  2. 图像处理管道(缩放、旋转、压缩)是否正常
  3. 联网上传是否可用

建议:预检只是第一步,后续需要进行完整的业务流程验证。


总结

HarmonyOS 6.1.1 中的相机预检体系需要遵循以下核心原则:

  1. 多层递进:权限 → 设备 → 预览能力 → 会话 → 能力,逐层深入
  2. 真实记录:不伪造、不猜测,每个节点的真实结果都被记录
  3. 分级结论:能明确判断"哪一层出问题了"
  4. 人工补充:自动检查提供基础,人工确认补充运行时感受
  5. 可追溯性:预检报告成为故障诊断的基础
  6. 备选方案:检查结果指导后续工作流程的选择

通过这套体系,企业可以:

  • 在上线前有信心地验收相机功能
  • 现场问题发生时快速定位根因
  • 为远程支持提供充分的诊断信息
  • 指导现场人员选择合适的工作流程

验证状态:✅ 本文对应的代码已集成到项目,所有五层预检流程(权限、设备、预览能力、会话、焦点与构图)均在 CameraAutoFramingFocusPage.ets 中完整实现,包括每层的检查、记录、异常处理和回退机制。

后续复拍建议:计划在后续版本中支持预检报告的导出、历史对比、批量设备验证等高级功能。

必要条件|模拟器与真机准备对照

条件 API 24 模拟器 HarmonyOS 6.1.1 真机
SDK/API与构建工具 使用 API 24 镜像验证构建和基础页面 使用兼容 API 24 的签名包安装
Kit引入 先确认编译期 Kit 类型可用 再确认设备运行时模块实际可用
模块/页面配置 页面路由和 Stage 启动可验证 页面路由、签名和设备安装状态均需验证
权限 可演练授权弹窗和拒绝分支 需重新授权并确认系统设置中的真实状态
系统能力/硬件 只能代表模拟器提供的能力 Camera、麦克风、地图、视觉识别等以真机能力为准

SDK/API 对照完成后插入 DevEco Studio API 24 与构建配置截图:

在这里插入图片描述

授权对照完成后插入真实设备权限截图:
在这里插入图片描述

版本和能力对照完成后插入设备/模拟器信息截图:

在这里插入图片描述

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐