鸿蒙权限管理不踩坑指南:做个“懂分寸”的合规好应用

在鸿蒙(HarmonyOS)的世界里,权限管理就像应用的“社交礼仪”——懂分寸、不越界,才能赢得用户好感和系统“青睐”;要是乱要权限、硬闯隐私,轻则被用户无情卸载,重则过不了应用市场审核。今天就用接地气的吐槽+干货,把鸿蒙权限使用的核心玩法说清楚,让你开发路上少踩雷!

一、鸿蒙权限使用基本原则:做个“不贪心、不霸道”的好应用

鸿蒙对权限的要求,本质就是让应用“守规矩”,这几个原则记牢,能少走99%的弯路:

  1. 最小权限原则:别当“伸手党”
    应用需要啥权限就拿啥,多余的一概不碰!比如一个看小说的APP,非要申请相机、麦克风权限,这不是“没事找事”吗?用户看到直接黑人问号脸,卸载按钮都要按出火星子。
  2. 必要性原则:按需“点菜”,别一次性“包场”
    权限要在用户用到对应功能时再申请,别一打开APP就弹窗“轰炸”:“要位置!要存储!要通讯录!” 这跟刚见面就问人要银行卡密码一样离谱,用户不拒绝你才怪。
  3. 透明化原则:坦诚相待,别“玩套路”
    申请权限前,得跟用户说清楚“要这玩意儿干啥”。比如“要存储权限是为了保存你下载的小说”,别只说“需要存储权限”,用户哪知道你是不是要偷偷存人家照片?坦诚才是必杀技!
  4. 尊重用户选择原则:拒绝就体面点,别死缠烂打
    用户拒绝权限后,别反复弹窗“骚扰”,更不能搞“不授权就用不了核心功能”的霸道操作。人家不想给相机权限,你还不让人看小说了?格局打开,给个替代方案不香吗?
  5. 合规性原则:别“走歪路”,系统爸爸会“打屁股”
    别想着绕过系统授权、隐藏权限用途这些“骚操作”,鸿蒙的审核机制可不是吃素的,违规的话,应用上架直接“原地凉凉”,前期开发全白费。

二、权限分类:鸿蒙的“权限等级表”,别认错“大佬”和“路人”

鸿蒙把权限分了三六九等,就像职场里的“普通员工”“核心骨干”“大老板”,待遇和申请难度完全不一样,千万别搞混了:

(一)按权限等级分类

  1. system_grant(系统授权):“路人甲”级别的小透明
    不涉及隐私、不影响系统安全,比如网络访问、查看蓝牙的配置这些基础操作。系统会自动“放行”,不用麻烦用户手动授权,只要在配置文件里打个“报告”就行,省心又省力。
  2. user_grant(用户授权):“高危操作选手”,需用户点头
    涉及用户隐私(位置、相册)或核心功能(相机、麦克风)的权限,都是“高危分子”。想使用这些权限,必须让用户明确“点头”同意,系统还会特意弹窗提醒,相当于给用户一个“反悔的机会”。
  3. manual_settings(手动设置授权):“大佬级”权限,申请门槛拉满
    比如拦截键盘输入、无需弹窗录制屏幕、无需弹窗访问用户公共路径这些“核心操作”,属于权限里的“天花板”。想拿到它们可不容易,应用没法直接申请,得引导用户手动去系统设置里开启,相当于要去“大佬办公室”亲自报备。

另外,在system_grant和user_grant类型权限中,还藏着一些特殊的 “受限开放权限”,比如悬浮窗、读取联系人等等。这些权限可不是声明就能用的,必须提前单独申请,否则应用上架时直接会被审核老师打回,连辩解的机会都没有。
受限开放权限申请步骤(直接抄官方流程不踩坑)

(二)按功能权限组分类:权限也爱“抱团取暖”

鸿蒙特别贴心地把功能相关的权限分成了 “小组”,既方便开发者管理,也方便用户理解。这里有个小知识点要划重点:应用请求权限时,同一权限组内的权限会在一个弹窗内统一请求用户授权,用户一旦同意,整个权限组内的权限就会被批量授予。不过有例外 —— 位置信息、通讯录、日历这三个权限组,不遵循这个 “抱团授权” 规则,得单独留意。

以位置信息权限组和相机权限组举个例子帮你快速理解,一看就懂:。

当应用只申请权限ohos.permission.APPROXIMATELY_LOCATION(属于位置信息权限组)时,用户将收到一个请求位置信息的弹窗,包含单个权限的申请。
当应用同时申请权限ohos.permission.APPROXIMATELY_LOCATION和ohos.permission.LOCATION(均属于位置信息权限组)时,用户将收到一个请求位置信息的弹窗,包含两个权限的申请。
当应用同时申请权限ohos.permission.APPROXIMATELY_LOCATION(属于位置信息权限组)和ohos.permission.CAMERA(属于相机权限组)时,用户将收到请求位置信息、请求使用相机的两个弹窗。
权限组完整使用说明(官方清单,按需查阅)

三、权限申请方法:三步搞定,不做“尴尬申请者”

鸿蒙权限申请讲究“先报备、再申请、看结果”,不同权限有不同玩法,一步步来准没错:

(一)静态声明:先给系统“打报告”,不报备可不行

所有权限都得先在项目的 module.json5 文件里“登记备案”,相当于告诉系统“我要用这些权限啦”,这是基础操作,少了这步直接翻车。

  1. 找到 module.json5 文件,在 requestPermissions 节点里添加权限信息;
  2. 正确示范(别瞎写,权限名称要跟官方一致):
"requestPermissions": [
  {
    "name": "ohos.permission.CAMERA", // 相机权限,官方名称不能改
    "reason": "$string:camera_permission_reason", // 用途说明,比如“拍照上传头像”
    "usedScene": {
      "abilities": ["MainAbility"], // 哪个功能要用
      "when": "inuse" // 只有用的时候才申请,别一直要
    }
  }
]
  1. 避坑提醒:用途说明别写“需要权限”这种废话,要写清楚“用权限干嘛”,不然审核老师会给你打回重写!

(二)动态申请:看准时机“表白”,别盲目冲锋

危险权限光报备不够,还得在用户用对应功能时“趁热打铁”申请,流程就像“表白”——先探探口风,再正式出击:

  1. 先查状态:别做“舔狗”
    PermissionManager 查一查权限有没有被授权,已经授权了就直接用,别反复申请,不然用户会烦:“都给你了还问!”
  2. 再发起申请:真诚最重要
    没授权就调用 requestPermissionsFromUser 申请,系统会弹出弹窗,把用途告诉用户,让用户心甘情愿点头。
  3. 处理结果:成了就用,不成别纠缠
    用户同意了就开开心心用功能;拒绝了就好好说:“要开启相机权限才能拍照哦,可去系统设置里打开~” 别逼用户,不然会被反感。
  4. 代码示例(核心思路,看懂不踩坑):
import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

const permissions: Permissions[] = ['ohos.permission.LOCATION', 'ohos.permission.APPROXIMATELY_LOCATION'];

function reqPermissionsFromUser(permissions: Array<Permissions>, context: common.UIAbilityContext): void {
  let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
  // requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
  atManager.requestPermissionsFromUser(context, permissions).then((data) => {
    let grantStatus: number[] = data.authResults;
    let length: number = grantStatus.length;
    for (let i = 0; i < length; i++) {
      if (grantStatus[i] === 0) {
        // 用户授权,可以继续访问目标操作
        console.info(`${permissions[i]} is granted by user.`);
      } else {
        // 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
        return;
      }
    }
    // 授权成功
  }).catch((err: BusinessError) => {
    console.error(`Failed to request permissions from user, code: ${err.code}, message: ${err.message}`);
  })
}

@Entry
@Component
struct Index {
  aboutToAppear() {
    const context: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext;
    reqPermissionsFromUser(permissions, context);
  }

  build() {
    // ...
  }
}

官方申请权限开发步骤

(三)用户拒绝授权了怎么办?别慌,有备选方案

这里有个关键知识点:当应用通过requestPermissionsFromUser()拉起弹窗请求用户授权时,如果用户明确拒绝,后续应用就无法再通过这个方法拉起同款弹窗了,只能引导用户去系统设置里手动授权。

在“设置”应用中的路径如下:

路径一:设置 > 隐私与安全 > 权限类型(如位置信息) > 具体应用
路径二:设置 > 应用和元服务 > 某个应用

当然,你也可以更贴心一点,通过调用 requestPermissionOnSetting(),直接拉起权限设置弹窗,帮用户省去手动找路径的麻烦,好感度直接拉满:

import { abilityAccessCtrl, Context, common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

// ···
   let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
   let context: Context = this.getUIContext().getHostContext() as common.UIAbilityContext;
   atManager.requestPermissionOnSetting(context, ['ohos.permission.APPROXIMATELY_LOCATION']).then((data: Array<abilityAccessCtrl.GrantStatus>) => {
     console.info(`requestPermissionOnSetting success, result: ${data}`);
   }).catch((err: BusinessError) => {
     console.error(`requestPermissionOnSetting fail, code: ${err.code}, message: ${err.message}`);
   });

授权最佳实践(直接 ctrl c/ctrl v 就能用,抄作业就完了)

下面给大家整理了一套权限管理工具类,核心逻辑已经帮你梳理清晰,无需理解太深,直接复制到项目里就能用,堪称 “懒人福音”。
工具类核心逻辑说明

  1. 状态检查阶段:先筛选,再申请,不做无用功
    ・通过应用tokenId获取真实身份标识,确保权限检查的准确性;
    ・采用异步方式批量校验每个权限的当前状态,效率更高;
    ・动态构建待申请权限列表,只处理未授权权限,避免重复弹窗打扰用户。
  2. 分级申请策略:首次、二次分开处理,更懂用户心理
    ・首次申请:直接弹窗请求所有未授权权限,一步到位,不折腾用户;
    ・二次申请:当检测到dialogShownResults存在false值时(说明用户已拒绝过一次)→
    自动转入设置页引导模式,不反复弹窗惹人烦;→ 逐个发起权限申请(因系统限制,二次申请不同权限组无法批量操作),提高授权成功率。

具体调用方式(注意:权限需要先在module.json5中声明)

checkAndRequestPermissions(['ohos.permission.MICROPHONE', 'ohos.permission.CAMERA'], "hello world")

权限管理工具类(完整可复制)

import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
import bundleManager from '@ohos.bundle.bundleManager';
import common from '@ohos.app.ability.common';
import promptAction from '@ohos.promptAction';
import { BusinessError } from '@ohos.base';


/**
 * 单个权限授权状态检查
 * @param permission 待检查的单个权限
 * @returns 权限授权状态
 */
async function checkPermissionGrant(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
  let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
  let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;

  // 获取应用程序的accessTokenID
  let tokenId: number = 0;
  try {
    let bundleInfo: bundleManager.BundleInfo =
      await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
    let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
    tokenId = appInfo.accessTokenId;
  } catch (error) {
    const err: BusinessError = error as BusinessError;
    console.error(`获取应用Bundle信息失败,错误码:${err.code},错误信息:${err.message}`);
    return grantStatus;
  }

  // 校验应用是否被授予该权限
  try {
    grantStatus = await atManager.checkAccessToken(tokenId, permission);
  } catch (error) {
    const err: BusinessError = error as BusinessError;
    console.error(`检查权限授权状态失败,错误码:${err.code},错误信息:${err.message}`);
  }

  return grantStatus;
}

/**
 * 权限申请辅助方法(处理首次拒绝与二次引导设置)
 * @param permissions 待申请的权限数组
 * @param refuseStr 权限申请被拒绝后的提示语
 * @returns 最终是否授权成功
 */
async function requestPermissionHelper(permissions: Array<Permissions>, refuseStr: string): Promise<boolean> {
  // 判断首次申请是否全部授权成功
  let userGrant = true;
  try {
    let context = getContext() as common.UIAbilityContext;
    let isFirstTime: boolean = true;
    let atManager = abilityAccessCtrl.createAtManager();
    let grantStatus = await atManager.requestPermissionsFromUser(context, permissions);
    for (let element of grantStatus.authResults) {
      if (element !== abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
        userGrant = false;
        break;
      }
    }

    // 判断是否为非首次申请(弹窗未显示说明已被用户拒绝过一次)
    if (grantStatus.dialogShownResults) {
      for (let element of grantStatus.dialogShownResults) {
        if (!element) {
          isFirstTime = false;
          break;
        }
      }
    }

    // 非首次申请且授权失败:引导用户去设置页面开启
    if (!isFirstTime && !userGrant) {
      // 不同权限组不能同时申请,逐个申请并校验
      for (let permission of permissions) {
        const data: Array<abilityAccessCtrl.GrantStatus> =
          await atManager.requestPermissionOnSetting(context, [permission]);
        userGrant = true;
        for (let element of data) {
          if (element !== abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
            userGrant = false;
          }
        }
        if (!userGrant) {
          promptAction.showToast({
            message: refuseStr,
            duration: 5000
          });
        }
      }
    } else if (isFirstTime && !userGrant) {
      // 首次申请且授权失败:仅提示用户
      promptAction.showToast({
        message: refuseStr,
        duration: 5000
      });
    }
  }catch ( err){
    console.error(`Request permissions failed, code: ${(err as BusinessError).code}, message: ${(err as BusinessError).message}`);
    userGrant =  false;
  }

  return userGrant;
}

/**
 * 核心封装函数:先批量检查所有权限,仅对未授权权限发起申请
 * @param permissions 待校验/申请的权限数组
 * @param refuseStr 权限申请被拒绝后的提示语
 * @returns 所有权限是否最终授权成功(true:全部授权;false:存在未授权权限)
 */
export async function checkAndRequestPermissions(permissions: Array<Permissions>, refuseStr: string): Promise<boolean> {
  // 边界处理:空权限数组直接返回授权成功
  if (!permissions || permissions.length === 0) {
    console.warn("待申请权限数组为空,无需处理");
    return true;
  }

  // 第一步:批量检查所有权限的授权状态,筛选出未授权的权限
  const unGrantedPermissions: Array<Permissions> = [];
  for (const permission of permissions) {
    const grantStatus = await checkPermissionGrant(permission);
    if (grantStatus !== abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
      unGrantedPermissions.push(permission);
      console.log(`权限【${permission}】未授权,加入申请队列`);
    } else {
      console.log(`权限【${permission}】已授权,无需重复申请`);
    }
  }

  // 第二步:如果所有权限都已授权,直接返回true
  if (unGrantedPermissions.length === 0) {
    console.log("所有权限均已授权,无需发起申请");
    return true;
  }

  // 第三步:仅对未授权权限发起申请,返回最终申请结果
  console.log(`开始发起未授权权限申请,待申请权限:${unGrantedPermissions.join(', ')}`);
  const finalGrantResult = await requestPermissionHelper(unGrantedPermissions, refuseStr);

  return finalGrantResult;
}

一、核心调用流程

  1. 入口方法:checkAndRequestPermissions(permissions, refuseStr)
    └─▶ 边界检查:空数组直接返回授权成功
    └─▶ 执行权限状态检查(checkPermissionGrant)
        └─▶ 通过bundleManager获取应用tokenId
        └─▶ 调用AtManager.checkAccessToken验证权限状态
    └─▶ 筛选出未授权权限集合unGrantedPermissions
    └─▶ 调用requestPermissionHelper发起实际授权请求

  2. 权限申请辅助方法:requestPermissionHelper(unGrantedPermissions, refuseStr)
    └─▶ 首次申请请求
        └─▶ 通过AtManager.requestPermissionsFromUser发起弹窗申请
        └─▶ 校验授权结果authResults
    └─▶ 二次申请处理
        └─▶ 通过dialogShownResults判断是否首次拒绝
        └─▶ 调用requestPermissionOnSetting引导系统设置页
        └─▶ 逐个权限进行二次申请校验

结语

其实鸿蒙权限管理并没有想象中复杂,核心就是 “懂分寸、不贪心、够坦诚”。遵循本文的原则、分类标准和申请方法,再直接抄用现成的权限管理工具类,你的应用既能顺利实现功能,又能让用户觉得 “这 APP 真懂事”,好感度拉满!
如果想了解更详细的权限列表、API 用法,可直接查阅华为开发者联盟官方文档,合规开发才是在鸿蒙生态里走得更远的关键哦!

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐