12 · 鸿蒙权限申请与运行时授权 ⭐

阶段:阶段四 · 鸿蒙端深度适配(专栏核心) | 篇号:12 / 25 |


开篇

权限是鸿蒙开发踩坑率最高的环节之一——明明代码写对了,运行就是报 Permission denied,新手往往怀疑人生。

本篇把鸿蒙权限模型彻底讲清楚,并给出一份**「声明 + 申请 + 封装」三步可复用代码**,让权限问题在你这里终结。

本篇你将收获

  1. ✅ 区分鸿蒙的两类权限(system_grant / user_grant)
  2. ✅ 在 module.json5 中正确声明权限
  3. ✅ 用 ArkTS 运行时动态申请,并通过 Channel 暴露给 Dart
  4. ✅ 封装一个跨端通用的 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 · 鸿蒙端调试技巧 ⭐


© 版权声明

相关文章

1 条评论

none
暂无评论...