HarmonyOS6.1.1-Camera:巡检相机权限已通过-预览为何仍可能无法启动 📅 2026/8/14 13:03:31 相机页面最容易出现一句让人误判的话权限已允许。看到它以后很多人会自然把后半句补上“那预览应该起来了。”我第一次排这个问题时也是如此。页面已经获得ohos.permission.CAMERA按钮也没有灰掉右侧却还是一块黑色区域状态停在“等待 Surface”或者“设备未查询”。那一刻最容易做的是反复申请权限或者把相机权限当成唯一排查入口。后来我才把流程拆开权限只是进门证预览真正跑起来还要经过 Surface 就绪、设备发现、输出能力查询、输入输出绑定和会话启动。门开了不等于里面每一盏灯都已经亮。我当时犯的第一个错误我把相机预览理解成一条直线点击按钮申请权限出现画面。可现有页面并不是这样设计的。它有明确的运行阶段type CameraPhase waiting_surface | requesting_permission | querying_device | starting_preview | previewing | unavailable | failed | released; State phase: CameraPhase waiting_surface; State surfaceReady: boolean false; State permissionState: string 未请求; State previewState: string 等待 Surface;这几个状态摆在一起就已经否定了“权限通过就等于预览成功”的想法。至少在点击前XComponent必须先完成加载否则应用没有一个可交给相机输出的 Surface。没有 Surface连创建预览输出都没有意义。否是否是打开相机页面XComponent onLoadsurfaceReady true点击启动真实后摄预览申请 CAMERA 权限是否授权unavailable: 权限未授予查询真实后摄设备是否有后摄和 previewProfileunavailable: 设备或输出能力不足创建输入、预览输出、VideoSessioncommitConfig 与 startpreviewing: 真实后摄预览运行中这张流程图的意义不在于背下来而是在黑屏出现时先找它卡在哪一格。不同格子的处理完全不同Surface 未就绪不能靠重复授权解决没有后摄设备也不能靠重建 UI 解决会话启动失败则需要看运行错误和资源释放路径。黑屏那次我先查错了地方当时页面已经显示“已授予”我就不断点击启动按钮。没有新画面我以为系统把权限结果缓存错了。实际上启动函数一开始就有一个前置判断private async startCameraSession(): Promisevoid { if (!this.surfaceReady) { this.previewState Surface 尚未就绪; this.markRuntime(未启动相机XComponent Surface 未就绪); return; } if (this.phase previewing || this.phase starting_preview) { return; } // 后面才会申请权限并创建会话。 }它把“页面还没准备好”留成了一个可解释的分支。这个分支很重要因为如果在 Surface 未就绪时仍然硬往下执行后面失败的位置会变得含糊。你可能看到的是创建输出失败、会话失败最后却误以为是相机 API 不稳定。我后来把排查动作改得很简单第一次进入页面不急着点按钮先看事件栏是否出现XComponent Surface 已就绪可请求真实后摄预览。如果没有先检查页面生命周期和 XComponent如果有再开始看权限与设备。这样做看似比直接点一次多了一步却会省掉很多无效猜测。权限之后还有两个真实查询用户同意授权后函数把阶段切到querying_device再从CameraManager找后摄设备。这里不会假设任何机器都有后摄更不会假设后摄一定提供普通视频预览 Profile。const manager camera.getCameraManager(getContext(this)); const device this.selectBackCamera(manager.getSupportedCameras()); if (device undefined) { this.phase unavailable; this.deviceState 未发现后摄设备; this.previewState 设备不支持后摄预览; return; } const capability manager.getSupportedOutputCapability(device, camera.SceneMode.NORMAL_VIDEO); const previewProfile capability.previewProfiles.length 0 ? capability.previewProfiles[0] : undefined;我很喜欢这里的写法它没有把“查询失败”压成一个笼统的“不支持”。找不到后摄和找不到previewProfile是两种不同问题。前者说明目标设备不在支持列表里后者说明设备虽然存在但这个场景没有可用的视频预览输出。两者后续的处理方式也不一样。以前我会把黑屏统称为“没有预览”现在更愿意把它写成具体句子Surface 未就绪、权限被拒绝、未发现后摄、没有预览 Profile、会话启动失败或者会话已经启动。每一句话都比“相机异常”更接近下一步。会话启动时别忽略资源的进出真正进入预览前页面会创建CameraInput、PreviewOutput和VideoSession依次加到会话里再提交配置、设置对焦与自动构图能力、最后调用start()。如果其中任何一步抛错页面会把阶段置为failed随后释放已经创建的资源。这不是为了把代码写得复杂而是避免下一次启动继承上次失败留下的半截资源。相机这类独占能力很像一个临时工作台工具没有收干净下一次拿起新工具时往往先被旧东西绊住。VideoSessionCameraManagerXComponent Surface页面VideoSessionCameraManagerXComponent Surface页面alt[启动成功][任一步失败]确认 Surface 已就绪请求 CAMERA 权限查询后摄与 previewProfile设备和输出能力 或 不可用beginConfig / addInput / addOutputcommitConfigstart进入 previewing抛出错误stop / releasephase failed 并保留错误文本这里有一条很实用的原则不要在catch里只写“启动失败”。至少要让页面同时保留阶段、预览状态、最后事件和错误摘要。现有 Demo 中的fail()就做了这件事。测试人员看到“运行失败”时可以继续看具体是哪一段调用出了问题而不是从头重跑所有步骤。我的测试顺序这页不适合用“允许权限后应该看到相机”这种一句话做测试。它会把多个阶段压成一个结果失败时没有方向。我现在按下面的顺序测否是否是否是否是进入页面事件栏是否显示 Surface 已就绪检查 XComponent onLoad 与页面初始化点击请求权限并启动permissionState 是否已授予记录拒绝或授权请求错误deviceState 是否返回后摄和尺寸记录设备或 previewProfile 不可用phase 是否进入 previewing查看 lastEvent 与 errorMessage 并检查资源释放记录当前会话已启动另行观察对焦与自动构图系统状态这里的最后一步故意写成“另行观察”。预览会话启动只证明当前页面的后摄预览链路已经走到previewing不证明连续自动对焦已经准确完成也不证明自动构图在当前系统控制中心已经启用。后两个问题需要单独看能力查询和实际系统反馈不能顺手搭车。我会把状态字段当作下一步的路标这页里最有用的不是某一个“成功”字样而是几个状态组合起来后的含义。phasewaiting_surface配合previewState等待 Surface说明还不该把排查带到权限phasequerying_device则说明授权已经过去页面正在读取真实设备能力。若deviceState写着未发现后摄继续反复点启动按钮不会创造出硬件若它已经写出后摄 ID 和尺寸却仍停在starting_preview或进入failed才该把注意力放到输入、输出、提交配置和异常文本上。我复测时会先记录这一组状态再按停止、重新进入页面、等待 Surface、重新启动的顺序走一遍。停止操作后应能看到会话已释放再次启动时页面重新查询设备并创建新的会话而不是沿用上一轮的对象。这个回归动作不能证明实际成像效果却能排除一个很常见的误会第一次失败后留下的输入或会话资源让第二次失败看起来像同一条错误。还有一点很容易忽略previewing是会话已启动的页面状态不是一张由页面生成的“预览成功截图”。右侧 XComponent 只承载实际 Camera 输出黑色背景本身不能被解释成有画面或没画面。需要评价真实预览时我会在目标真机上记录时间、设备、页面状态和屏幕结果当前代码没有加载静态图片替代因此也不该把页面的占位背景说成相机画面。这次问题留下的三个检查项我后来把黑屏按阶段记下来真正排这类问题时我不会只留一张黑色预览区的截图。那张图只能说明当时屏幕上没有我期待的画面说明不了流程走到了哪里。更有用的记录至少要同时写下阶段、权限、设备、预览状态、最近事件和错误文本。比如waiting_surface时按钮被禁用或提示 Surface 尚未就绪是合理结果这时反复要求用户去设置页开权限只会把人带离问题。等onLoad触发后surfaceReady变为 true页面才具备进入下一步的条件。如果授权被拒绝代码会把权限写成被拒绝并把预览停在未启动的位置。这里我会记录用户是否主动拒绝、是否有授权请求异常但不会把它和设备问题合并。授权已授予后getSupportedCameras()返回的列表才成为下一处判断依据。页面专门寻找后摄不是任意一个摄像头都能替代模拟器、无后摄的设备或某些设备模式下的可见列表不同都可能走到未发现后摄这一支。这个分支的后果是流程结束而不是再拿默认对象继续创建输入。找到设备也还没有完成。代码继续在普通视频场景查询输出能力并从previewProfiles中取第一个候选项。候选项为空时页面会明确写出设备没有提供视频预览输出能力。它和“后摄不存在”相似都是不可用但对排查来说不是同一句话前者要确认设备枚举和目标摄像头后者要确认该场景的输出能力。把两者分开后续拿到新的设备日志时才不会把硬件发现、场景能力和会话配置混在一起。进入starting_preview后我会特别留意错误发生在打开输入、创建输出、配置会话还是启动会话。源码把输入、输出、会话保存为成员异常后会进入释放逻辑因此页面的 failed 不是一句泛泛的“相机坏了”而是需要和错误摘要一起读。回归时我会故意先启动一次再点停止并离开页面确认释放后的状态能回到可再次启动的路径。这个检查只验证资源生命周期是否收口不表示第二次预览一定能给出同样的实际画面。还有一个容易被忽视的时间顺序页面刚出现时 Surface 的加载与用户点击并不保证谁先发生。按钮通过surfaceReady控制可用性是为了把这次竞态变成可见状态而不是让用户在尚无承载面的时刻提交一个注定无法绑定的输出。看到按钮不可点时我会先等待事件文本更新看到可点后再点一次即可。把等待写进测试步骤比“偶发黑屏重试即可”更能让后来的人复现和判断。用最小变量做一次回归我的回归不会同时换镜头、切后台、改权限和重装应用。先在同一台设备进入页面等待 Surface 状态明确再启动一次若失败保存本轮字段后停止并重新进入若成功也先停止并重新启动观察阶段是否能从 released 回到 querying_device 与 starting_preview。只有这条页面内路径稳定才会额外测试拒绝授权、无后摄或能力为空等环境分支。这样每一次现象都只有一个主要变量日志里的差异才有解释空间。如果要把结果交给别人复查我会把“没有启动”换成源码里的具体终点。例如“Surface 尚未就绪未进入权限请求”“权限被拒绝未查询设备”“未发现后摄未创建会话”“没有预览 Profile未绑定输出”“会话启动抛错已进入释放”。这些句子不会增加相机能力却准确划出代码已经做过和没有做过的事情。接手的人看到后不需要猜我是在描述视觉黑屏、按钮不可用还是某个异步调用失败。同时我会保留错误发生前最后一次状态不只抄异常消息。异常消息可能随运行环境变化阶段和设备字段却让它有上下文。比如同样是启动失败已经查到某个后摄和尺寸与设备列表为空排查顺序完全不同。页面已有lastEvent、lastUpdatedAt和errorMessage正好可以把这层上下文留在同一轮记录里。它们帮助解释页面行为但不把实际预览画面的质量或是否成像替页面作保证。最后还要区分“停止”与“失败”。用户主动停止时代码会尝试停止会话、释放预览输出并关闭输入随后把阶段写成 released异常路径则保留 failed 与错误。两者都可能让右侧看起来没有画面却代表不同的后续动作。前者可以重新启动验证资源是否释放干净后者应先保存错误与前序状态。把这点写清楚后页面结束预览不再一律被叫作黑屏故障。交接给同事时我会要求先复述当前阶段再操作。因为相机页面里的同一个启动按钮在等待 Surface、授权中、查询设备、运行中和已释放这些阶段背后含义不同。先读状态再点击可以避免在运行中重复启动也避免把释放后的正常空白误报为异常。这个习惯很小却能让每次问题描述从一开始就落在正确分支上。若现场无法继续测试我至少会留下设备类型、页面进入时间、最后阶段和错误原文。没有这些信息的“预览起不来”后续只能从头重复每个分支有了它们即使没有真实画面材料也能先判断本轮流程在哪一步结束决定下一次应补生命周期、权限、设备能力还是会话错误的观察。回归完成后我还会从页面退出再重新进入一次。这个动作不评价相机画面只确认aboutToDisappear的释放路径没有让下一次 Surface、设备查询或会话创建停在旧对象上。若第二次的状态从等待 Surface 开始并重新按顺序变化就说明页面自身的生命周期记录是连贯的若出现异常则应将两次的时间和错误并列而不是只保留最后一条。权限前Surface 是否已经 ready授权后设备和previewProfile是否真的存在会话失败时页面是否保留阶段和错误并释放已创建资源相机预览不该被写成一扇只需要权限钥匙的门。它更像一条走廊门禁只是第一道门后面还有设备、输出、会话和资源回收。把这些门逐一道出黑屏就不再是一个让人抓不住的结果而是一条可以走回去的路径。本文只依据现有页面的权限、设备、输出能力和VideoSession状态撰写。相机实际画面、成像质量以及不同设备的表现仍需在对应 API 24 真机环境中单独记录。