文章目录
- 前言
- Camera Kit 架构
- 权限配置
- 创建预览
- 拍照实现
- 保存到相册
- 前后置切换
- 踩坑记录
- 写在最后
前言
相机开发,可以说是移动端最复杂的功能之一。HarmonyOS7 把相机能力收敛到 Camera Kit 里,架构比以前清晰了不少,但流程还是长——创建管理器、选摄像头、建会话、配流、启动预览、拍照、保存……少一步都不行。我第一次写的时候漏了commitConfig,预览死活出不来,排查了两小时。今天把完整流程捋一遍,每一步都有代码和讲解,照着走不会翻车。
Camera Kit 架构
Camera Kit 分三层,搞清层级关系才不会迷:
CameraManager ← 管理层:创建/获取所有相机对象 ├── CameraInput ← 输入层:选哪个摄像头(前/后) ├── PreviewOutput ← 输出层-预览流:画面实时显示 ├── PhotoOutput ← 输出层-拍照流:拍照后的图片数据 └── PhotoSession ← 会话层:把输入和输出串起来 ├── addInput() ├── addOutput() ├── commitConfig() └── start()简单理解:CameraManager 是总管,CameraInput 是眼睛,PreviewOutput/PhotoOutput 是嘴巴,Session 把它们绑一起才能工作。
权限配置
相机开发要两个权限,module.json5里必须声明:
{"requestPermissions":[{"name":"ohos.permission.CAMERA"},{"name":"ohos.permission.WRITE_IMAGEVIDEO"}]}CAMERA是拍照权限,WRITE_IMAGEVIDEO是保存到相册的权限。都要动态申请,静态声明不够:
import{abilityAccessCtrl,common}from'@kit.AbilityKit'asyncrequestPermissions():Promise<boolean>{constatManager=abilityAccessCtrl.createAtManager()constcontext=getContext(this)ascommon.UIAbilityContextconstpermissions=['ohos.permission.CAMERA','ohos.permission.WRITE_IMAGEVIDEO']constresult=awaitatManager.requestPermissionsFromUser(context,permissions)returnresult.authResults.every((r:number)=>r===0)}别忘了在
onPageShow或按钮点击时调这个方法。用户第一次弹授权弹窗可能拒绝,要做好拒绝后的提示处理。
创建预览
预览是相机开发的第一步,也是最关键的一步——预览出不来,后面全白搭。
import{camera}from'@kit.CameraKit'privatecameraManager:camera.CameraManager|null=nullprivatepreviewOutput:camera.PreviewOutput|null=nullprivatephotoOutput:camera.PhotoOutput|null=nullprivatesession:camera.PhotoSession|null=nullprivatesurfaceId:string=''asyncinitCamera(surfaceId:string){this.surfaceId=surfaceId// 1. 获取 CameraManagerconstcontext=getContext(this)ascommon.UIAbilityContextthis.cameraManager=camera.getCameraManager(context)// 2. 获取可用摄像头constcameras=this.cameraManager.getSupportedCameras()if(cameras.length===0){console.error('没有可用摄像头')return}constbackCamera=cameras[0]// 默认后置// 3. 创建 CameraInputconstcameraInput=this.cameraManager.createCameraInput(backCamera)cameraInput.open()// 4. 创建 PreviewOutputconstoutputCapability=this.cameraManager.getSupportedOutputCapability(backCamera)constpreviewProfile=outputCapability.previewProfiles[0]this.previewOutput=this.cameraManager.createPreviewOutput(previewProfile,surfaceId)// 5. 创建 PhotoOutputconstphotoProfile=outputCapability.photoProfiles[0]this.photoOutput=this.cameraManager.createPhotoOutput(photoProfile)// 6. 创建 Session 并配置this.session=this.cameraManager.createPhotoSession(cameraInput,this.previewOutput,this.photoOutput)awaitthis.session.commitConfig()awaitthis.session.start()}关键讲解:
surfaceId从 XComponent 组件获取,用来承载预览画面。UI 层要放一个XComponent:XComponent({id:'cameraXComponent',type:XComponentType.SURFACE,libraryname:''}).onLoad(()=>{this.initCamera(this.xComponentController.getXComponentSurfaceId())})outputCapability拿到摄像头支持的配置,取第一个就行。想精细控制可以遍历选合适的分辨率commitConfig()一定要调!我就是忘了这步,预览黑屏排查两小时。配置改了也得重新 commit
拍照实现
预览跑起来了,拍照就简单了——三步走:
// 1. 先注册照片可用回调setupPhotoCallback(){if(!this.photoOutput)returnthis.photoOutput.on('photoAvailable',(errCode:BusinessError,photo:camera.Photo)=>{if(errCode||!photo){console.error('拍照失败: '+errCode?.code)return}constimageObj=photo.main imageObj.getComponent(image.ComponentType.JPEG,(err,component)=>{if(err||!component?.byteBuffer)returnthis.saveToGallery(component.byteBuffer)imageObj.release()// ← 这行绝对不能漏!})})}// 2. 触发拍照takePhoto(){if(!this.photoOutput)returnconstsettings:camera.PhotoCaptureSetting={quality:camera.QualityLevel.QUALITY_LEVEL_HIGH,rotation:camera.ImageRotation.ROTATION_0,mirror:false}this.photoOutput.capture(settings)}关键 API 讲解:
photoOutput.on('photoAvailable', ...):拍照成功后的数据回调。必须在拍照前注册,不然照片数据拿不到photo.main:获取主图,还有photo.thumbnail可以拿缩略图getComponent(image.ComponentType.JPEG):从 Image 对象提取 JPEG 数据,返回ArrayBufferimageObj.release():释放资源!不调会导致 BufferQueue 阻塞,后续拍照全部失败。这是最高频的坑photoOutput.capture(settings):触发拍照。settings 可选,不传也行
保存到相册
拿到图片 Buffer 后,保存到系统相册:
import{photoAccessHelper}from'@kit.MediaLibraryKit'import{fileIo}from'@kit.CoreFileKit'asyncsaveToGallery(buffer:ArrayBuffer){consthelper=photoAccessHelper.getPhotoAccessHelper(getContext(this))consturi=awaithelper.createAsset(photoAccessHelper.PhotoType.IMAGE,'jpg')constfile=fileIo.openSync(uri,fileIo.OpenMode.READ_WRITE)fileIo.writeSync(file.fd,buffer)fileIo.closeSync(file.fd)console.info('保存成功: '+uri)}createAsset在相册创建一条空记录并返回 URI,然后用fileIo把数据写进去。整个流程就是"先开个房间号,再把行李搬进去"。
前后置切换
切换摄像头不是简单换个 CameraInput,得重新建 Session:
@StateisBackCamera:boolean=trueasyncswitchCamera(){if(!this.cameraManager)return// 先停当前会话awaitthis.session?.stop()constcameras=this.cameraManager.getSupportedCameras()consttargetCamera=this.isBackCamera?cameras[1]:cameras[0]this.isBackCamera=!this.isBackCameraconstcameraInput=this.cameraManager.createCameraInput(targetCamera)awaitcameraInput.open()// 重建 Sessionthis.session=this.cameraManager.createPhotoSession(cameraInput,this.previewOutput!,this.photoOutput!)awaitthis.session.commitConfig()awaitthis.session.start()}别想着只换 Input 不重建 Session——试过,不行。Session 绑定了 Input,得整体重建。
踩坑记录
| 坑 | 表现 | 解决 |
|---|---|---|
没调commitConfig() | 预览黑屏 | Session 配置完必须 commit |
没调imageObj.release() | 第二次拍照失败 | 回调里必须释放 |
| 没动态申请权限 | 直接闪退 | requestPermissionsFromUser |
surfaceId为空 | 预览不显示 | 在 XComponentonLoad里获取 |
| 后置摄像头不是 cameras[0] | 预览画面是前置 | 通过cameraPosition筛选 |
| 权限拒绝后直接调相机 | 崩溃 | 先判断授权结果 |
写在最后
Camera Kit 的流程确实长,但每一步都有存在的理由。核心就是那张架构图——Manager → Input/Output → Session,搞清这个逻辑链,流程就不会乱。最坑的三个点我都踩过了:commitConfig 别忘、release 别忘、权限别忘。
拍照只是相机功能的一部分,录像、闪光灯、对焦、变焦这些后面有机会再聊。下一篇换个轻松的话题——用加速度计做个摇一摇,几行代码就搞定。