本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新

   鸿蒙应用开发中,当应用需要访问用户的隐私信息或使用系统能力时(如获取位置、使用相机、访问日历等),必须向用户申请授权。这些权限属于user_grant类型。

user_grant权限指的是需要用户明确授权的权限,通常涉及用户隐私信息或系统能力。

常见user_grant权限

  • 位置信息(精确/模糊)

  • 相机拍摄

  • 麦克风录音

  • 日历访问

  • 通讯录读取

  • 健身运动数据

user_grant权限的申请原则

原则 说明
用户可知可控 必须由用户主动授权,系统弹窗提示
最小化原则 只申请业务必需的权限
动态申请 在需要使用权限时申请,而非启动时全部申请
不频繁弹窗 避免频繁打扰用户

二、user_grant权限申请流程

2.1 四步走流程

步骤1:在配置文件中声明权限
    ↓
步骤2:将权限与目标操作关联(开发阶段)
    ↓
步骤3:运行时检查权限 → 未授权则动态申请
    ↓
步骤4:处理授权结果(同意/拒绝)

2.2 步骤1:声明权限(module.json5)

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.LOCATION",
        "reason": "$string:location_permission_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.APPROXIMATELY_LOCATION",
        "reason": "$string:approximately_location_permission_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      }
    ]
  }
}

注意:user_grant权限必须填写reasonusedScene字段,用于上架审核和用户知情。

2.3 步骤2:权限与目标操作关联

在代码层面,将需要权限的操作与权限检查逻辑关联。

三、核心开发步骤(步骤3-4)

3.1 导入所需模块

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

3.2 检查权限是否已授权

在进行权限申请之前,需要先检查当前应用是否已被授予权限。

// PermissionUtil.ets
import { abilityAccessCtrl, bundleManager, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

async function checkPermissionGrant(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
    let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
    let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
    
    // 1. 获取应用程序的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(`Failed to get bundle info, code: ${err.code}, message: ${err.message}`);
    }

    // 2. 校验应用是否被授予权限
    try {
        grantStatus = await atManager.checkAccessToken(tokenId, permission);
    } catch (error) {
        const err: BusinessError = error as BusinessError;
        console.error(`Failed to check access token, code: ${err.code}, message: ${err.message}`);
    }

    return grantStatus;
}

// 检查多个权限的状态
async function checkPermissions(): Promise<void> {
    // 获取精确定位权限状态
    let grantStatus1: boolean = await checkPermissionGrant('ohos.permission.LOCATION') 
        === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
    
    // 获取模糊定位权限状态
    let grantStatus2: boolean = await checkPermissionGrant('ohos.permission.APPROXIMATELY_LOCATION') 
        === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;

    // 精确定位权限只能跟模糊定位权限一起申请,或者已经有模糊定位权限才能申请精确定位权限
    if (grantStatus2 && !grantStatus1) {
        // 已有模糊权限,需要申请精确定位权限
        console.info('Need to request precise location permission');
    } else if (!grantStatus1 && !grantStatus2) {
        // 两种权限都没有,需要申请模糊定位权限与精确定位权限
        console.info('Need to request both permissions');
    } else {
        // 已经授权,可以继续访问目标操作
        console.info('Permissions already granted');
    }
}

3.3 动态向用户申请授权

通过requestPermissionsFromUser()方法向用户请求授权。

方式一:在UIAbility中申请授权
// SecondAbility.ets
import { abilityAccessCtrl, common, Permissions, UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
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;
            for (let i = 0; i < grantStatus.length; i++) {
                if (grantStatus[i] === 0) {
                    // 用户授权
                    console.info(`${permissions[i]} is granted by user.`);
                } else {
                    // 用户拒绝授权
                    console.warn(`${permissions[i]} is denied by user.`);
                    // 引导用户到设置中开启
                    this.guideToSettings();
                }
            }
        })
        .catch((err: BusinessError) => {
            console.error(`Failed to request permissions, code: ${err.code}, message: ${err.message}`);
        });
}

export default class SecondAbility extends UIAbility {
    onWindowStageCreate(windowStage: window.WindowStage): void {
        // 重要:需要在loadContent回调中申请权限
        windowStage.loadContent('secondpages/Index', (err) => {
            if (!err) {
                reqPermissionsFromUser(permissions, this.context);
            }
        });
    }
    
    private guideToSettings(): void {
        // 引导用户去设置页面
        console.info('Guide user to settings');
    }
}
方式二:在UI中申请授权
// Index.ets
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();
    
    atManager.requestPermissionsFromUser(context, permissions)
        .then((data) => {
            let grantStatus: number[] = data.authResults;
            for (let i = 0; i < grantStatus.length; i++) {
                if (grantStatus[i] === 0) {
                    console.info(`${permissions[i]} is granted.`);
                } else {
                    console.warn(`${permissions[i]} is denied.`);
                    // 可以在UI上提示用户
                }
            }
        })
        .catch((err: BusinessError) => {
            console.error(`Failed to request permissions, code: ${err.code}`);
        });
}

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

    build() {
        Column() {
            Text('位置权限示例')
                .fontSize(20)
                .margin(20)
            
            Button('获取位置')
                .onClick(() => {
                    // 每次使用前最好再检查一次权限
                    // 因为用户可能在设置中关闭了权限
                })
        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
    }
}

3.4 处理授权结果

用户同意授权
if (grantStatus[i] === 0) {
    // 用户授权,可以继续访问目标操作
    this.accessLocation();
}
用户拒绝授权

当用户拒绝授权时,有几种处理方式:

方式1:引导用户到系统设置中开启

import { common } from '@kit.AbilityKit';

private guideToSettings() {
    let context = getContext(this) as common.UIAbilityContext;
    context.startAbility({
        action: 'action.settings.app.info',
        parameters: { bundleName: 'com.example.myapp' }
    });
}

方式2:调用requestPermissionOnSetting拉起权限设置弹窗

// 从API 12开始支持
async function requestPermissionInSetting(permission: Permissions) {
    let atManager = abilityAccessCtrl.createAtManager();
    try {
        await atManager.requestPermissionOnSetting(this.context, permission);
        // 用户已在设置中授权
        console.info('Permission granted in settings');
    } catch (error) {
        console.error('Failed to request permission in setting');
    }
}

四、限制

4.1 弹窗规则

规则 说明
不可被遮挡 系统权限弹窗不可被其他组件或控件遮挡
完整展示 弹窗信息需完整展示,便于用户识别
优先级最高 如果与其他组件同时展示,系统权限弹窗将默认覆盖其他组件

4.2 调用时机限制

场景 要求
在UIAbility的onWindowStageCreate中申请 需要等待loadContent/setUIContent执行结束后,或在回调中调用
在UIExtensionAbility中申请 需要在onWindowStageCreate函数执行结束后,或在回调中调用
原因 在Content加载完成前,requestPermissionsFromUser会调用失败

4.3 权限状态不可持久化

// 错误做法:缓存授权状态
let cachedPermission = true; // 假设用户之前授权了
if (cachedPermission) {
    this.accessLocation(); // 可能失败,因为用户可能在设置中关闭了权限
}

// 正确做法:每次使用前检查
async function accessLocation() {
    let grantStatus = await checkPermissionGrant('ohos.permission.LOCATION');
    if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
        // 已授权,可以访问
    } else {
        // 未授权,重新申请
    }
}

原因:用户可能在动态授予权限后通过系统设置来取消应用的权限,因此不能将之前授予的授权状态持久化。

五、完整示例

5.1 权限声明(module.json5)

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.APPROXIMATELY_LOCATION",
        "reason": "$string:approximately_location_permission_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.LOCATION",
        "reason": "$string:location_permission_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      }
    ]
  }
}

5.2 权限工具类

// PermissionManager.ets
import { abilityAccessCtrl, bundleManager, Permissions, common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

export class PermissionManager {
    private static instance: PermissionManager;
    private atManager: abilityAccessCtrl.AtManager;
    
    private constructor() {
        this.atManager = abilityAccessCtrl.createAtManager();
    }
    
    static getInstance(): PermissionManager {
        if (!PermissionManager.instance) {
            PermissionManager.instance = new PermissionManager();
        }
        return PermissionManager.instance;
    }
    
    // 检查单个权限
    async checkPermission(permission: Permissions): Promise<boolean> {
        try {
            let bundleInfo = await bundleManager.getBundleInfoForSelf(
                bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION
            );
            let tokenId = bundleInfo.appInfo.accessTokenId;
            
            let grantStatus = await this.atManager.checkAccessToken(tokenId, permission);
            return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
        } catch (error) {
            console.error('Check permission failed', error);
            return false;
        }
    }
    
    // 申请权限
    async requestPermissions(permissions: Permissions[], context: common.UIAbilityContext): Promise<boolean> {
        try {
            let result = await this.atManager.requestPermissionsFromUser(context, permissions);
            // 检查是否所有权限都被授予
            return result.authResults.every(status => status === 0);
        } catch (error) {
            console.error('Request permissions failed', error);
            return false;
        }
    }
    
    // 检查并申请权限(一步到位)
    async checkAndRequestPermissions(permissions: Permissions[], context: common.UIAbilityContext): Promise<boolean> {
        // 先检查是否都已授权
        for (let perm of permissions) {
            let granted = await this.checkPermission(perm);
            if (!granted) {
                // 有未授权的权限,发起申请
                return await this.requestPermissions(permissions, context);
            }
        }
        return true; // 全部已授权
    }
}

5.3 在页面中使用

// LocationPage.ets
import { PermissionManager } from './PermissionManager';
import { common } from '@kit.AbilityKit';

@Entry
@Component
struct LocationPage {
    @State hasPermission: boolean = false;
    @State locationText: string = '未知位置';
    
    async aboutToAppear() {
        const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
        const permissions = [
            'ohos.permission.LOCATION',
            'ohos.permission.APPROXIMATELY_LOCATION'
        ];
        
        // 检查并申请权限
        this.hasPermission = await PermissionManager.getInstance()
            .checkAndRequestPermissions(permissions, context);
    }
    
    async getLocation() {
        if (!this.hasPermission) {
            // 再次尝试申请
            const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
            this.hasPermission = await PermissionManager.getInstance()
                .requestPermissions(['ohos.permission.LOCATION'], context);
            
            if (!this.hasPermission) {
                // 引导用户去设置
                this.showSettingsDialog();
                return;
            }
        }
        
        // 获取位置逻辑
        this.locationText = '北京市朝阳区';
    }
    
    showSettingsDialog() {
        AlertDialog.show({
            message: '需要位置权限才能使用此功能,请在设置中开启',
            primaryButton: {
                value: '去设置',
                action: () => {
                    // 跳转到应用设置页
                }
            },
            secondaryButton: {
                value: '取消'
            }
        });
    }
    
    build() {
        Column() {
            Text(this.locationText)
                .fontSize(16)
                .margin(20)
            
            Button('获取当前位置')
                .enabled(this.hasPermission)
                .onClick(() => this.getLocation())
            
            if (!this.hasPermission) {
                Text('需要位置权限才能使用此功能')
                    .fontColor('#FF0000')
                    .fontSize(14)
                    .margin(10)
            }
        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
    }
}

Logo

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

更多推荐