阶段:阶段四 · 鸿蒙端深度适配(专栏核心) | 篇号:12 / 25 |
开篇
权限是鸿蒙开发踩坑率最高的环节之一——明明代码写对了,运行就是报 Permission denied,新手往往怀疑人生。
本篇把鸿蒙权限模型彻底讲清楚,并给出一份**「声明 + 申请 + 封装」三步可复用代码**,让权限问题在你这里终结。
本篇你将收获
- ✅ 区分鸿蒙的两类权限(system_grant / user_grant)
- ✅ 在 module.json5 中正确声明权限
- ✅ 用 ArkTS 运行时动态申请,并通过 Channel 暴露给 Dart
- ✅ 封装一个跨端通用的 Permission Service
一、鸿蒙权限模型
1.1 两类授权方式
|
类型 |
含义 |
例子 |
|
system_grant |
安装时自动授予,无需用户弹窗 |
网络、振动、网络状态 |
|
user_grant |
运行时弹窗询问,用户必须手动同意 |
相机、位置、麦克风、存储、联系人 |
核心认知:user_grant 权限光在 module.json5 声明不够,必须在运行时调
requestPermissionsFromUser() 才能真正拿到。
1.2 鸿蒙常用权限速查表
|
权限名 |
说明 |
类型 |
|
ohos.permission.INTERNET |
网络 |
system_grant |
|
ohos.permission.VIBRATE |
振动 |
system_grant |
|
ohos.permission.GET_NETWORK_INFO |
网络状态 |
system_grant |
|
ohos.permission.CAMERA |
相机 |
user_grant |
|
ohos.permission.MICROPHONE |
麦克风 |
user_grant |
|
ohos.permission.LOCATION |
位置 |
user_grant |
|
ohos.permission.READ_IMAGEVIDEO |
读相册 |
user_grant |
|
ohos.permission.WRITE_IMAGEVIDEO |
写相册 |
user_grant |
|
ohos.permission.APP_TRACKING_CONSENT |
广告追踪 |
user_grant |
完整列表见官方文档:
https://developer.huawei.com/consumer/cn/doc/harmonyos-references/permission-list
二、第一步:在 module.json5 声明
ohos/entry/src/main/module.json5 增加 requestPermissions 字段(与 abilities 同级):
{
"module": {
"name": "entry",
"type": "entry",
// ...
"abilities": [ /* ... */ ],
"requestPermissions": [
{
"name": "ohos.permission.INTERNET",
"reason": "$string:permission_internet_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "always"
}
},
{
"name": "ohos.permission.CAMERA",
"reason": "$string:permission_camera_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.LOCATION",
"reason": "$string:permission_location_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
}
字段说明:
|
字段 |
必填 |
说明 |
|
name |
✅ |
权限名(官方常量) |
|
reason |
✅ |
申请理由(字符串资源,会在弹窗显示) |
|
usedScene.when |
✅ |
always(始终) / inuse(使用时) |
2.1 在 string.json 添加理由
ohos/entry/src/main/resources/base/element/string.json:
{
"string": [
{ "name": "permission_internet_reason", "value": "用于加载网络内容" },
{ "name": "permission_camera_reason", "value": "用于扫码和拍照" },
{ "name": "permission_location_reason", "value": "用于显示您附近的商家" }
]
}
⚠️ user_grant 权限必须填 reason,否则上架会被拒。
三、第二步:运行时动态申请(ArkTS)
system_grant 装上就有了,但 user_grant 必须运行时弹窗申请。
3.1 申请逻辑
修改 EntryAbility.ets,新增权限 Channel:
import abilityAccessCtrl, {
Permissions,
PermissionRequestResult,
} from '@ohos.abilityAccessCtrl';
const CHANNEL_NAME = 'com.example.app/permission';
class PermissionMethodHandler {
async onMethodCall(call: MethodCall, result: MethodCallResult) {
switch (call.method) {
case 'checkPermission':
const granted = await this.checkPermission(call.args as string);
result.success(granted);
break;
case 'requestPermission':
const ok = await this.requestPermission(call.args as string);
result.success(ok);
break;
default:
result.notImplemented();
}
}
/** 检查是否已授权 */
private async checkPermission(permission: string): Promise<boolean> {
const atm = abilityAccessCtrl.createAtManager();
const tokenId = await this.getTokenId();
const status = await atm.checkAccessToken(tokenId, permission);
return status === 0; // 0 = 已授权
}
/** 弹窗申请 */
private async requestPermission(permission: string): Promise<boolean> {
const atm = abilityAccessCtrl.createAtManager();
const context = getContext(this); // ability context
const res: PermissionRequestResult =
await atm.requestPermissionsFromUser(context, [permission]);
return res.authResults[0] === 0;
}
private async getTokenId(): Promise<number> {
const bundleInfo =
await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
return bundleInfo.appInfo.accessTokenId;
}
}
注册到 delegate:
onCreate(want, launchParam) {
this.delegate = new FlutterAbilityDelegateNew(this);
this.delegate.onCreate(want, launchParam);
this.delegate.addMethodChannel(CHANNEL_NAME, new PermissionMethodHandler());
}
四、第三步:Dart 端封装
lib/services/harmony_permission_service.dart:
import 'dart:io';
import 'package:flutter/services.dart';
/// 鸿蒙权限常量(与 module.json5 对应)
class HarmonyPermissions {
static const internet = 'ohos.permission.INTERNET';
static const camera = 'ohos.permission.CAMERA';
static const microphone = 'ohos.permission.MICROPHONE';
static const location = 'ohos.permission.LOCATION';
static const readImageVideo = 'ohos.permission.READ_IMAGEVIDEO';
static const writeImageVideo = 'ohos.permission.WRITE_IMAGEVIDEO';
}
class HarmonyPermissionService {
static const _channel = MethodChannel('com.example.app/permission');
static bool get isHarmonyOS => Platform.operatingSystem == 'ohos';
/// 检查是否已授权
static Future<bool> check(String permission) async {
if (!isHarmonyOS) return true;
return await _channel.invokeMethod<bool>('checkPermission', permission) ?? false;
}
/// 申请权限(弹窗)
static Future<bool> request(String permission) async {
if (!isHarmonyOS) return true;
return await _channel.invokeMethod<bool>('requestPermission', permission) ?? false;
}
/// 一次性申请多个
static Future<Map<String, bool>> requestAll(List<String> permissions) async {
final result = <String, bool>{};
for (final p in permissions) {
result[p] = await request(p);
}
return result;
}
}
4.1 在业务里使用
Future<void> takePhoto() async {
// 1. 先检查
final granted = await HarmonyPermissionService.check(HarmonyPermissions.camera);
if (!granted) {
// 2. 未授权则申请
final ok = await HarmonyPermissionService.request(HarmonyPermissions.camera);
if (!ok) {
// 3. 用户拒绝,引导设置页
_showPermissionDeniedDialog('相机');
return;
}
}
// 4. 已授权,执行业务
await _openCamera();
}
五、跨端抽象(推荐架构)
用 permission_handler 生态思路,把权限封装为统一接口:
abstract class PermissionService {
Future<bool> requestCamera();
Future<bool> requestLocation();
Future<bool> requestStorage();
}
class HarmonyPermissionServiceImpl implements PermissionService {
Future<bool> requestCamera() =>
HarmonyPermissionService.request(HarmonyPermissions.camera);
Future<bool> requestLocation() =>
HarmonyPermissionService.request(HarmonyPermissions.location);
Future<bool> requestStorage() =>
HarmonyPermissionService.request(HarmonyPermissions.readImageVideo);
}
// Android/iOS 用 permission_handler 包实现另一份
业务代码只依赖 PermissionService,完全不感知平台。
六、最佳实践
6.1 时机:场景化申请
❌ 不要应用启动就一口气申请 5 个权限(用户会害怕)。
✅ 正确做法:用到才申请——点扫码才申请相机,点定位才申请位置。
6.2 拒绝后的引导
if (await HarmonyPermissionService.request(...) == false) {
// 用户点了「拒绝」,引导到设置页
showAlertDialog(
title: '需要相机权限',
content: '请在设置中开启相机权限以使用扫码功能',
actions: [
TextButton(
child: Text('去设置'),
onPressed: () => _openAppSettings(), // 调原生跳设置
),
],
);
}
鸿蒙跳系统设置页(需 Channel 调用):
case 'openSettings':
wantConstant.startAbility({
bundleName: 'com.huawei.hmos.settings',
abilityName: 'MainAbility',
});
result.success(true);
break;
6.3 「永久拒绝」处理
用户勾选「不再询问」后,
requestPermissionsFromUser 直接返回失败。此时只能引导到设置页手动开。
七、常见问题
Q1:报Permission denied,但module.json5已经声明
user_grant 权限光声明不够,必须运行时调用 request。
Q2:弹窗不出现
检查 usedScene.when 字段。inuse 表明「使用时弹」,always 表明「始终弹」。
Q3:上架被拒「权限理由不清晰」
reason 字段不要写「for feature」,要写具体业务用途:
- ❌ 用于应用功能
- ✅ 用于扫码登录和实名认证
Q4:多个权限同时申请
鸿蒙支持数组,但不提议一次申请超过 2 个,用户会直接拒绝全部。
Q5:申请权限时应用闪退
getContext(this) 拿不到 ability context。确保 handler 在 EntryAbility.onCreate 后注册,且持有 ability 引用。
Q6:鸿蒙的权限和 Android 一样吗
不一样。例如 Android 的 READ_EXTERNAL_STORAGE 在鸿蒙对应 READ_IMAGEVIDEO。不要直接套 Android 权限名。
Q7:DevEco 模拟器上权限弹窗不出现
部分鸿蒙 API 在模拟器上不可用,真机测试更准。
八、小结
|
步骤 |
文件 / 操作 |
|
1. 声明 |
module.json5 加 requestPermissions |
|
2. 理由 |
string.json 加对应 reason 文案 |
|
3. 申请 |
ArkTS abilityAccessCtrl.requestPermissionsFromUser() |
|
4. 封装 |
Dart HarmonyPermissionService + 跨端抽象 |
|
5. 引导 |
拒绝时跳设置页 |
权限搞定后,下一篇我们谈调试技巧——hdc 命令、HiLog 日志、DevEco 联调,让你在鸿蒙端的调试效率追平 Android。
下篇预告
→ 13 · 鸿蒙端调试技巧 ⭐





