【听见课堂 HarmonyOS NEXT 实战系列 22】麦克风权限应在什么时候申请:HarmonyOS 按需授权与恢复路径

实时字幕离不开麦克风,但“应用一启动就弹授权框”并不是最简单的做法,而是把用户尚未理解的隐私决定提前了。更麻烦的是,用户首次拒绝后,应用如果继续机械调用同一个授权接口,系统可能不再弹窗;页面若没有恢复路径,就会变成一个永远不可用的“开始”按钮。

听见课堂把权限申请绑定到用户点击“开始听写”的时刻,并把首次申请、已拒绝、系统设置恢复和已有字幕保留做成完整状态流。本文结合 module.json5LiveTranscriptionService.start()recoverMicrophonePermission(),拆解 HarmonyOS 麦克风权限从声明到运行时恢复的工程边界。

HarmonyOS 麦克风按需授权与拒绝恢复路径

一、Manifest 声明和运行时授权是两道门

项目首先在 entry/src/main/module.json5 声明权限:

{
  "name": "ohos.permission.MICROPHONE",
  "reason": "$string:microphone_permission_reason",
  "usedScene": {
    "abilities": ["EntryAbility"],
    "when": "inuse"
  }
}

这一步告诉系统应用为什么、在哪个 Ability、什么使用阶段需要麦克风,但不会替用户完成授权。运行到实际采集前,还要检查当前状态并通过系统接口请求用户决定。

缺少清单声明,运行时请求没有合法前提;只有清单声明,没有运行时授权,敏感能力仍不能直接使用。

二、为什么选择“点击开始”后再申请

听见课堂在首页和复盘页都不需要麦克风。用户进入实时课堂,也可能只是回看已有字幕。只有明确点击“开始”才表示准备采集课堂声音。

按需申请有三个好处:

  1. 系统弹窗紧邻用户动作,目的更容易理解;
  2. 只看字幕的用户不会被无关权限打扰;
  3. 拒绝后仍可浏览已有内容,页面不必整体封锁。

权限请求应该由真实功能动作触发,而不是用 App 启动、页面加载或生命周期回调“抢跑”。

三、先查当前状态,再决定是否弹窗

项目把权限检查封装在 Service:

private async requestMicrophonePermission(
  context: common.UIAbilityContext
): Promise<boolean> {
  const manager: abilityAccessCtrl.AtManager =
    abilityAccessCtrl.createAtManager();
  const status = manager.getSelfPermissionStatus(MICROPHONE_PERMISSION);

  if (status === abilityAccessCtrl.PermissionStatus.GRANTED) {
    return true;
  }
  if (status === abilityAccessCtrl.PermissionStatus.DENIED) {
    return false;
  }

  const result = await manager.requestPermissionsFromUser(
    context,
    [MICROPHONE_PERMISSION]
  );
  return result.authResults.length > 0 && result.authResults[0] === 0;
}

已授权时直接继续,已拒绝时不反复弹窗,尚未询问时才进入首次系统授权。这比“每次开始都请求”更符合系统行为,也让状态机能准确区分首次请求与拒绝恢复。

四、权限请求必须发生在创建采集器之前

start() 的顺序是:

进入 starting
  -> 检查/请求 MICROPHONE
  -> 权限通过
  -> 创建识别引擎
  -> 创建 AudioCapturer
  -> startListening
  -> capturer.start
  -> listening

如果先创建 AudioCapturer 再请求权限,失败路径会多出半初始化资源、异常日志和释放分支。先完成权限门禁,可以保证拒绝态没有活跃采集器,也不会误导用户认为应用已经开始录音。

五、权限理由要和真实数据流一致

项目的页面和状态文案反复说明“原始音频不会保存”。这不是营销文案,而是代码边界:采集到的 PCM 帧只通过 writeAudio() 送入识别引擎,实时 Service 中没有 saveAudiofileIo 或录音文件写入路径。

权限说明至少应回答:

  • 为什么需要麦克风:生成实时课堂字幕;
  • 什么时候使用:用户主动开始听写且应用在用;
  • 数据如何处理:音频流用于识别,不落盘保存;
  • 不授权会怎样:已有字幕与其他本机功能仍可使用。

声明、页面文案和真实实现必须一致。如果未来新增录音回放,权限说明和隐私政策也必须重新评估。

六、首次拒绝后为什么不能只再调一次同样接口

华为开发者联盟当前权限 FAQ 明确提醒:用户拒绝 requestPermissionsFromUser() 后,后续调用可能不再弹出授权框。继续盲目调用会形成“按钮有反馈但系统什么都没发生”的假恢复。

因此项目在检测到 DENIED 时进入独立的 permission_denied 状态,主按钮改为“授权”,页面展示“前往系统设置”。用户明确触发后,才调用 requestPermissionOnSetting()

七、requestPermissionOnSetting 的恢复结果也要复查

系统设置返回值不是唯一判断依据。用户可能中途返回、系统调用可能抛错,权限状态也可能已经被其他路径改变。项目同时检查返回数组和实时权限状态:

const results = await manager.requestPermissionOnSetting(
  context,
  [MICROPHONE_PERMISSION]
);
const granted = results.length > 0 &&
  results[0] === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;

if (granted || manager.getSelfPermissionStatus(MICROPHONE_PERMISSION) ===
  abilityAccessCtrl.PermissionStatus.GRANTED) {
  // 回到 idle,等待用户再次点击开始
}

恢复成功后回到 idle,而不是自动开始采集。授权和开始是两个不同的用户决定,系统设置返回不应成为偷偷开启麦克风的捷径。

八、拒绝后已有字幕为什么必须保留

麦克风权限控制的是“继续获取新音频”,不是“是否有资格查看本机已有字幕”。项目在拒绝路径只改变状态和提示,不清空 captions

this.state = LiveSessionState.PERMISSION_DENIED;
this.statusMessage =
  '麦克风权限未授权。已有字幕仍可查看,你可以再次点击开始重试授权。';

对于听障课堂助手,这一点尤其重要。用户可能临时拒绝、误触或在系统设置里关闭权限,但此前的课堂信息仍有无障碍价值。把权限失败扩大成内容不可见,既没有技术必要,也会制造产品死路。

清单声明、首次授权、拒绝态和系统设置恢复的分层关系

九、页面层只负责拿 Context 和展示恢复入口

ArkUI 页面通过 getHostContext() 取得 UIAbilityContext,再交给 Service。权限状态判断、首次请求和系统设置恢复都不散落到按钮组件中。

页面只做三件事:

  • 根据 permission_denied 显示明确说明;
  • 提供 48vp 的“前往系统设置”可点击入口;
  • 把 Service 返回的 LiveSessionSnapshot 应用到 UI。

这样手机页和 2in1 大屏页可以复用同一套权限规则,不会各自实现一份容易漂移的分支。

十、异常路径不能伪装成“已拒绝”

权限接口抛错可能来自上下文失效、系统服务异常或设备状态,不一定是用户拒绝。当前 requestMicrophonePermission() 为了保守安全统一返回 false,UI 表现为未授权;recoverMicrophonePermission() 则在 catch 中再次读取真实状态,并区分“已开启”和“暂时无法打开设置”。

更细粒度的生产实现还可以把 user_deniedcontext_unavailablesystem_error 分成内部原因码,但对外文案应保持可理解,日志不得输出用户字幕或其他敏感内容。

十一、权限被系统中途撤销怎么办

用户可能在会话外部修改权限,系统也可能因策略变化导致采集失败。项目当前通过采集/识别错误进入 error,释放资源并保留字幕;下一次“继续”会再次检查权限。

如果目标 SDK 支持权限状态变化监听,可以进一步在前台恢复时刷新标签,但仍要避免每次页面显示就重复请求。监听只负责发现变化,真正的敏感授权仍由用户动作触发。

十二、权限测试矩阵应该覆盖什么

场景 预期结果
首次进入实时课堂但不点开始 不弹权限框,不创建采集器
首次点击开始并允许 进入启动链路,随后监听或能力降级
首次点击开始并拒绝 进入 permission_denied,已有字幕保留
拒绝后再次点击 显示系统设置恢复路径,不重复骚扰式弹窗
设置页允许后返回 状态回到 idle,等待用户再次点击开始
设置页仍拒绝 保持拒绝态,内容与非麦克风操作可用
设置调用抛错 给出可恢复提示,不崩溃、不清空字幕
会话中途撤权/采集失败 释放资源,进入错误或拒绝恢复路径
强停重启 不自动开始采集,权限标签按系统真实状态刷新

测试时要同时检查系统弹窗、UI 树、采集器状态和崩溃日志。看到按钮文案改变,不代表权限或底层资源已经满足后置条件。

十三、项目当前验证边界

历史验收在 HarmonyOS 6.0.2(22) 模拟器上真实看到过麦克风授权弹窗、拒绝态、系统设置恢复与监听生命周期;实时听写合约脚本也检查了清单声明、requestPermissionsFromUser()requestPermissionOnSetting()

但华为当前语音识别指南注明 Core Speech 识别能力不支持模拟器,因此模拟器结果不能证明真实音频识别成功。物理真机上的授权弹窗差异、课堂环境持续采集、后台切换、系统来电/音频中断和发布审核所需的隐私说明仍需单独验证。

十四、总结

麦克风权限的正确流程不是“声明一下,再弹个框”,而是把用途说明、用户动作、首次授权、拒绝恢复、内容保留和资源门禁连成一条可测试路径。

听见课堂选择在用户点击开始后申请,把拒绝放进显式状态,用 requestPermissionOnSetting() 提供恢复,并坚持“授权成功后仍等待用户再次开始”。这样的按需授权既减少打扰,也让无障碍功能在权限失败时仍保持可用。

下一篇将继续向底层展开:Core Speech Kit、AudioCapturer、sessionId 和识别回调怎样组成一条不保存原始音频的实时转写链路。

参考:HarmonyOS 权限拒绝后再次授权 FAQCore Speech Kit 语音识别指南

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐