HarmonyOS-6.1.1-Camera:远程验收建立相机预览前-怎样检查权限、设备、Surface与会话条件
企业项目实施工程师视角:本文从现场采集系统上线前的验收需求出发,讲解如何在 HarmonyOS 6.1.1 中建立完整的预检体系,通过权限、设备、预览Surface与会话条件的多层检查,确保相机功能真实可用,并为现场故障诊断和远程支持奠定基础。
一、企业场景与挑战
1.1 相机系统上线前的验收困局
在实际部署中,相机功能的上线验收往往面临以下困境:
- 现场条件差异大:不同型号手机、不同Android版本、不同定制系统的相机能力不一致
- 权限管理复杂:用户可能在运行时撤销权限,系统设置中禁用相机,或企业MDM策略限制相机使用
- 设备识别困难:某些设备无后摄,某些设备的预览Profile不完整,某些设备的焦点模式受限
- 会话启动隐蔽失败:相机会话看起来启动了,但实际上焦点、自动构图等能力可能不生效
- 远程支持困难:现场人员说"相机不能用",但无法提供诊断信息,导致远程定位问题困难
表面现象:“现场验收时相机功能可用,上线后却出现间歇性故障"或"A地能用,B地不能用”
根本原因:缺少系统化的预检体系,无法区分"硬件/系统不支持"和"应用配置有误"
1.2 为什么"能启动预览"不等于"功能可用"
许多项目采用简化的验收策略:
启动预览 → 画面出现 → 验收通过
这种方案的根本缺陷:
- 只验证了表面:预览画面能显示,不代表焦点、自动构图等核心功能可用
- 无诊断依据:出问题时无法回溯"当初检查了什么"
- 无分级策略:权限问题、硬件限制、系统配置这些不同层级的问题无法区分
- 无恢复路径:无法判断是否可以通过配置调整来解决问题
- 无交接记录:现场验收的结论无法成为后续故障排查的基础
1.3 企业实施的真实需求
相机系统的预检必须满足以下要求:
- 多层检查的完整性:权限 → 设备 → 预览能力 → 会话启动 → 焦点与构图能力,逐层递进
- 真实状态的记录:每个检查节点的真实结果都被记录,不伪造、不猜测
- 分级的诊断结论:能明确判断"哪一层出问题了",为后续处理提供方向
- 人工与自动的结合:自动检查提供基础结论,人工确认补充运行时的真实感受
- 远程支持的可追溯性:现场人员通过检查报告,远程支持可以快速定位问题
- 故障恢复的备选方案:检查结果能够指导"如果某项不可用,如何调整策略继续工作"
二、核心技术概念与设计思路
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)}`
);
}
关键原则:
- 权限请求异常(如
requestPermissionsFromUser抛出异常)与权限被拒绝(返回值表示拒绝)是不同的情况 - 异常应该导致检查中止,被拒绝应该提供恢复建议
- 记录权限请求的时间戳,用于后续审计
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: 焦点与预览是独立的能力。焦点问题可能的原因:
- 硬件不支持连续自动对焦
- 焦点设置失败(异常捕获)
- 焦点模式与具体场景不匹配
建议:使用人工对焦或依赖自动曝光,预检报告应该清楚地记录焦点状态,而不是只看预览画面。
Q2: 控制中心启用成功,但AUTO_FRAMING实际不生效?
A: 系统控制中心的"启用成功"只表示API调用成功,不表示实际的构图逻辑已激活。原因可能包括:
- 系统控制中心有自己的条件判断(如需要足够的特征点)
- 特定场景下系统禁用了AUTO_FRAMING
- 系统版本的实现差异
建议:采用"预检只检查API支持,运行时通过用户反馈判断实际效果"的策略。
Q3: 某些设备预检全部通过,但现场仍无法正常采集?
A: 预检只能检查相机系统层的能力,无法检查应用层的完整流程。需要补充检查:
- 拍摄后的图像保存是否成功
- 图像处理管道(缩放、旋转、压缩)是否正常
- 联网上传是否可用
建议:预检只是第一步,后续需要进行完整的业务流程验证。
总结
HarmonyOS 6.1.1 中的相机预检体系需要遵循以下核心原则:
- 多层递进:权限 → 设备 → 预览能力 → 会话 → 能力,逐层深入
- 真实记录:不伪造、不猜测,每个节点的真实结果都被记录
- 分级结论:能明确判断"哪一层出问题了"
- 人工补充:自动检查提供基础,人工确认补充运行时感受
- 可追溯性:预检报告成为故障诊断的基础
- 备选方案:检查结果指导后续工作流程的选择
通过这套体系,企业可以:
- 在上线前有信心地验收相机功能
- 现场问题发生时快速定位根因
- 为远程支持提供充分的诊断信息
- 指导现场人员选择合适的工作流程
验证状态:✅ 本文对应的代码已集成到项目,所有五层预检流程(权限、设备、预览能力、会话、焦点与构图)均在 CameraAutoFramingFocusPage.ets 中完整实现,包括每层的检查、记录、异常处理和回退机制。
后续复拍建议:计划在后续版本中支持预检报告的导出、历史对比、批量设备验证等高级功能。
必要条件|模拟器与真机准备对照
| 条件 | API 24 模拟器 | HarmonyOS 6.1.1 真机 |
|---|---|---|
| SDK/API与构建工具 | 使用 API 24 镜像验证构建和基础页面 | 使用兼容 API 24 的签名包安装 |
| Kit引入 | 先确认编译期 Kit 类型可用 | 再确认设备运行时模块实际可用 |
| 模块/页面配置 | 页面路由和 Stage 启动可验证 | 页面路由、签名和设备安装状态均需验证 |
| 权限 | 可演练授权弹窗和拒绝分支 | 需重新授权并确认系统设置中的真实状态 |
| 系统能力/硬件 | 只能代表模拟器提供的能力 | Camera、麦克风、地图、视觉识别等以真机能力为准 |
SDK/API 对照完成后插入 DevEco Studio API 24 与构建配置截图:

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

更多推荐


所有评论(0)