feat:鸿蒙元服务接入麦克风运行时权限申请

MICROPHONE 是 user_grant 权限, 上一提交补了 module.json5 的 reason 只解决
了编译校验, 运行时仍需弹窗申请, 否则录音功能不可用。

在 EntryAbility.onCreate 中调用 abilityAccessCtrl.requestPermissionsFromUser。

关键点:
- AscfUIAbility 父类已定义 onCreate, 重写时必须调 super.onCreate(want,
  launchParam), 否则跳过 ascf runtime 初始化导致元服务白屏
- 选 onCreate 而非按需触发: 权限状态在 JS 侧 (uni.getRecorderManager) 读不到,
  项目无现成 JS<->ArkTS 通道; 且 requestPermissionsFromUser 对已授权应用
  直接 resolve 不弹窗, 实际只有首次启动打扰用户一次
- 降级: 未授权不影响浏览/下单/卡片, 仅语音不可用, 前端 voice-panel 会
  showToast 提示

readme 4.3.3 补充该实现与踩坑说明, 4.3.4 移除已解决项。
This commit is contained in:
2026-10-17 11:46:57 +08:00
parent a9f3c7acc2
commit 9086f1665c
2 changed files with 60 additions and 6 deletions

View File

@@ -183,9 +183,26 @@ Error Message: The reason and usedScene attributes are mandatory for user_grant
📌 `reason` 文案必须是**用户可读的中文说明**,不能为空或写 `xxx description` 之类的占位文本,否则上架审核会被驳回。
📌 **改完 `reason` 还不够** —— `user_grant` 权限编译通过后,仍需在运行时弹窗申请,否则录音等功能不可用。本项目已在 [EntryAbility.ets](stub-app-projects/mp-harmony/entry/src/main/ets/entryability/EntryAbility.ets) 的 `onCreate` 中接入:
```ts
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 必须先调父类: ascf runtime 的初始化在 AscfUIAbility.onCreate 里, 漏掉会导致元服务白屏
super.onCreate(want, launchParam);
this.requestMicrophonePermission();
}
```
> ⚠️ **坑**:`AscfUIAbility` 父类**已定义** `onCreate`,重写时必须调 `super.onCreate(want, launchParam)`,否则 ascf runtime 初始化被跳过,元服务白屏。
📌 为什么在 `onCreate` 申请而不是"用户点录音时才申请":
- 权限状态在 JS 侧(`uni.getRecorderManager`)读不到,项目里也没有现成的 JS ↔ ArkTS 通道,做不了按需触发
- `requestPermissionsFromUser` 对已授权的应用直接 resolve 且**不弹窗**,所以实际只有首次启动打扰用户一次
📌 用户拒绝后的降级:不影响浏览/下单/卡片等其他功能,仅语音输入不可用;[ai-chat-voice-panel.vue](pages_tool/ai-chat/components/ai-chat-voice-panel.vue) 会 catch 到 recorder 错误并 `showToast` 提示用户。
#### 4.3.4 【待完善】⚠️
- **运行时权限申请未接入**:entry 模块中暂未找到 `abilityAccessCtrl` / `requestPermissionsFromUser` 调用。`user_grant` 权限编译通过后仍需在运行时弹窗申请,否则录音等功能会静默失败。
- **release 签名仍指向 debug 证书**:`build-profile.json5` 中 `release` 签名的 `profile` 仍是 `./cert-profile/qq/qqDebug.p7b`。本地调试无碍,但提交 AppGallery 前必须换成**发布证书 + release 类型 provisioning profile**,否则 AGC 校验会直接拒绝。
- **release 签名仍指向 debug 证书**:`build-profile.json5` 中 `release` 签名的 `profile` 仍是 `./cert-profile/qq/qqDebug.p7b`,与 `default`(debug)共用同一份证书。本地调试无碍,但提交 AppGallery 前必须换成**发布证书 + release 类型 provisioning profile**,否则 AGC 校验会直接拒绝。
#### 4.3.5 【交付与最终发布】🔍
1. 将生成的 `.zip` 文件发送给运维人员

View File

@@ -1,4 +1,41 @@
import { AscfUIAbility } from '@atomicservice/ascfapi';
export default class EntryAbility extends AscfUIAbility {
}
import { abilityAccessCtrl, AbilityConstant, Permissions, Want } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { AscfUIAbility } from '@atomicservice/ascfapi';
export default class EntryAbility extends AscfUIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 必须先调父类: ascf runtime 的初始化在 AscfUIAbility.onCreate 里, 漏掉会导致元服务白屏
super.onCreate(want, launchParam);
this.requestMicrophonePermission();
}
/**
* 申请麦克风权限 (ohos.permission.MICROPHONE, user_grant 类型)。
*
* 为什么放在 onCreate 而不是"用户点录音时才申请":
* 1. 权限状态在 JS 侧 (uni.getRecorderManager) 读不到, 项目里也没有现成的
* JS <-> ArkTS 通道, 做不了按需触发的条件判断;
* 2. requestPermissionsFromUser 对已授权的应用直接 resolve 且不弹窗,
* 所以实际只有首次启动会打扰用户一次, 之后每次冷启动都是无感的。
*
* 未授权时的降级行为:
* - 不影响其他功能 (浏览/下单/卡片), 只是语音输入不可用;
* - 前端 ai-chat-voice-panel.vue 会 catch 到 recorder 错误并 showToast 提示用户。
*/
private async requestMicrophonePermission(): Promise<void> {
try {
const atManager = abilityAccessCtrl.createAtManager();
const result = await atManager.requestPermissionsFromUser(this.context, [Permissions.MICROPHONE]);
const authResult = result.authResults[0];
if (authResult === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
console.info('[EntryAbility] MICROPHONE 已授权, 语音输入可用');
} else {
console.warn('[EntryAbility] MICROPHONE 未授权, 语音输入不可用 (不影响其他功能)');
}
} catch (err) {
// 用户取消授权弹窗 / context 异常, 同样降级为"语音不可用", 不阻断启动
const e = err as BusinessError;
console.warn(`[EntryAbility] 申请麦克风权限异常: code=${e.code}, message=${e.message}`);
}
}
}