OpenHarmony 原生 Camera 相机拍摄实现(API Version23 + 适配版)
摘要Camera 模块为系统原生相机能力支持实时预览、拍照保存图片、录像等功能适用于扫码、头像拍摄、单据上传等业务场景。API Version23 重构相机生命周期、设备释放、帧数据回调、存储写入逻辑修复低版本相机占用无法释放、多次打开黑屏、拍摄图片保存失败、切换前后摄像头崩溃、后台驻留相机耗电等兼容缺陷。旧项目升级 API23 后常出现页面退出相机未释放导致下次打开黑屏、拍摄无文件、预览画面拉伸变形、无相机权限直接闪退等问题。本文基于 DevEco Studio 适配 OpenHarmony API23 及以上版本封装相机通用工具类实现实时预览、拍照保存至沙盒、前后摄像头切换三大核心功能搭配拍摄页面完整实战代码输出权限校验、设备资源释放、图片持久化规范汇总版本升级兼容故障解决方案为鸿蒙相机拍摄业务开发提供标准化实操模板。关键词OpenHarmonyArkUIAPI Version23camera相机预览拍照媒体存储资源释放一、引言1.1 相机开发背景头像拍摄、证件上传、扫码识别、视频录制等场景都需要调用硬件相机。系统 camera 模块直接操作硬件摄像头相比相册选择能实时取景拍摄交互更贴合拍照类业务。相机属于独占硬件资源页面销毁必须主动释放设备否则会持续占用硬件导致其他应用无法调用相机。 OpenHarmony API Version23 针对 camera 底层硬件调度引擎完成全面升级核心变更点标准化 CameraDevice 完整生命周期创建→打开预览→拍摄→关闭释放缺失步骤直接报错新增硬件占用强回收机制页面销毁未释放相机则系统强制断开硬件统一拍摄图片沙盒写入逻辑修复图片丢失、路径无效、无写入权限问题重构 Surface 预览绑定规则修复预览画面拉伸、黑屏、画面倒置强化 CAMERA 动态权限强校验未授权调用直接抛出可捕获异常区分前后摄像头设备 ID切换摄像头自动销毁重建预览流无画面卡顿。大量 API9~11 旧项目升级后二次进入相机页面黑屏、拍摄图片找不到、切换摄像头闪退根源是未完整释放 CameraDevice 硬件资源因此掌握 API23 标准相机生命周期与资源释放规范是拍摄类功能必备技能。1.2 开发环境与测试场景开发工具DevEco Studio 5.0 及以上 适配系统OpenHarmony API Version23、HarmonyOS NEXT 导入模块ohos.multimedia.camera、ohos.file.fs、ohos.file.common 前置依赖动态 CAMERA 权限、存储写入权限 测试场景实时相机预览、拍摄照片保存本地、前后摄像头切换、页面退出释放相机、无权限拦截提示二、API23 camera 相机核心 API 与版本变更说明2.1 核心对象生命周期顺序CameraManager相机管理器获取设备列表CameraDevice摄像头硬件实例核心独占资源用完必须 closePreviewOutput预览输出流绑定 XComponent 渲染画面CaptureOutput拍照输出流处理拍摄图片保存2.2 关键核心方法etsimport camera from ohos.multimedia.camera // 获取相机管理器 const camMgr camera.getCameraManager(getContext(this)) // 获取全部摄像头设备ID列表 const deviceIds camMgr.getSupportedCameraDeviceIds() // 打开摄像头硬件 const cameraDev await camMgr.openCamera(deviceId) // 创建预览输出 const preview await camMgr.createPreviewOutput(surfaceId, cameraDev) // 创建拍照输出 const capture await camMgr.createCaptureOutput(fileFd, cameraDev) // 开始预览 preview.start() // 执行拍照 await capture.capture() // 停止预览、关闭硬件必须执行 preview.stop() cameraDev.close()2.3 XComponent 预览容器相机画面依赖 XComponent 组件承载通过 onSurfaceCreated 获取 surfaceId 绑定预览流。2.4 API23 废弃与强制约束废弃页面销毁不执行 cameraDev.close () 写法硬件长期占用造成黑屏禁止多页面同时持有 CameraDevice 实例同一时间仅允许一个页面使用相机拍摄图片必须传入文件文件描述符 fd不支持直接传入绝对路径必须前置动态申请 ohos.permission.CAMERA 权限无权限直接捕获异常切换摄像头必须先关闭当前 CameraDevice再重新创建新设备实例后台切页必须停止预览并关闭相机避免持续耗电、硬件占用。三、API23 标准基础示例代码3.1 基础相机打开与预览流程etsimport camera from ohos.multimedia.camera import common from ohos.app.ability.common let cameraDev: camera.CameraDevice | null null let previewOut: camera.PreviewOutput | null null async function openCamera(ctx: common.UIAbilityContext, surfaceId: string, deviceId: string) { const camMgr camera.getCameraManager(ctx) // 打开硬件摄像头 cameraDev await camMgr.openCamera(deviceId) // 创建预览输出绑定XComponent画布 previewOut await camMgr.createPreviewOutput(surfaceId, cameraDev) // 启动预览画面 await previewOut.start() } // 释放全部相机资源页面退出必调用 async function releaseCamera() { if (previewOut) { await previewOut.stop() previewOut null } if (cameraDev) { await cameraDev.close() cameraDev null } }3.2 基础拍照并保存沙盒文件etsimport fs from ohos.file.fs import camera from ohos.multimedia.camera async function takePhoto(ctx: common.UIAbilityContext) { const camMgr camera.getCameraManager(ctx) // 创建沙盒图片文件 const filePath ctx.filesDir /photo_ Date.now() .jpg const fd fs.openSync(filePath, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY) // 创建拍照输出流 const captureOut await camMgr.createCaptureOutput(fd, cameraDev!) // 执行拍摄 await captureOut.capture() // 关闭文件描述符 fs.closeSync(fd) return filePath }四、完整实战案例相机工具类 拍摄页面4.1 实战一相机工具封装 utils/camera_util.etsetsimport camera from ohos.multimedia.camera import fs from ohos.file.fs import common from ohos.app.ability.common import promptAction from ohos.promptAction class CameraUtil { private static instance: CameraUtil private ctx: common.UIAbilityContext | null null private cameraDev: camera.CameraDevice | null null private previewOut: camera.PreviewOutput | null null private curDeviceId: string static getInstance(): CameraUtil { if (!CameraUtil.instance) { CameraUtil.instance new CameraUtil() } return CameraUtil.instance } setContext(context: common.UIAbilityContext) { this.ctx context } // 获取前后摄像头ID0后置1前置 getCameraIds(): string[] { if (!this.ctx) return [] const mgr camera.getCameraManager(this.ctx) return mgr.getSupportedCameraDeviceIds() } // 初始化相机预览 async initCamera(surfaceId: string, deviceId: string): Promiseboolean { if (!this.ctx) return false // 先释放已有相机 await this.release() try { const mgr camera.getCameraManager(this.ctx) this.curDeviceId deviceId this.cameraDev await mgr.openCamera(deviceId) this.previewOut await mgr.createPreviewOutput(surfaceId, this.cameraDev) await this.previewOut.start() return true } catch (err) { promptAction.showToast({ message: 相机打开失败请检查权限 }) console.error(initCamera error, err) return false } } // 执行拍照返回图片沙盒路径 async takePicture(): Promisestring { if (!this.ctx || !this.cameraDev) return try { const mgr camera.getCameraManager(this.ctx) const fileName cam_ Date.now() .jpg const fullPath this.ctx.filesDir / fileName const fd fs.openSync(fullPath, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY) const captureOut await mgr.createCaptureOutput(fd, this.cameraDev) await captureOut.capture() fs.closeSync(fd) promptAction.showToast({ message: 拍摄完成 }) return fullPath } catch (err) { promptAction.showToast({ message: 拍摄失败 }) console.error(takePicture error, err) return } } // 释放相机全部硬件资源页面销毁强制调用 async release() { if (this.previewOut) { try { await this.previewOut.stop() } catch (e) {} this.previewOut null } if (this.cameraDev) { try { await this.cameraDev.close() } catch (e) {} this.cameraDev null } } } export default CameraUtil.getInstance()4.2 实战二相机拍摄页面XComponent 预览 切换摄像头 拍照etsimport CameraUtil from ../utils/camera_util import PermissionUtil, { PERMISSION } from ../utils/permission_util import ImagePickerUtil from ../utils/image_picker Entry Component struct CameraPage { State surfaceId: string State photoPath: ResourceStr deviceList: string[] [] State currentIndex: number 0 async aboutToAppear() { // 绑定上下文并校验相机权限 PermissionUtil.setContext(getContext(this)) const hasCamPerm await PermissionUtil.requestSingle(PERMISSION.CAMERA, 相机) if (!hasCamPerm) return CameraUtil.setContext(getContext(this)) this.deviceList CameraUtil.getCameraIds() } // XComponent画布创建完成初始化相机预览 onSurfaceReady(surface: string) { this.surfaceId surface if (this.deviceList.length 0) { CameraUtil.initCamera(this.surfaceId, this.deviceList[this.currentIndex]) } } // 切换前后摄像头 async switchCamera() { this.currentIndex this.currentIndex 0 ? 1 : 0 await CameraUtil.initCamera(this.surfaceId, this.deviceList[this.currentIndex]) } // 执行拍照 async shotPhoto() { const path await CameraUtil.takePicture() if (path) this.photoPath path } build() { Column() { // 相机预览画布 XComponent() .width(100%) .layoutWeight(1) .onSurfaceCreated((surface) this.onSurfaceReady(surface)) // 底部操作按钮栏 Row({ space: 30 }) { Button(切换摄像头).width(140).height(48).onClick(() this.switchCamera()) Button(拍照).width(140).height(48).backgroundColor(#f56c6c).onClick(() this.shotPhoto()) } .width(100%) .height(80) .justifyContent(FlexAlign.Center) // 拍摄预览缩略图 if (this.photoPath) { Image(this.photoPath) .width(80) .height(80) .position({ x: 10, y: -90 }) .borderRadius(8) } } .width(100%) .height(100%) } async aboutToDisappear() { // 页面退出强制释放相机硬件资源API23硬性要求 await CameraUtil.release() } }五、API23 相机适配与硬件性能优化规范5.1 硬件资源释放强制规范页面aboutToDisappear必须调用工具 release () 停止预览、关闭 CameraDevice切换摄像头先关闭当前设备再创建新预览流避免硬件冲突应用切后台 onBackground 主动释放相机前台返回再重新初始化。5.2 权限与存储规范打开相机前动态申请 CAMERA 权限无权限阻断相机初始化流程拍摄图片保存至应用沙盒 filesDir 目录无需额外存储权限拍照文件必须使用 fs 文件描述符 fd 传入 CaptureOutput禁止直接传路径。5.3 预览画面适配规范XComponent 铺满页面宽高避免画面拉伸、黑边设备 ID 0 默认后置主摄1 前置自拍自动区分设备列表初始化失败捕获异常弹窗提示不直接闪退应用。5.4 性能耗电规范长时间停留相机页面不频繁重复创建 / 销毁预览页面退出、切后台立刻关闭相机硬件停止图像解码节省电量不循环连续调用拍照增加按钮防抖锁防止重复拍摄。六、API23 升级高频兼容问题与解决方案问题 1第一次打开相机正常返回再次进入页面黑屏无画面 解决页面销毁执行 release 完整关闭 CameraDevice 硬件下次进入重新初始化。问题 2点击拍照无图片沙盒目录找不到照片文件 解决使用 fs.openSync 获取文件描述符 fd 传入拍摄输出不能直接传文件路径字符串。问题 3切换前后摄像头画面卡顿、直接闪退 解决切换前先释放当前预览与摄像头设备再新建预览流。问题 4无相机权限进入页面直接崩溃 解决页面加载前置动态权限校验未授予权限直接阻断相机初始化逻辑。问题 5切后台后再切回相机画面冻结不动 解决onBackground 释放相机onForeground 重新调用 initCamera 重建预览。问题 6多次打开相机后系统提示相机被其他应用占用 解决保证所有退出分支都执行 release无遗漏硬件关闭逻辑。七、总结camera 原生相机模块是鸿蒙硬件拍摄标准实现方案API Version23 严格标准化摄像头硬件生命周期强制要求页面销毁释放 CameraDevice 硬件资源解决旧版硬件长期占用、黑屏、拍摄图片丢失、切换摄像头崩溃等核心问题。项目封装独立相机工具类统一管理预览、拍照、设备释放逻辑搭配 XComponent 预览画布、动态权限校验完整实现前后摄像头切换、拍照保存本地核心业务。

相关新闻

秉持透明规范理念,探析中医药对糖尿病的调理思路

秉持透明规范理念,探析中医药对糖尿病的调理思路

糖尿病作为常见慢性代谢类疾病,发病诱因包含饮食结构、作息习惯、体质差异等多重因素,病程周期较长,日常管理存在不小挑战。不少患者在长期控糖过程中,希望获得规范、信息通畅的健康干预方案。百芝堂中医药在开展糖尿病相关健康服…

2026/7/12 5:23:10 阅读更多 →
海伯森光谱共焦位移传感器赋能光学玻璃镜片精密测高检测

海伯森光谱共焦位移传感器赋能光学玻璃镜片精密测高检测

车载镜头、安防监控镜头、高端数码成像镜头里,光学玻璃镜片几项关键尺寸 —— 整体高度、台阶高低、曲面顶点高度、装配定位落差,都是生产里必须盯紧的核心指标。镜片的高度尺寸精度,会直接影响镜头组装后的同心度、对焦清不清晰、画面畸变大…

2026/7/12 5:23:10 阅读更多 →
Matlab nlinfit非线性拟合实战:从黑箱到可控参数优化

Matlab nlinfit非线性拟合实战:从黑箱到可控参数优化

1. 项目概述:为什么非线性拟合不能只靠“点几下鼠标” 在工程建模、生物动力学分析、材料性能标定、传感器校准这些真实场景里,我见过太多人把 Matlab的Curve Fitting Toolbox 当成万能膏药——拖进数据、选个“Exponential”或“Gaussian”模型、点“…

2026/7/12 5:23:10 阅读更多 →

最新新闻

Huggingface 镜像站 hf-mirror.com 与 hf-cdn.sufy.com 对比:2种方案下载速度与稳定性实测

Huggingface 镜像站 hf-mirror.com 与 hf-cdn.sufy.com 对比:2种方案下载速度与稳定性实测

HuggingFace 镜像站深度评测:hf-mirror.com 与 hf-cdn.sufy.com 的实战对比在国内使用HuggingFace生态时,网络连接质量往往成为影响工作效率的关键因素。本文将针对两个主流镜像站——hf-mirror.com和hf-cdn.sufy.com,从下载速度、稳定性、功…

2026/7/12 6:15:23 阅读更多 →
GitHub Copilot CLI v0.0.369 安装与实战:5分钟完成终端AI编程环境搭建

GitHub Copilot CLI v0.0.369 安装与实战:5分钟完成终端AI编程环境搭建

GitHub Copilot CLI v0.0.369 终端AI编程环境5分钟极速搭建指南对于习惯在终端工作的开发者而言,GitHub Copilot CLI的推出无疑是一场效率革命。这个独立于IDE的命令行工具将AI编程能力直接注入到您的工作流中,让脚本编写、自动化任务和日常开发变得前所…

2026/7/12 6:15:23 阅读更多 →
Unity高性能场景管理:八叉树空间加速器实现与优化指南

Unity高性能场景管理:八叉树空间加速器实现与优化指南

1. 项目概述:为什么Unity开发者需要自己的空间加速器?如果你在Unity里做过稍微复杂点的3D项目,比如一个开放世界、一个策略游戏,或者一个有成百上千个动态物体的模拟场景,大概率遇到过这样的问题:当你想检测…

2026/7/12 6:13:23 阅读更多 →
H3C S7600-X/S12500 系列交换机指示灯全解析:从电源到端口的7类状态诊断

H3C S7600-X/S12500 系列交换机指示灯全解析:从电源到端口的7类状态诊断

H3C S7600-X/S12500 系列交换机指示灯全解析:从电源到端口的7类状态诊断在数据中心网络架构中,H3C S7600-X和S12500系列交换机作为核心设备,其运行状态直接关系到整个网络的稳定性。这些高端交换机通过丰富的指示灯系统,为运维人员…

2026/7/12 6:13:23 阅读更多 →
最小生成树算法:从并查集优化到斐波那契堆,3种性能提升方案实测

最小生成树算法:从并查集优化到斐波那契堆,3种性能提升方案实测

最小生成树算法:从并查集优化到斐波那契堆的深度性能调优在社交网络分析、地图路径规划和分布式系统设计中,最小生成树(MST)算法扮演着关键角色。当处理百万级节点的图数据时,传统Kruskal和Prim算法的性能瓶颈会显著暴…

2026/7/12 6:11:22 阅读更多 →
智慧校园平台怎么选?五大核心功能对比帮你少走弯路

智慧校园平台怎么选?五大核心功能对比帮你少走弯路

✅作者简介:合肥自友科技 📌核心产品:智慧校园平台(包括教工管理、学工管理、教务管理、考务管理、后勤管理、德育管理、资产管理、公寓管理、实习管理、就业管理、离校管理、科研平台、档案管理、学生平台等26个子平台) 。公司所有人员均有多…

2026/7/12 6:11:22 阅读更多 →

日新闻

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨 在一个阳光明媚的上午,互联网大厂的面试官坐在桌前,准备迎接他的面试候选人——燕双非,一个以搞笑和幽默著称的程序员。第一轮提问 面试官:燕双非,作…

2026/7/12 0:03:13 阅读更多 →
车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估

车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估

车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估在智能驾驶和车联网技术快速发展的今天,车载以太网作为新一代车载网络的核心传输技术,其物理层性能直接决定了数据传输的可靠性和稳定性。1000BASE-T1作为当前主流的…

2026/7/12 0:03:14 阅读更多 →
VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战,5步完成Keil工程转换

VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战,5步完成Keil工程转换

VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战指南嵌入式开发领域正经历一场工具链的静默革命。当传统Keil用户首次打开VSCode的扩展市场搜索EIDE时,往往会惊讶于这个看似简单的插件竟能重构十余年的开发习惯。本文将揭示如何用五个精准步骤&#xff0…

2026/7/12 0:03:14 阅读更多 →

周新闻

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨 在一个阳光明媚的上午,互联网大厂的面试官坐在桌前,准备迎接他的面试候选人——燕双非,一个以搞笑和幽默著称的程序员。第一轮提问 面试官:燕双非,作…

2026/7/12 0:03:13 阅读更多 →
车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估

车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估

车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估在智能驾驶和车联网技术快速发展的今天,车载以太网作为新一代车载网络的核心传输技术,其物理层性能直接决定了数据传输的可靠性和稳定性。1000BASE-T1作为当前主流的…

2026/7/12 0:03:14 阅读更多 →
VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战,5步完成Keil工程转换

VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战,5步完成Keil工程转换

VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战指南嵌入式开发领域正经历一场工具链的静默革命。当传统Keil用户首次打开VSCode的扩展市场搜索EIDE时,往往会惊讶于这个看似简单的插件竟能重构十余年的开发习惯。本文将揭示如何用五个精准步骤&#xff0…

2026/7/12 0:03:14 阅读更多 →

月新闻