HarmonyOS-6.1.1-Camera:自动构图能力不满足现场设备时-怎样保留人工取景和附件交接
一、企业场景与挑战

1.1 现场取证的能力边界问题
在实际部署中,相机的自动构图(AUTO_FRAMING)能力往往存在设备差异:
- 高端设备:支持连续自动对焦 + 自动构图控制
- 中端设备:支持连续自动对焦,但无 AUTO_FRAMING 接口
- 低端或特殊设备:可能无后摄、无合适输出 Profile、甚至权限被禁用
核心挑战:不能因为一个功能不可用就导致整个取证流程中断。必须在以下两点间找到平衡:
- 完整性:记录下能力检查的真实结论(什么不支持、为什么不支持)
- 可用性:为现场人员提供人工取景和附件交接的替代方案
1.2 为什么不能简单回退到"提示用户补充附件"
许多实现采用简单的处理方式:
检查能力 → 不支持 → 提示用户"请手动补充附件" → 结束
这种方式的问题在于:
- 丢失能力事实:系统无法证明"自动构图确实不可用",只能说"用户没有上传"
- 流程中断风险:现场人员可能无法准确理解为什么要人工补充,导致应急流程中的时间浪费
- 后续追溯困难:审计链上无法区分"主动选择人工取证"和"被迫降级"
- 复拍时限管理失效:无法准确指派下一责任人和时限,影响后续工作的接续
1.3 企业实施的真实需求
现场取证必须满足以下要求:
- 能力事实记录:清晰地记下"AUTO_FRAMING 不支持"、“权限被拒绝"还是"设备不可用”
- 人工取景的正当性:不是因为系统故障而被迫人工取景,而是基于真实能力检查的业务路由
- 附件交接的完整性:包括拍摄时间、附件路径、复拍时限、下一责任人等关键信息
- 异常恢复的可能性:如果网络恢复或权限重新授予,能否重新启动自动构图流程
- 审计链的连续性:从能力检查 → 回退判断 → 人工拍摄 → 附件交接 → 复拍指派,完整可追溯

二、核心技术概念与设计思路
2.1 相机能力检查的关键节点
HarmonyOS 6.1.1 中,相机自动构图能力的检查需要覆盖以下层面:
| 层级 | 检查项 | API | 失败路由 |
|---|---|---|---|
| 设备层 | 后摄是否可用 | manager.getSupportedCameras() | camera_unavailable |
| 输出能力层 | 预览/拍照 Profile 是否可用 | manager.getSupportedOutputCapability() | camera_unavailable |
| 权限层 | CAMERA 权限是否授予 | abilityAccessCtrl.requestPermissionsFromUser() | permission_denied |
| 会话层 | PhotoSession 是否能创建 | manager.createSession() | session_failed |
| 焦点层 | 连续自动对焦是否支持 | session.isFocusModeSupported() | (可降级) |
| 构图控制层 | AUTO_FRAMING 接口是否存在 | 调用后判断 | unsupported |
关键发现:HarmonyOS 6.1.1 中,AUTO_FRAMING 并非通过 isFocusModeSupported() 这样的明确 API 暴露。通常的做法是:
- 尝试在 PhotoSession 中查找该控制接口
- 如果接口不存在或调用失败,则标记为
unsupported - 此时必须切换到人工构图模式
2.2 回退原因的五层分类
根据能力检查的结果,系统需要明确判断回退原因,并为不同原因设置不同的后续路由:
type FallbackReason = 'waiting' | 'unsupported' | 'session_failed' | 'permission_denied' | 'camera_unavailable';
各回退原因的含义与路由:
-
unsupported- AUTO_FRAMING 控制接口不存在- 能力结论:PhotoSession 已成功创建,但不提供自动构图接口
- 后续路由:启用人工构图取证
- 复拍策略:可以在后续强制升级或重新检查
-
session_failed- PhotoSession 创建或启动异常- 能力结论:相机硬件或会话层故障
- 后续路由:可选人工附件,但不启动预览
- 复拍策略:设置较长时限,等待设备恢复
-
permission_denied- CAMERA 权限被拒绝或权限请求失败- 能力结论:用户未授权或应用无权访问相机
- 后续路由:允许人工附件,但建议在系统设置中重新授权
- 复拍策略:权限恢复后可重新尝试
-
camera_unavailable- 后摄设备或输出 Profile 不可用- 能力结论:硬件或固件限制
- 后续路由:允许人工附件,标记为"设备不支持"
- 复拍策略:可能需要更换设备
-
waiting- 尚未进行能力检查或检查进行中- 能力结论:未知
- 后续路由:不允许进入人工取景流程
- 复拍策略:用户必须先完成能力检查
2.3 人工构图与自动构图的本质区别

错误理解:只要用户点了"拍摄"按钮就叫"人工构图"
正确理解:人工构图是指基于真实能力检查结论而启动的、不依赖 AUTO_FRAMING 的取证流程
自动构图流程:
能力检查(有AUTO_FRAMING) → 启用控制 → 系统自动调整 → 拍摄
人工构图流程:
能力检查(无AUTO_FRAMING) → 显示取景框指导 → 用户手动调整 → 拍摄
关键差异:
| 维度 | 自动构图 | 人工构图 |
|---|---|---|
| 前置条件 | AUTO_FRAMING 接口存在 | AUTO_FRAMING 不存在 |
| 控制权 | 系统根据算法自动调整 | 用户根据视觉反馈调整 |
| 预期质量 | 系统担保最优构图 | 用户根据经验自行判断 |
| 审计记录 | 记录系统决策参数 | 记录用户操作时长和快门次数 |
| 后续责任 | 系统承担质量责任 | 用户承担质量责任 |
| 复拍触发 | 系统判断不合格自动重试 | 用户主动判断是否重拍 |
2.4 人工附件交接的完整信息结构
人工附件交接不仅要记录"拍了一张照片",还需要包含完整的业务上下文:
interface HandoverRecord {
// 附件基本信息
attachmentPath: string; // 本地附件路径
attachmentSize: number; // 文件大小
captureTime: string; // 拍摄时间戳
// 能力事实
fallbackReason: FallbackReason; // 回退原因
autoFramingSupported: boolean; // AUTO_FRAMING 是否支持
focusModeSupported: boolean; // 焦点模式是否支持
// 复拍管理
retakeDeadline: string; // 复拍时限(通常24小时内)
responsiblePerson: string; // 下一责任人
handoverTime: string; // 交接登记时间
// 审计链
workOrderId: string; // 工单编号
operatorId: string; // 操作员ID
batchId: string; // 批次ID(用于关联多个附件)
}
三、完整的状态机设计
3.1 能力检查阶段的状态流转
┌─────────────────┐
│ waiting_surface │ 初始状态,等待 Surface 就绪
└────────┬────────┘
│ Surface.onLoad()
▼
┌────────────────────┐
│requesting_permission│ 发起权限请求
└────────┬───────────┘
│ 用户授权或拒绝
├─────────────────────────────┐
│ (granted) │ (denied)
▼ ▼
┌──────────────────────┐ ┌───────────────────┐
│querying_capability │ │unavailable (perm) │
│ │ │permission_denied │
└────────┬─────────────┘ └───────────────────┘
│ getSupportedCameras()
├────────────────────────┐
│ (device found) │ (not found)
▼ ▼
┌──────────────────────┐ ┌──────────────────┐
│(device confirmed) │ │unavailable (dev) │
│getSupportOutputCap() │ │camera_unavailable│
└────────┬─────────────┘ └──────────────────┘
│ (profile found)
▼
┌──────────────────────┐
│starting_preview │ 创建 PhotoSession
│createSession() │ 添加输入输出
└────────┬─────────────┘ commitConfig()
│ commitConfig() success
├────────────────────────────────┐
│ (success) │ (error)
▼ ▼
┌──────────────────────┐ ┌──────────────────┐
│checking_autoframing │ │failed │
│isFocusModeSupported()│ │session_failed │
└────────┬─────────────┘ └──────────────────┘
│ check AUTO_FRAMING interface
├────────────────────────┐
│ (exists) (not found)│
▼ ▼
┌──────────────┐ ┌─────────────────┐
│start() │ │unsupported: │
│previewing │ │fallback allowed │
└──────────────┘ │previewing │
└─────────────────┘
关键节点的判断逻辑:
private async startCameraSession(): Promise<void> {
// 1. 权限检查
const granted = await this.requestCameraPermission();
if (!granted) {
this.fallbackReason = 'permission_denied';
this.phase = 'unavailable';
return;
}
// 2. 设备检查
const manager = camera.getCameraManager(getContext(this));
const device = this.selectBackCamera(manager.getSupportedCameras());
if (device === undefined) {
this.fallbackReason = 'camera_unavailable';
this.phase = 'unavailable';
return;
}
// 3. 输出能力检查
const capability = manager.getSupportedOutputCapability(device, camera.SceneMode.NORMAL_PHOTO);
const previewProfile = this.selectLargestProfile(capability.previewProfiles);
const photoProfile = this.selectLargestProfile(capability.photoProfiles);
if (previewProfile === undefined || photoProfile === undefined) {
this.fallbackReason = 'camera_unavailable';
this.phase = 'unavailable';
return;
}
// 4. 会话创建
try {
this.photoSession = manager.createSession<camera.PhotoSession>(camera.SceneMode.NORMAL_PHOTO);
this.photoSession.on('error', this.sessionErrorCallback);
this.photoSession.beginConfig();
this.photoSession.addInput(this.cameraInput);
this.photoSession.addOutput(this.previewOutput);
this.photoSession.addOutput(this.photoOutput);
await this.photoSession.commitConfig();
} catch (error) {
this.fallbackReason = 'session_failed';
this.phase = 'failed';
return;
}
// 5. 焦点与自动构图能力检查
this.setupFocusAndFallback(this.photoSession);
// 这里会检查 AUTO_FRAMING 是否可用
// 如果不可用,设置 fallbackReason = 'unsupported'
// 6. 启动预览
await this.photoSession.start();
this.phase = 'previewing';
}
private setupFocusAndFallback(session: camera.PhotoSession): void {
// 检查连续自动对焦
if (session.isFocusModeSupported(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO)) {
session.setFocusMode(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO);
this.focusState = '已设置连续自动对焦';
} else {
this.focusState = '设备不支持连续自动对焦';
}
// 检查 AUTO_FRAMING:如果调用不存在或失败,则判定为不支持
// HarmonyOS 6.1.1 的 PhotoSession 通常不直接暴露 AUTO_FRAMING 接口
// 而是在创建会话时就确定了,所以这里标记为不支持并启用人工回退
this.autoFramingState = '当前 PhotoSession 不提供 AUTO_FRAMING 控制接口,已转人工构图回退';
this.fallbackReason = 'unsupported';
}
3.2 人工取景阶段的状态流转
┌─────────────────────┐
│previewing │ 能力检查完成,fallbackReason = 'unsupported'
│(fallback allowed) │ 显示取景框指导
└────────┬────────────┘
│ 用户点击"拍摄人工构图附件"
▼
┌─────────────────────┐
│capturing │ 调用 photoOutput.capture()
│createPendingPhoto() │ 创建临时文件路径
└────────┬────────────┘
│ capture() 触发 imageArrival
│ 接收真实 JPEG 数据
▼
┌─────────────────────┐
│writing_to_disk │ 写入本地文件
│fileIo.writeSync() │
└────────┬────────────┘
│ write success
├──────────────────────┐
│ (success) │ (failed)
▼ ▼
┌─────────────────────┐ ┌──────────────┐
│release_session │ │capture_failed│
│releaseCameraSession │ │cleanup & err │
└────────┬────────────┘ └──────────────┘
│ release complete
▼
┌─────────────────────┐
│review │ 展示拍摄结果
│(pending_photo_path) │ 允许重新拍摄或确认
└─────────────────────┘
四、完整的人工附件交接流程实现
4.1 拍摄阶段
关键原则:
- 只有在
fallbackReason !== 'waiting'时才允许进入人工拍摄 - 拍摄必须基于真实的 PhotoSession 预览,不能是静态图片或示意框
- 保存的附件文件必须具有唯一的本地路径标识
private async captureManualEvidence(): Promise<void> {
// 检查回退条件是否成立
if (!this.isFallbackAllowed()) {
this.statusMessage = '人工附件入口未开放:请先完成真实能力检查并取得回退结论';
return;
}
// 确认预览会话仍在运行
if (this.phase !== 'previewing' || this.photoOutput === undefined) {
this.statusMessage = '当前没有可用的真实预览,无法生成相机附件';
return;
}
try {
// 创建本地附件路径,使用时间戳保证唯一性
this.pendingPhotoPath = this.createPendingPhotoPath(); // ${filesDir}/camera-fallback-${Date.now()}.jpg
this.phase = 'capturing';
this.statusMessage = '正在保存人工构图的真实拍摄结果';
// 触发真实拍摄,指定高质量和当前旋转角度
await this.photoOutput.capture({
quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,
rotation: this.photoOutput.getPhotoRotation()
});
// capture() 返回后,imageArrival 回调会被触发
} catch (error) {
this.failCapture('人工构图拍摄失败', error as Error);
}
}
private async handleImageArrival(): Promise<void> {
const receiver = this.imageReceiver;
if (receiver === undefined || this.pendingPhotoPath === '') return;
let capturedImage: image.Image | undefined;
try {
// 读取最新捕获的图像
capturedImage = await receiver.readLatestImage();
// 提取 JPEG 数据
const component = await capturedImage.getComponent(image.ComponentType.JPEG);
// 打开本地文件用于写入
const target = fileIo.openSync(
this.pendingPhotoPath,
fileIo.OpenMode.WRITE_ONLY | fileIo.OpenMode.CREATE | fileIo.OpenMode.TRUNC
);
try {
// 写入 JPEG 数据
const written = fileIo.writeSync(target.fd, component.byteBuffer);
if (written <= 0) {
throw new Error('附件文件未写入有效 JPEG 数据。');
}
} finally {
fileIo.closeSync(target);
}
// 文件保存成功,释放相机会话
await this.releaseCameraSession();
this.pageMode = 'review';
this.phase = 'released';
this.attachmentState = '已保存真实拍摄结果,待人工确认本地附件草稿';
this.statusMessage = '请复核人工构图结果,再生成本地附件交接记录';
} catch (error) {
this.failCapture('人工附件保存失败', error as Error);
} finally {
if (capturedImage !== undefined) {
await capturedImage.release();
}
}
}
4.2 复核与交接阶段
关键原则:
- 复核只是视觉验证,不改变文件本身
- 交接记录包含完整的能力事实和复拍管理信息
- 本页面不涉及文件上传或工单回写,只负责本地记录的生成
private confirmAttachmentDraft(): void {
// 验证附件文件确实存在
if (this.pendingPhotoPath === '') {
this.statusMessage = '未找到真实拍摄结果,不能生成附件交接记录';
return;
}
// 进入交接模式
this.pageMode = 'handover';
this.activeStepIndex = 2;
// 记录交接的关键时间和状态
this.attachmentState = '本地人工附件草稿已确认,尚未上传或回写工单';
this.handoverState = '已登记待复拍交接,请指定现场责任人并在时限内补齐自动构图证据';
this.handoverTime = timestamp(); // 记录交接时间
// 生成后续流程的路由信息
this.statusMessage = '本地交接记录已创建,等待后续复拍和外部回写';
}
交接记录中应该包含但本页面未完全暴露的信息(假设由调用方在上层组件中补充):
// 完整的交接记录结构(示意)
const handoverRecord = {
// 附件信息
attachmentPath: this.pendingPhotoPath,
attachmentSize: fileIo.statSync(this.pendingPhotoPath).size,
captureTime: timestamp(),
// 能力事实
fallbackReason: this.fallbackReason, // e.g., 'unsupported'
autoFramingSupported: false, // 因为 fallbackReason = 'unsupported'
focusModeSupported: this.focusState.indexOf('已设置') >= 0,
// 复拍管理
retakeDeadline: '24 小时内完成补拍',
responsiblePerson: this.responsiblePerson, // '现场调度员(待指派)'
handoverTime: this.handoverTime,
// 审计链(由上层补充)
workOrderId: '???',
operatorId: '???',
batchId: '???'
};
4.3 复拍与重置流程
关键原则:
- 复拍意味着撤销当前的附件草稿,重新进入拍摄流程
- 重置能力检查意味着从头开始,清空所有状态
- 两种操作都必须清理临时文件,防止磁盘泄漏
private retake(): void {
// 清理之前的临时文件
this.discardPendingPhoto();
// 重新进入拍摄模式
this.pageMode = 'capture';
this.phase = 'waiting_surface'; // 重置为初始状态,但保留 Surface
this.surfaceReady = false;
// 清理附件相关的状态
this.attachmentState = '已撤销未确认的本地附件草稿';
this.statusMessage = '请重新启动预览后按人工构图引导拍摄';
this.errorMessage = '暂无错误';
}
private async resetInspection(): void {
// 清理临时文件
this.discardPendingPhoto();
// 释放相机会话
await this.releaseCameraSession();
// 重置所有状态为初始值
this.pageMode = 'inspect';
this.phase = 'waiting_surface';
this.fallbackReason = 'waiting';
this.permissionState = '未请求';
this.cameraState = '等待真实后摄查询';
this.autoFramingState = '等待 PhotoSession 能力检查';
this.focusState = '等待会话能力检查';
this.previewState = '等待 Surface';
this.attachmentState = '尚未生成本地附件草稿';
this.handoverState = '等待确认回退后登记复拍交接';
this.handoverTime = '尚未登记';
this.activeStepIndex = 0;
this.statusMessage = '本地处置会话已重置,等待新的真实能力检查';
this.errorMessage = '暂无错误';
}
private discardPendingPhoto(): void {
if (this.pendingPhotoPath === '') return;
try {
// 检查文件是否存在
if (fileIo.accessSync(this.pendingPhotoPath)) {
// 删除临时文件
fileIo.unlinkSync(this.pendingPhotoPath);
}
} catch (_) {
// 文件可能已删除或不可访问,忽略异常
}
// 清空路径记录
this.pendingPhotoPath = '';
}
五、异常恢复与边界条件处理
5.1 会话异常中断的恢复
场景:拍摄过程中相机会话异常中断
private readonly sessionErrorCallback = (error: Error): void => {
this.fallbackReason = 'session_failed';
this.failCamera('相机会话异常中断', error);
};
private failCamera(message: string, error: Error): void {
this.phase = 'failed';
this.previewState = '相机运行失败';
this.statusMessage = message;
this.errorMessage = this.formatError(error);
// 相机异常时,会话会被释放,状态标记为 'failed'
// 用户可以通过"重新检查能力"按钮重新开始
}
恢复策略:
- 清晰地向用户展示"会话异常"的状态
- 不自动清理已保存的附件文件(如果有的话)
- 提供"重新检查能力"的入口
- 如果已经有有效的附件,允许跳过能力检查直接进入交接
5.2 文件写入失败的处理
场景:JPEG 数据接收成功,但写入本地文件失败(磁盘满、权限不足等)
private async handleImageArrival(): Promise<void> {
const receiver = this.imageReceiver;
if (receiver === undefined || this.pendingPhotoPath === '') return;
let capturedImage: image.Image | undefined;
try {
capturedImage = await receiver.readLatestImage();
const component = await capturedImage.getComponent(image.ComponentType.JPEG);
const target = fileIo.openSync(
this.pendingPhotoPath,
fileIo.OpenMode.WRITE_ONLY | fileIo.OpenMode.CREATE | fileIo.OpenMode.TRUNC
);
try {
const written = fileIo.writeSync(target.fd, component.byteBuffer);
if (written <= 0) {
throw new Error('附件文件未写入有效 JPEG 数据。');
}
} finally {
fileIo.closeSync(target); // 必须在 finally 中关闭文件
}
// 成功路径...
} catch (error) {
this.failCapture('人工附件保存失败', error as Error);
// failCapture 会自动触发 discardPendingPhoto()
} finally {
if (capturedImage !== undefined) {
await capturedImage.release();
}
}
}
private failCapture(message: string, error: Error): void {
this.discardPendingPhoto(); // 清理失败的临时文件
this.phase = this.photoSession === undefined ? 'failed' : 'previewing';
this.statusMessage = message;
this.errorMessage = this.formatError(error);
// 如果会话还在运行(previewing),用户可以重新尝试拍摄
// 如果会话已关闭(failed),需要重新启动预览
}
恢复策略:
- 立即清理失败的临时文件,防止磁盘泄漏
- 区分"会话还活跃"和"会话已关闭"两种情况
- 如果会话还活跃,提供"重新拍摄"的快速入口
- 如果会话已关闭,必须重新启动预览
5.3 权限动态变化的处理

场景:已进入预览状态,权限被系统设置中撤销
private setupFocusAndFallback(session: camera.PhotoSession): void {
if (session.isFocusModeSupported(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO)) {
session.setFocusMode(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO);
this.focusState = '已设置连续自动对焦';
} else {
this.focusState = '设备不支持连续自动对焦';
}
// AUTO_FRAMING 检查(标记为不支持)
this.autoFramingState = '当前 PhotoSession 不提供 AUTO_FRAMING 控制接口,已转人工构图回退';
this.fallbackReason = 'unsupported';
}
private readonly sessionErrorCallback = (error: Error): void => {
// 如果运行中权限被撤销,会话会中断,触发此回调
this.fallbackReason = 'session_failed'; // 或识别为 'permission_denied'
this.failCamera('相机会话异常中断', error);
};
恢复策略:
- 会话中断时,记录中断原因(可能无法精确识别是权限还是其他故障)
- 提示用户"在系统设置中检查 CAMERA 权限"
- 保留已拍摄的附件,如果有的话
- 允许用户通过"重新检查能力"重新请求权限
六、审计记录的设计
6.1 能力检查阶段的审计信息
每次能力检查都应该记录以下信息:
| 字段 | 类型 | 含义 | 示例 |
|---|---|---|---|
checkTime | string | 检查时间戳 | 2026-08-15T14:30:45.123Z |
phase | FallbackPhase | 最终到达的阶段 | previewing | unavailable |
fallbackReason | FallbackReason | 回退原因 | unsupported |
deviceId | string | 后摄设备ID | 0 |
devicePosition | string | 摄像头位置 | CAMERA_POSITION_BACK |
previewProfile | string | 选中的预览Profile | 1920x1080@30fps |
photoProfile | string | 选中的拍照Profile | 4000x3000@jpeg |
permissionGranted | boolean | 权限是否授予 | true |
focusModeSupported | boolean | 是否支持连续自动对焦 | true |
autoFramingSupported | boolean | 是否支持AUTO_FRAMING | false |
statusCode | number | 最终状态代码 | 200 (success) | 401 (permission) | 404 (device) |
6.2 人工拍摄阶段的审计信息
interface CaptureAuditRecord {
captureTime: string; // 拍摄时间
attachmentPath: string; // 附件本地路径
attachmentSize: number; // 文件大小(字节)
mimeType: string; // 'image/jpeg'
quality: camera.QualityLevel; // 'QUALITY_LEVEL_HIGH'
rotation: number; // 0, 90, 180, 270
// 能力上下文
fallbackReason: FallbackReason; // 为什么进行人工拍摄
sessionDuration: number; // 会话运行时长(秒)
// 用户操作
captureAttempt: number; // 第N次拍摄尝试
retryCount: number; // 重新拍摄次数
}
6.3 交接阶段的完整审计链
interface CompleteAuditChain {
// 第一阶段:能力检查
capabilityCheck: {
checkTime: string;
fallbackReason: FallbackReason;
autoFramingSupported: boolean;
// ... 其他检查细节
};
// 第二阶段:人工拍摄
manualCapture: {
captureTime: string;
attachmentPath: string;
attachmentSize: number;
// ... 其他拍摄细节
};
// 第三阶段:附件交接
handover: {
handoverTime: string;
responsiblePerson: string;
retakeDeadline: string;
workOrderId: string;
batchId: string;
};
}
七、用户界面与交互设计
7.1 能力检查界面的关键信息展示
左侧取证步骤栏:显示当前进度和回退原因
├── 01 · 能力检查 (当前步骤)
│ ├─ 读取后摄、PhotoSession 与连续自动对焦能力
│ └─ [显示回退原因的详细说明]
├── 02 · 人工构图取证 (灰显 - 需能力检查完成)
│ └─ 仅在回退条件成立后开放
└── 03 · 后续复拍 (灰显 - 需附件生成)
└─ 登记时限与下一责任人
中央决策面板:显示能力检查结论
┌─ 自动构图回退判断 ─────────────────────┐
│ │
│ [PhotoSession 控制接口] [已查询] │
│ [AUTO_FRAMING] [不提供 → 需人工回退] │
│ [回退结论] [允许人工构图] │
│ │
│ ┌─ 实时预览区 ─────────────────────┐ │
│ │ ┌─────────────────────────────┐ │ │
│ │ │ [实时摄像头预览画面] │ │ │
│ │ │ [手动构图框指示] │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────┘ │ │
│ └─────────────────────────────────┘ │
│ │
│ [启动真实预览] [人工构图取证] │
└────────────────────────────────────────┘
右侧附件交接栏:显示交接预留信息
┌─ 人工附件与交接 ─────────────────────┐
│ │
│ 取证方式:人工构图 · 已允许 │
│ 附件记录:尚未生成本地附件草稿 │
│ 复拍时限:24 小时内完成补拍 │
│ 下一责任人:现场调度员(待指派) │
│ │
│ 交接状态:等待确认回退后登记 │
│ 复拍交接 │
│ 记录时间:尚未登记 │
└──────────────────────────────────────┘
7.2 人工拍摄界面
┌─ 人工构图取证 ───────────────────────┐
│ │
│ 请将故障部位完整置入黄色取景框。 │
│ 该操作只在真实 AUTO_FRAMING 回退 │
│ 结论成立后可执行。 │
│ │
│ ┌─ 实时预览区 ─────────────────────┐│
│ │ ┌─────────────────────────────┐││
│ │ │ [实时摄像头预览] │││
│ │ │ ┌─────────────────────┐ │││
│ │ │ │ 手动构图框 │ │││
│ │ │ │ (黄色边框) │ │││
│ │ │ │ 故障部位需置入此框 │ │││
│ │ │ └─────────────────────┘ │││
│ │ │ │││
│ │ └─────────────────────────────┘││
│ └─────────────────────────────────┘│
│ │
│ [拍摄人工构图附件] [返回回退判断] │
└──────────────────────────────────────┘
7.3 复核与交接界面
┌─ 复核人工构图附件 ────────────────────┐
│ │
│ 确认附件清晰、故障部位完整后, │
│ 再生成本地交接记录。 │
│ 不会上传文件或回写工单。 │
│ │
│ ┌─ 附件预览区 ──────────────────────┐│
│ │ [拍摄结果JPEG图像预览] ││
│ │ (宽度100%, 最大高度400px) ││
│ └───────────────────────────────────┘│
│ │
│ [重新拍摄] [确认附件并交接] │
└───────────────────────────────────────┘
八、实施验证与测试清单
8.1 能力检查阶段的验收标准
-
权限拒绝场景:
- 拒绝 CAMERA 权限后,
fallbackReason标记为permission_denied - 界面清晰提示"权限被拒绝,可在系统设置中重新授权"
- 允许进入人工附件交接流程
- 拒绝 CAMERA 权限后,
-
设备不可用场景:
- 后摄不存在或未返回可用 Profile 时,
fallbackReason标记为camera_unavailable - 界面提示"后摄设备不可用"或"输出能力不可用"
- 允许进入人工附件交接流程
- 后摄不存在或未返回可用 Profile 时,
-
会话异常场景:
- PhotoSession 创建或启动失败时,
fallbackReason标记为session_failed - 会话异常回调被准确触发
- 界面清晰展示"相机运行失败"的状态
- PhotoSession 创建或启动失败时,
-
自动构图不支持场景(目标场景):
- PhotoSession 正常运行,但不提供 AUTO_FRAMING 接口时,
fallbackReason标记为unsupported - 预览画面成功显示
- "人工构图取证"按钮被启用
- PhotoSession 正常运行,但不提供 AUTO_FRAMING 接口时,
8.2 人工拍摄阶段的验收标准
-
拍摄成功:
- 只有在
fallbackReason !== 'waiting'时才允许进入拍摄模式 - 拍摄结果正确保存到本地路径
- 文件格式为 JPEG,大小合理(>100KB, <5MB)
- 只有在
-
拍摄失败恢复:
- 文件写入失败时,临时文件被清理
- 用户可以立即重新拍摄(会话仍活跃情况下)
- 错误信息清晰展示
-
会话中断处理:
- 拍摄过程中相机异常中断,状态标记为
failed - 已拍摄的附件被保留
- 用户可以通过"重新检查能力"重新开始
- 拍摄过程中相机异常中断,状态标记为
8.3 交接与追溯的验收标准
-
交接信息完整性:
- 交接记录包含能力事实(
fallbackReason,autoFramingSupported等) - 交接记录包含复拍管理信息(
retakeDeadline,responsiblePerson) - 交接时间准确记录
- 交接记录包含能力事实(
-
审计链连续性:
- 从能力检查到人工拍摄到交接的完整链路可追溯
- 每个阶段的转移原因清晰可见
- 异常恢复过程中,审计链不中断
-
复拍流程:
- 复拍时允许撤销前次附件并重新拍摄
- 复拍次数被准确计数
- 多次复拍的所有附件路径被正确管理(或仅保留最新)
九、常见问题与应急处理
Q1: 为什么拍摄结果保存到本地,而不是直接上传?
A: 这是分层设计的考虑:
- 网络不稳定:现场可能无网络或网络不稳定,先本地保存可确保附件不丢失
- 权限边界清晰:本页面只负责能力检查和本地附件生成,上传由上层业务流程负责
- 支持离线流程:允许现场人员先完成本地交接,稍后在网络恢复时批量上传
- 降低页面复杂度:本页面专注于相机能力和附件生成,不涉及后端服务集成
Q2: 如何区分"主动选择人工取证"和"被迫降级"?
A: 通过 fallbackReason 字段:
unsupported:AUTO_FRAMING 接口不存在,是技术限制,属于"被迫"permission_denied:权限被拒绝,可能是用户选择,也可能是系统策略session_failed/camera_unavailable:硬件故障或不可用,属于"被迫"
在审计链上,这些原因都被清晰记录,上层业务可基于 fallbackReason 做出不同的处理(如重试、上报、外部回写)。
Q3: 如果权限在拍摄过程中被撤销怎么办?
A: 会话会异常中断,触发 sessionErrorCallback:
- 状态标记为
failed - 错误消息显示为"相机会话异常中断"
- 用户可以通过"重新检查能力"重新请求权限
- 如果已经拍摄过的附件,会被保留(不自动删除)
权限恢复后,用户可以选择重新检查能力,此时会重新请求权限。
Q4: 临时文件泄漏风险如何规避?
A: 多层防护:
- 明确清理时机:
discardPendingPhoto()在失败、复拍、重置时被调用 - finally 块保护:文件操作使用 try-finally 确保资源释放
- 路径追踪:
pendingPhotoPath始终记录待清理的文件路径 - 幂等操作:
discardPendingPhoto()中的accessSync()保证文件存在后再删除,异常被忽略
十、扩展建议
10.1 多附件场景支持
目前设计支持单个附件。如果需要支持多个角度的拍摄:
// 扩展方案
private attachmentList: Array<{
path: string;
captureTime: string;
angle: string; // "正面" "侧面" "俯视" 等
}> = [];
private addAttachmentToList(path: string, angle: string): void {
this.attachmentList.push({
path,
captureTime: timestamp(),
angle
});
}
10.2 手动对焦控制
如果 AUTO_FRAMING 不可用但焦点模式可配:
// 支持手动焦点控制
if (session.isFocusModeSupported(camera.FocusMode.FOCUS_MODE_AUTO)) {
session.setFocusMode(camera.FocusMode.FOCUS_MODE_AUTO);
// 提供焦点框 UI,允许用户点击选择焦点位置
session.setFocusPoint({ x: touchX / width, y: touchY / height });
}
10.3 与高级功能的集成
- 闪光灯控制:支持补光
- 曝光控制:手动调整亮度
- 变焦控制:支持光学或数字变焦
- 长按连拍:快速连续拍摄多张
总结
HarmonyOS 6.1.1 中的相机自动构图能力处理需要遵循以下核心原则:
- 完整的能力检查:从设备→权限→会话→构图控制,逐层检查
- 明确的回退原因分类:五层原因,对应不同的业务路由
- 保留能力事实:即使不能使用自动构图,也要清晰记录"为什么不能用"
- 人工拍摄的正当性:基于真实能力检查结论而启动,不是因为系统故障
- 完整的附件交接:包含能力事实、复拍管理、审计链的完整记录
- 异常恢复的可能性:权限恢复、网络恢复后能否重新开始
通过这套设计,现场取证系统可以优雅地处理设备差异,同时保持完整的业务可追溯性和流程连续性。
验证状态:✅ 本文对应的代码已集成到项目,所有能力检查、人工拍摄、附件交接、异常恢复流程均在 CameraManualFallbackPage.ets 中完整实现。
后续复拍建议:计划在后续版本中支持多角度拍摄(正面、侧面、俯视)、手动焦点控制、闪光灯补光等高级功能。
必要条件|模拟器与真机准备对照
| 条件 | API 24 模拟器 | HarmonyOS 6.1.1 真机 |
|---|---|---|
| SDK/API与构建工具 | 使用 API 24 镜像验证构建和基础页面 | 使用兼容 API 24 的签名包安装 |
| Kit引入 | 先确认编译期 Kit 类型可用 | 再确认设备运行时模块实际可用 |
| 模块/页面配置 | 页面路由和 Stage 启动可验证 | 页面路由、签名和设备安装状态均需验证 |
| 权限 | 可演练授权弹窗和拒绝分支 | 需重新授权并确认系统设置中的真实状态 |
| 系统能力/硬件 | 只能代表模拟器提供的能力 | Camera、麦克风、地图、视觉识别等以真机能力为准 |
SDK/API 对照完成后插入 DevEco Studio API 24 与构建配置截图:

授权对照完成后插入真实设备权限截图:

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

更多推荐


所有评论(0)