当前位置: 首页 > news >正文

第24篇|相机权限和设备枚举:先判断能力再打开预览

双镜记忆相机的相机页不能只靠一个“打开相机”按钮。真正稳定的 CameraKit 入口,需要把 CAMERA 权限、Surface 是否就绪、设备列表、前后摄能力和并发能力都串成一条清晰链路。

学习目标

  • 知道 module.json5 与运行时授权分别解决什么问题。
  • 能从 CameraManager 获取设备列表,并区分后摄、前摄和单摄兜底设备。
  • 理解为什么要在预览前先做能力探测,而不是失败后再补救。
  • 把权限失败、设备为空、Surface 未就绪变成用户可理解的状态。

一、先看相机入口应该长什么样

第 24 篇先不急着拍照,而是把相机页打开前的地基铺稳。双镜记忆相机的目标是同时服务“双拍”和“单拍回退”,所以入口处要做三件事:页面上的 Surface 已经创建,用户已经允许 CAMERA 权限,设备侧至少能找到一个可用 CameraDevice。只有这三个条件都满足,后面的 PreviewOutput、PhotoSession、PhotoOutput 才有意义。

如果直接在页面出现时调用 createCameraInput,常见问题是黑屏、权限弹窗还没响应就初始化失败、部分机型只有一个摄像头时双摄逻辑反复重试。训练营的写法是先把能力探测做完整,再决定进入双摄、单摄或提示用户处理权限。

图 1 相机页运行效果与权限、设备探测链路

二、清单权限:module.json5 先声明 CAMERA

HarmonyOS 的相机权限不是只在代码里请求就够了,模块配置里必须先声明需要的权限和使用场景。项目的声明放在 entry/src/main/module.json5 的 requestPermissions 节点中,CAMERA 权限限定在 EntryAbility 的 inuse 场景。这样系统权限弹窗有合法来源,审核时也能看到权限用途。

这一步解决的是“应用有没有资格申请权限”的问题;下一步 requestPermissionsFromUser 解决的是“用户是否同意”。两个层次缺一不可。

图 2 module.json5 中的 CAMERA 权限声明

"requestPermissions": [ { "name": "ohos.permission.CAMERA", "reason": "$string:camera_permission_reason", "usedScene": { "abilities": [ "EntryAbility" ], "when": "inuse" } },

三、运行时授权:先查状态,再触发请求

进入相机页后,项目先通过 checkPermissionGrant 判断当前是否已经授权。如果已经授权,直接返回 true;如果还没有授权,再调用 requestPermissionsFromUser。这里还有一个细节:权限请求和短超时用 Promise.race 包住,避免用户停留在系统弹窗或系统返回异常时,页面一直处于“正在申请权限”的等待态。

这段逻辑的价值不只是拿到布尔值,而是让相机入口具备可恢复能力。权限已经给过时,不重复打扰用户;权限没有给时,页面能给出可见提示;授权结果回来后,再继续初始化 CameraKit。

图 3 Index.ets 中运行时 CAMERA 权限请求逻辑

private async requestCameraPermission(): Promise<boolean> { const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); const hostContext = this.getUIContext().getHostContext() as common.UIAbilityContext; try { const currentGrant = await this.checkPermissionGrant('ohos.permission.CAMERA'); if (currentGrant === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) { console.info('[superImage][capture] camera permission already granted'); return true; } console.info('[superImage][capture] request camera permission from user'); const requestPromise: Promise<PermissionRequestResult> = atManager.requestPermissionsFromUser( hostContext, this.cameraPermissionList ); const timeoutPromise: Promise<PermissionRequestResult | undefined> = new Promise((resolve) => { setTimeout(() => { resolve(undefined); }, 1800); }); const result = await Promise.race([requestPromise, timeoutPromise]); if (!result) {

四、设备枚举:从 CameraManager 推导可用路线

权限通过后,项目创建 CameraManager,并调用 getSupportedCameras 获取设备列表。这个列表不是拿来展示数字的,而是整个相机模式的决策输入:能找到后摄和前摄,就继续探测官方并发能力;只找到一个设备,就进入单摄预览;设备为空,就不要创建会话,直接结束探测并保持页面可重试。

注意这里没有把“双摄支持”当成默认成立。项目会先拿官方推荐的前后摄组合,再看 getCameraConcurrentInfos 是否返回内容。返回为空并不代表应用坏了,而是当前设备不支持前后摄同时工作,这时应该优雅回退到单摄。

图 4 权限通过后初始化 CameraKit 并枚举设备

this.cameraStatusText = '正在初始化 CameraKit...'; const hostContext = this.getUIContext().getHostContext() as common.UIAbilityContext; const cameraManager: camera.CameraManager = camera.getCameraManager(hostContext); const cameras: Array<camera.CameraDevice> = cameraManager.getSupportedCameras(); console.info(`[superImage][capture] camera devices=${cameras.length}`); this.cameraDeviceCount = cameras.length; this.cameraProbeResultText = this.buildConcurrentProbeReport(cameraManager, cameras); if (cameras.length === 0) { this.cameraCapabilityChecked = true; this.cameraStatusText = ''; return; } const officialPair = this.getOfficialConcurrentCameraPair(cameraManager); const backDevice = officialPair.backDevice ?? this.findCameraDeviceByPosition(cameras, camera.CameraPosition.CAMERA_POSITION_BACK); const frontDevice = officialPair.frontDevice ?? this.findCameraDeviceByPosition(cameras, camera.CameraPosition.CAMERA_POSITION_FRONT); const fallbackSingleDevice = backDevice ?? frontDevice ?? cameras[0]; this.cameraManager = cameraManager; this.backCameraDevice = backDevice; this.frontCameraDevice = frontDevice; this.preferredBackSingleCameraDevice = backDevice; this.singleCameraDevice = fallbackSingleDevice; this.singleCameraRole = this.getCameraRole(fallbackSingleDevice); this.singleCameraSupported = true; this.refreshBackLensOptions(cameras, backDevice); if (!backDevice || !frontDevice) { this.cameraCapabilityChecked = true; this.cameraStatusText = ''; await this.ensureCameraPreview(); return; } const concurrentInfos = officialPair.concurrentInfos; this.concurrentInfos = concurrentInfos; this.cameraCapabilityChecked = true; this.cameraConcurrentProfileCount = concurrentInfos.length; this.dualCameraSupported = concurrentInfos.length > 0; if (this.dualCameraSupported) { this.cameraStatusText = ''; await this.ensureCameraPreview(); } else { this.cameraStatusText = ''; await this.ensureCameraPreview();

五、把入口状态设计成可解释,而不是只看异常

相机入口最容易被忽略的是“半成功状态”。例如 Surface 还没创建,但权限已经允许;设备列表有后摄没有前摄;并发能力为空但单摄可用;权限弹窗超时但用户稍后又打开系统设置授权。项目没有把这些都归成一个 catch,而是拆成 cameraPermissionReady、cameraCapabilityChecked、dualCameraSupported、singleCameraSupported、cameraStatusText 等状态。

这么做的好处是页面能知道下一步应该做什么。Surface 未就绪就稍后 scheduleCameraCapabilityPrepare;权限未通过就展示提示;双摄不支持就调用 ensureCameraPreview 进入单摄;能力探测完成后不再重复请求。相机页由此从“碰运气打开”变成“按条件推进”。

训练营后续的双摄、镜头、闪光灯和失败态,都会复用这条入口链路。第 24 篇要记住的核心不是某一个 API,而是顺序:声明权限、请求授权、等待 Surface、枚举设备、探测并发、选择模式。

本篇检查清单

  • 权限声明位于 module.json5,并且 CAMERA 的 usedScene 指向 EntryAbility。
  • requestCameraPermission 先查已有授权,再请求用户授权。
  • prepareCameraCapability 在 Surface 未就绪时不会强行创建相机会话。
  • 设备为空、只有单摄、双摄并发为空都能走到明确分支。
  • 正文配图包含运行截图、权限声明截图、运行时权限代码截图和 CameraKit 初始化截图。

今日练习

  • 在真机上清除应用权限后重新进入相机页,观察权限弹窗和页面提示。
  • 给 prepareCameraCapability 增加一条日志,记录 cameras.length 与 concurrentInfos.length。
  • 把设备为空分支写成一条用户可读文案,再验证不会继续创建 PhotoSession。
http://www.zskr.cn/news/1429094.html

相关文章:

  • 打破Java字节码黑箱:JD-GUI的实战逆向工程指南
  • HS2-HF补丁:让Honey Select 2游戏体验焕然一新的终极解决方案
  • PyTorch实现的MANO手部模型:3D手势生成与计算机视觉应用终极指南
  • IGMP协议浅析
  • 2026 杭州直播代运营行业大洗牌,乱象频发,高 ROI 靠谱全链路服务商精选推荐 - 品牌榜中榜
  • 别再死磕梯度下降了!用Python手搓一个遗传算法,轻松搞定那些‘不听话’的优化问题
  • 别再让回车变空格了!手把手教你用JavaScript处理textarea换行符(含 转br实战)
  • 用Scratch打造钩针图案生成器:连接编程与手工的创意实践
  • 2026年 西安消防器材/消防设备/消防设施/灭火器材/应急消防器材最新推荐:精选品牌与实战性能深度解析! - 品牌企业推荐师(官方)
  • 从假设检验到机器学习:正态分布与卡方分布在数据分析中的实战联动指南
  • WarcraftHelper终极指南:让经典魔兽争霸3焕发新生,解决所有版本兼容问题
  • 乔布斯教会耄耋的事:在《一念成仙》,耄耋如何定义“最好的产品”
  • 告别深夜夺命Call:如何利用 AI Agent Skills 自动自愈生产环境故障
  • 免费数据恢复神器:TestDisk与PhotoRec的终极使用指南
  • 预训练模型破解AI搜索冷启动:从BERT到向量检索的实战指南
  • 告别杜邦线乱飞!用Arduino Uno和TM1650驱动数码管模块,一个IIC接口搞定四位显示
  • 嵌入式开发避坑指南:用HexView移动固件数据时,如何避免覆盖已有数据?
  • 别只刷题了!用‘整理高手’算法题,手把手教你理解双向冒泡排序的C++实现
  • 【几分钟搞定】OpenClaw 聊天渠道配置 飞书对接方法(包含安装包)
  • 2026年阿拉善左旗TOP4高性价比电器门店,哪家才是真正最低价?
  • 从BEV检测实战出发:深入理解Nuscenes与Argoverse数据集的坐标系‘基因’差异
  • 苏州做 GEO 效果怎么样?2026年行业实践解析 - 品牌排行榜
  • go swagger慢
  • 如何在Windows上高效安装安卓应用:APK安装器完整指南
  • 如何通过APKMirror安全获取安卓应用?这款开源客户端为你提供官方商店外的可靠选择
  • 2026年石家庄GEO优化权威排名:调研AI核心数据于深度解析指南优化避坑指南 - 资讯纵览
  • OBS-Multi-RTMP:一键开启多平台直播推流的终极解决方案
  • Inkscape光线追踪扩展终极指南:5分钟创建专业光学图表
  • 2026年锡林浩特哪些电器门店值得放心?看这份TOP5榜单
  • 终极免费视频下载助手:VideoDownloadHelper Chrome插件完全指南