HarmonyOS APP开发中的相机基础
HarmonyOS APP开发中的相机基础
核心要点:掌握 HarmonyOS 相机开发的基础架构,理解 CameraManager 的核心职责,学会相机设备的枚举与管理,熟悉相机生命周期的状态流转,正确申请相机权限。
一、背景与动机
你有没有想过,手机里那个每天被打开几十次的相机 App,背后到底在干什么?
想象一下这个场景:周末你和朋友去露营,夕阳西下,你掏出手机准备拍一张绝美日落。打开相机——预览画面秒出,对焦丝滑,按下快门——照片瞬间保存。整个过程不到3秒,但背后却是一整套精密的硬件调度、状态管理和权限控制体系在协同工作。
在 HarmonyOS 上开发相机功能,不像调个 Intent 那么简单。你需要理解相机设备的管理方式,搞清楚会话(Session)的概念,掌握预览、拍照、录像三条数据流的生命周期,还要处理权限申请、设备热插拔、前后台切换等各种边界情况。
如果你一上来就写代码,大概率会踩坑——预览黑屏、权限被拒、相机打不开、切换摄像头崩溃……这些问题,本质上都是对相机基础架构理解不够深入导致的。
这篇文章,我们就从零开始,把 HarmonyOS 相机开发的地基打牢。
二、核心原理
2.1 相机整体架构
HarmonyOS 的相机能力由 @ohos.multimedia.camera 模块提供,整体架构可以分为三层:

关键概念解读:
| 概念 | 说明 | 类比 |
|---|---|---|
| CameraManager | 相机系统的总管家,负责设备管理和会话创建 | 物业经理 |
| CameraDevice | 物理相机设备(前置/后置/广角等) | 房间 |
| CameraInput | 相机输入流,连接设备和会话的桥梁 | 房间门 |
| Session | 会话,管理预览/拍照/录像的数据流 | 租约合同 |
| Output | 各种输出通道,决定数据去哪里 | 快递地址 |
2.2 相机生命周期状态机
相机的生命周期是理解一切的基础。一个相机设备从被打开到被释放,会经历以下状态流转:

状态说明:
- CLOSED:相机设备未打开,初始状态
- OPENING:正在打开相机,此过程可能耗时
- OPENED:相机已打开,可以创建会话
- CLOSING:正在关闭相机
- 在 OPENED 状态下,会话还有子状态:IDLE → CONFIGURED → READY
2.3 相机权限体系
HarmonyOS 对相机权限的管理非常严格,涉及两个层面:
- 声明权限:在
module.json5中声明ohos.permission.CAMERA - 运行时授权:首次使用时需要用户动态授权
权限流程如下:
flowchart LR
classDef primary fill:#4A90D9,stroke:#2C5F8A,color:#fff,font-weight:bold
classDef warning fill:#E8A838,stroke:#B87A1A,color:#fff,font-weight:bold
classDef error fill:#D94A4A,stroke:#8A2C2C,color:#fff,font-weight:bold
classDef info fill:#50B5A9,stroke:#2C7A6F,color:#fff,font-weight:bold
classDef purple fill:#9B59B6,stroke:#6C3483,color:#fff,font-weight:bold
A[声明权限]:::primary --> B{检查授权状态}:::warning
B -->|已授权| C[直接使用相机]:::info
B -->|未授权| D[请求用户授权]:::purple
D -->|用户同意| C
D -->|用户拒绝| E[提示并引导设置]:::error
三、代码实战
3.1 相机权限申请与检查
这是所有相机开发的第一步——确保你拿到了权限,否则后面的代码一行都跑不了。
import { camera } from '@kit.CameraKit';
import { abilityAccessCtrl, bundleManager, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 相机权限管理工具类
* 封装权限检查与请求的完整流程
*/
export class CameraPermissionHelper {
private static readonly CAMERA_PERMISSION: Permissions = 'ohos.permission.CAMERA';
/**
* 检查相机权限是否已授予
* @returns true 表示已授权,false 表示未授权
*/
static async checkPermission(): Promise<boolean> {
try {
const atManager = abilityAccessCtrl.createAtManager();
const bundleInfo = await bundleManager.getBundleInfoForSelf(
bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT
);
const grantStatus = await atManager.checkAccessToken(
bundleInfo.appInfo.accessTokenId,
CameraPermissionHelper.CAMERA_PERMISSION
);
return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
} catch (error) {
const err = error as BusinessError;
console.error(`[权限检查失败] code: ${err.code}, message: ${err.message}`);
return false;
}
}
/**
* 请求相机权限
* @returns true 表示用户授权,false 表示用户拒绝
*/
static async requestPermission(context: Context): Promise<boolean> {
// 先检查是否已经授权
const hasPermission = await CameraPermissionHelper.checkPermission();
if (hasPermission) {
console.info('[权限] 相机权限已授予,无需重复申请');
return true;
}
try {
const atManager = abilityAccessCtrl.createAtManager();
const result = await atManager.requestPermissionsFromUser(
context,
[CameraPermissionHelper.CAMERA_PERMISSION]
);
// 判断用户选择结果
const isGranted = result.authResults[0] === 0;
if (isGranted) {
console.info('[权限] 用户授予了相机权限');
} else {
console.warn('[权限] 用户拒绝了相机权限');
}
return isGranted;
} catch (error) {
const err = error as BusinessError;
console.error(`[权限请求失败] code: ${err.code}, message: ${err.message}`);
return false;
}
}
/**
* 引导用户前往系统设置页面手动开启权限
* 当用户拒绝权限后,可以调用此方法引导用户手动开启
*/
static async openPermissionSettings(context: Context): Promise<void> {
try {
const atManager = abilityAccessCtrl.createAtManager();
await atManager.requestPermissionsFromUser(
context,
[CameraPermissionHelper.CAMERA_PERMISSION]
);
} catch (error) {
console.error('[权限] 打开设置页面失败');
}
}
}
module.json5 中声明权限:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.CAMERA",
"reason": "$string:camera_permission_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
}
3.2 CameraManager 初始化与相机设备枚举
拿到权限后,下一步就是创建 CameraManager 并枚举可用的相机设备。
import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 相机设备管理器
* 负责初始化CameraManager、枚举设备、管理设备热插拔
*/
export class CameraDeviceManager {
private cameraManager: camera.CameraManager | null = null;
private cameraDevices: camera.CameraDevice[] = [];
private currentDevice: camera.CameraDevice | null = null;
// 设备变更回调
private onDeviceChange?: (devices: camera.CameraDevice[]) => void;
/**
* 初始化CameraManager
* 这是所有相机操作的第一步
*/
async init(context: Context): Promise<boolean> {
try {
// 创建CameraManager实例
this.cameraManager = camera.getCameraManager(context);
console.info('[CameraManager] 初始化成功');
// 枚举当前可用的相机设备
this.cameraDevices = this.cameraManager.getSupportedCameras();
console.info(`[CameraManager] 检测到 ${this.cameraDevices.length} 个相机设备`);
// 打印每个设备的信息
this.cameraDevices.forEach((device, index) => {
console.info(`[设备${index}] ID: ${device.cameraId}, ` +
`方向: ${device.cameraOrientation}, ` +
`位置: ${this.getCameraPositionText(device.cameraPosition)}, ` +
`类型: ${this.getCameraTypeText(device.cameraType)}`);
});
// 注册设备热插拔监听
this.registerDeviceChangeCallback();
return true;
} catch (error) {
const err = error as BusinessError;
console.error(`[CameraManager] 初始化失败: code=${err.code}, msg=${err.message}`);
return false;
}
}
/**
* 获取所有支持的相机设备
*/
getDevices(): camera.CameraDevice[] {
return this.cameraDevices;
}
/**
* 根据位置获取相机设备
* @param position 相机位置(前置/后置)
*/
getDeviceByPosition(position: camera.CameraPosition): camera.CameraDevice | null {
const device = this.cameraDevices.find(d => d.cameraPosition === position);
if (device) {
this.currentDevice = device;
console.info(`[CameraManager] 选中设备: ${device.cameraId}`);
} else {
console.warn(`[CameraManager] 未找到位置为 ${position} 的相机设备`);
}
return device;
}
/**
* 获取当前设备支持的输出能力
* 这是创建会话前必须了解的信息
*/
getSupportedOutputCapability(device: camera.CameraDevice): camera.CameraOutputCapability {
if (!this.cameraManager) {
throw new Error('CameraManager 未初始化');
}
return this.cameraManager.getSupportedOutputCapability(device);
}
/**
* 注册设备热插拔回调
* 当有新相机设备接入或移除时,系统会通过此回调通知
*/
private registerDeviceChangeCallback(): void {
if (!this.cameraManager) return;
this.cameraManager.on('cameraStatus', (statusInfo: camera.CameraStatusInfo) => {
console.info(`[设备变更] 设备: ${statusInfo.camera.cameraId}, ` +
`状态: ${this.getStatusText(statusInfo.status)}`);
// 重新枚举设备列表
this.cameraDevices = this.cameraManager!.getSupportedCameras();
// 通知外部
this.onDeviceChange?.(this.cameraDevices);
});
}
/**
* 注销设备变更监听
*/
unregisterDeviceChangeCallback(): void {
if (!this.cameraManager) return;
this.cameraManager.off('cameraStatus');
}
/**
* 释放CameraManager资源
*/
release(): void {
this.unregisterDeviceChangeCallback();
this.cameraManager = null;
this.cameraDevices = [];
this.currentDevice = null;
console.info('[CameraManager] 资源已释放');
}
// ===== 工具方法 =====
private getCameraPositionText(position: camera.CameraPosition): string {
const map: Record<number, string> = {
[camera.CameraPosition.CAMERA_POSITION_UNSPECIFIED]: '未指定',
[camera.CameraPosition.CAMERA_POSITION_BACK]: '后置',
[camera.CameraPosition.CAMERA_POSITION_FRONT]: '前置',
};
return map[position] || '未知';
}
private getCameraTypeText(type: camera.CameraType): string {
const map: Record<number, string> = {
[camera.CameraType.CAMERA_TYPE_UNSPECIFIED]: '未指定',
[camera.CameraType.CAMERA_TYPE_WIDE_ANGLE]: '广角',
[camera.CameraType.CAMERA_TYPE_ULTRA_WIDE]: '超广角',
[camera.CameraType.CAMERA_TYPE_TELEPHOTO]: '长焦',
[camera.CameraType.CAMERA_TYPE_TRUE_DEPTH]: '深感',
};
return map[type] || '未知';
}
private getStatusText(status: camera.CameraStatus): string {
const map: Record<number, string> = {
[camera.CameraStatus.CAMERA_STATUS_APPEAR]: '已接入',
[camera.CameraStatus.CAMERA_STATUS_DISAPPEAR]: '已移除',
[camera.CameraStatus.CAMERA_STATUS_AVAILABLE]: '可用',
[camera.CameraStatus.CAMERA_STATUS_UNAVAILABLE]: '不可用',
};
return map[status] || '未知';
}
}
3.3 完整的相机初始化流程(含生命周期管理)
这个示例把权限申请、CameraManager 初始化、CameraInput 创建、会话配置串起来,形成一个完整的相机启动流程。
import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 相机控制器 - 完整生命周期管理
* 演示从权限申请到相机启动的完整流程
*/
@Entry
@Component
struct CameraBasicPage {
// 相机核心对象
private cameraManager: camera.CameraManager | null = null;
private cameraInput: camera.CameraInput | null = null;
private previewOutput: camera.PreviewOutput | null = null;
private photoSession: camera.PhotoSession | null = null;
private surfaceId: string = '';
// UI 状态
@State cameraStatus: string = '未初始化';
@State deviceList: string[] = [];
@State currentDeviceIndex: number = 0;
@State errorMessage: string = '';
// XComponent 回调,用于获取预览的 Surface
private xComponentController: XComponentController = new XComponentController();
aboutToAppear(): void {
// 页面即将显示时,执行初始化
this.initCamera();
}
aboutToDisappear(): void {
// 页面即将消失时,释放所有资源
this.releaseCamera();
}
/**
* 相机初始化主流程
* 严格按照:权限 → Manager → 设备 → Input → Session 的顺序
*/
async initCamera(): Promise<void> {
this.cameraStatus = '正在初始化...';
this.errorMessage = '';
try {
// 第一步:检查并申请权限
const hasPermission = await CameraPermissionHelper.requestPermission(this.context);
if (!hasPermission) {
this.cameraStatus = '权限被拒绝';
this.errorMessage = '请授予相机权限后重试';
return;
}
// 第二步:创建 CameraManager
this.cameraManager = camera.getCameraManager(this.context);
this.cameraStatus = 'CameraManager 已创建';
// 第三步:枚举相机设备
const devices = this.cameraManager.getSupportedCameras();
this.deviceList = devices.map((d, i) =>
`设备${i}: ${this.getCameraPositionText(d.cameraPosition)} ${this.getCameraTypeText(d.cameraType)}`
);
if (devices.length === 0) {
this.cameraStatus = '未检测到相机设备';
this.errorMessage = '当前设备不支持相机功能';
return;
}
// 第四步:创建 CameraInput(默认使用后置摄像头)
const targetDevice = devices[this.currentDeviceIndex];
this.cameraInput = this.cameraManager.createCameraInput(targetDevice);
this.cameraStatus = 'CameraInput 已创建';
// 第五步:打开相机
await this.cameraInput.open();
this.cameraStatus = '相机已打开';
// 第六步:获取输出能力并创建预览输出
const outputCapability = this.cameraManager.getSupportedOutputCapability(targetDevice);
const previewProfiles = outputCapability.previewProfiles;
if (previewProfiles.length === 0) {
this.cameraStatus = '不支持预览';
return;
}
// 使用第一个预览配置(实际开发中应选择最合适的)
this.previewOutput = this.cameraManager.createPreviewOutput(previewProfiles[0], this.surfaceId);
// 第七步:创建并配置会话
this.photoSession = this.cameraManager.createPhotoSession(
this.cameraInput,
this.previewOutput
);
this.cameraStatus = '会话已创建,准备就绪';
console.info('[相机初始化] 全部完成');
} catch (error) {
const err = error as BusinessError;
this.cameraStatus = '初始化失败';
this.errorMessage = `错误码: ${err.code}, 消息: ${err.message}`;
console.error(`[相机初始化失败] ${this.errorMessage}`);
}
}
/**
* 切换前后摄像头
*/
async switchCamera(): Promise<void> {
if (!this.cameraManager) return;
try {
// 先释放当前会话和输入
this.photoSession?.stop();
this.cameraInput?.close();
// 切换设备索引
const devices = this.cameraManager.getSupportedCameras();
this.currentDeviceIndex = (this.currentDeviceIndex + 1) % devices.length;
const targetDevice = devices[this.currentDeviceIndex];
// 重新创建 CameraInput
this.cameraInput = this.cameraManager.createCameraInput(targetDevice);
await this.cameraInput.open();
// 重新创建会话
const outputCapability = this.cameraManager.getSupportedOutputCapability(targetDevice);
this.previewOutput = this.cameraManager.createPreviewOutput(
outputCapability.previewProfiles[0],
this.surfaceId
);
this.photoSession = this.cameraManager.createPhotoSession(
this.cameraInput,
this.previewOutput
);
this.cameraStatus = `已切换到${this.getCameraPositionText(targetDevice.cameraPosition)}摄像头`;
console.info(`[切换摄像头] ${this.cameraStatus}`);
} catch (error) {
const err = error as BusinessError;
this.errorMessage = `切换失败: ${err.message}`;
console.error(`[切换摄像头失败] ${this.errorMessage}`);
}
}
/**
* 释放所有相机资源
* 严格按照逆序释放:Session → Output → Input → Manager
*/
releaseCamera(): void {
try {
// 1. 停止并释放会话
if (this.photoSession) {
this.photoSession.stop();
this.photoSession.release();
this.photoSession = null;
}
// 2. 释放预览输出
if (this.previewOutput) {
this.previewOutput.release();
this.previewOutput = null;
}
// 3. 关闭并释放相机输入
if (this.cameraInput) {
this.cameraInput.close();
this.cameraInput.release();
this.cameraInput = null;
}
this.cameraStatus = '资源已释放';
console.info('[相机资源] 全部释放完成');
} catch (error) {
console.error(`[资源释放失败] ${error}`);
}
}
build() {
Column() {
// 状态显示
Text(`相机状态: ${this.cameraStatus}`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ top: 20, bottom: 10 })
// 预览区域
XComponent({
id: 'cameraPreview',
type: XComponentType.SURFACE,
controller: this.xComponentController
})
.onLoad(() => {
// XComponent 加载完成后获取 surfaceId
this.surfaceId = this.xComponentController.getXComponentSurfaceId();
console.info(`[XComponent] surfaceId: ${this.surfaceId}`);
// 获取到 surfaceId 后重新初始化预览
if (this.cameraManager && this.cameraInput) {
this.setupPreview();
}
})
.width('100%')
.height(400)
.backgroundColor(Color.Black)
// 设备列表
List() {
ForEach(this.deviceList, (item: string, index: number) => {
ListItem() {
Text(item)
.fontSize(14)
.fontColor(index === this.currentDeviceIndex ? '#4A90D9' : '#999')
.padding(8)
}
})
}
.width('90%')
.height(100)
.margin({ top: 10 })
// 操作按钮
Row() {
Button('切换摄像头')
.onClick(() => this.switchCamera())
.margin({ right: 10 })
Button('重新初始化')
.onClick(() => this.initCamera())
}
.margin({ top: 10 })
// 错误信息
if (this.errorMessage) {
Text(this.errorMessage)
.fontSize(14)
.fontColor(Color.Red)
.margin({ top: 10 })
.padding(10)
.backgroundColor('#FFF0F0')
.borderRadius(8)
}
}
.width('100%')
.height('100%')
}
// ===== 工具方法 =====
private get context(): Context {
return getContext(this);
}
private getCameraPositionText(position: camera.CameraPosition): string {
if (position === camera.CameraPosition.CAMERA_POSITION_BACK) return '后置';
if (position === camera.CameraPosition.CAMERA_POSITION_FRONT) return '前置';
return '未指定';
}
private getCameraTypeText(type: camera.CameraType): string {
if (type === camera.CameraType.CAMERA_TYPE_WIDE_ANGLE) return '广角';
if (type === camera.CameraType.CAMERA_TYPE_ULTRA_WIDE) return '超广角';
if (type === camera.CameraType.CAMERA_TYPE_TELEPHOTO) return '长焦';
return '';
}
/**
* 配置预览(在 XComponent 加载后调用)
*/
private async setupPreview(): Promise<void> {
try {
const devices = this.cameraManager!.getSupportedCameras();
const targetDevice = devices[this.currentDeviceIndex];
const outputCapability = this.cameraManager!.getSupportedOutputCapability(targetDevice);
this.previewOutput = this.cameraManager!.createPreviewOutput(
outputCapability.previewProfiles[0],
this.surfaceId
);
this.photoSession = this.cameraManager!.createPhotoSession(
this.cameraInput!,
this.previewOutput
);
console.info('[预览配置] 完成');
} catch (error) {
console.error(`[预览配置失败] ${error}`);
}
}
}
四、踩坑与注意事项
4.1 权限被拒绝后的处理
坑:用户拒绝了相机权限,App 直接崩溃或卡死。
解:一定要在调用相机 API 之前检查权限,被拒绝后给用户友好的提示,引导去设置页面手动开启。
// ❌ 错误做法:不检查权限直接用
const cameraManager = camera.getCameraManager(context); // 可能崩溃
// ✅ 正确做法:先检查再使用
const hasPermission = await CameraPermissionHelper.checkPermission();
if (!hasPermission) {
// 显示提示对话框,引导用户授权
return;
}
4.2 CameraManager 重复创建问题
坑:多次调用 camera.getCameraManager(context) 可能导致资源泄漏。
解:CameraManager 是单例模式,同一个 context 多次调用返回的是同一个实例。但建议在应用层做缓存,避免不必要的调用。
4.3 CameraInput 忘记 open()
坑:创建了 CameraInput 但忘记调用 open(),导致后续创建会话时报错。
解:CameraInput 创建后必须显式调用 open() 方法,这是一个异步操作,需要 await。
// ❌ 错误:忘记 open
const cameraInput = cameraManager.createCameraInput(device);
// 直接创建会话 → 报错!
// ✅ 正确:先 open 再使用
const cameraInput = cameraManager.createCameraInput(device);
await cameraInput.open();
// 然后创建会话
4.4 设备热插拔导致崩溃
坑:外接 USB 相机被拔出时,如果还在使用该设备,App 会崩溃。
解:注册 cameraStatus 回调,在设备消失时及时释放资源。
cameraManager.on('cameraStatus', (statusInfo: camera.CameraStatusInfo) => {
if (statusInfo.status === camera.CameraStatus.CAMERA_STATUS_DISAPPEAR) {
// 设备消失了,立即释放相关资源
this.releaseCamera();
}
});
4.5 surfaceId 为空字符串
坑:XComponent 的 onLoad 回调还没执行,surfaceId 还是空字符串,就传给了 createPreviewOutput。
解:确保在 onLoad 回调中获取到 surfaceId 后,再进行预览输出的创建。
4.6 资源释放顺序
坑:先释放 CameraInput,再释放 Session,导致 Session 中引用了已释放的 Input,报错。
解:严格按照创建的逆序释放:Session → Output → Input。
五、HarmonyOS 6 适配
5.1 API 变更
| 变更项 | HarmonyOS 5.0 | HarmonyOS 6.0 |
|---|---|---|
| CameraManager 创建 | camera.getCameraManager(context) |
保持不变 |
| 设备枚举 | getSupportedCameras() |
保持不变,新增 getSupportedCamerasSync() 同步方法 |
| 权限模型 | @ohos.abilityAccessCtrl |
保持不变,但新增了更细粒度的权限控制 |
| 错误码 | 数字错误码 | 新增了结构化错误信息 CameraError |
5.2 迁移要点
-
同步 API 使用:HarmonyOS 6.0 新增了部分同步方法,如
getSupportedCamerasSync(),在性能敏感场景可以优先使用。 -
错误处理增强:6.0 中错误信息更加详细,建议更新错误处理逻辑:
// HarmonyOS 6.0 增强的错误处理
try {
const cameraManager = camera.getCameraManager(context);
} catch (error) {
const cameraError = error as camera.CameraError;
// 新增了更详细的错误分类
console.error(`[相机错误] 类型: ${cameraError.code}, 描述: ${cameraError.message}`);
}
- 生命周期感知增强:6.0 中 CameraInput 的状态回调更加完善,新增了
on('error')回调,可以监听相机设备的硬件异常。
六、总结
mindmap
root((相机基础))
权限管理
声明 ohos.permission.CAMERA
运行时动态授权
被拒后引导设置
CameraManager
创建与初始化
枚举相机设备
设备热插拔监听
获取输出能力
CameraInput
创建并绑定设备
必须显式 open
状态回调监听
使用后必须 close
相机生命周期
CLOSED → OPENING → OPENED
会话子状态 IDLE → CONFIGURED → READY
释放顺序 Session → Output → Input
踩坑要点
权限检查前置
surfaceId 非空校验
资源释放逆序
设备热插拔处理
核心记忆口诀:
权限先申请,Manager 再创建;设备要枚举,Input 必须 open;Session 管数据流,释放要逆序。
下篇文章我们将深入「拍照」功能,讲解 PhotoSession 的使用、拍照参数配置、闪光灯控制等实战内容。
- 点赞
- 收藏
- 关注作者
评论(0)