本篇学习鸿蒙权限体系,实现优雅的权限申请流程

权限管理:安全与用户体验的平衡 教程结构图

图:权限管理:安全与用户体验的平衡 的关键流程与实现要点。

学习目标

完成本篇后,你将能够:

  • ✅ 理解鸿蒙权限体系
  • ✅ 实现权限检查与申请
  • ✅ 封装权限工具类
  • ✅ 处理权限拒绝场景

预计学习时间

约 90 分钟


实战一:理解权限体系

第一步:权限分类

类型 说明 示例
system_grant 系统授权,安装时自动授予 网络访问
user_grant 用户授权,需要运行时申请 相机、位置

第二步:权限等级

等级 说明
normal 普通权限,风险较低
restricted 受限权限,需要审核
dangerous 危险权限,涉及隐私

第三步:常用权限

权限 说明 类型
ohos.permission.INTERNET 网络访问 system_grant
ohos.permission.CAMERA 相机 user_grant
ohos.permission.LOCATION 位置 user_grant
ohos.permission.DETECT_GESTURE 手势检测 user_grant
ohos.permission.NFC_TAG NFC user_grant

实战二:声明权限

第一步:在 module.json5 中声明

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

第二步:添加权限说明

resources/base/element/string.json 中:

{
  "string": [
    {
      "name": "camera_permission_reason",
      "value": "用于拍摄官职名片照片"
    },
    {
      "name": "location_permission_reason",
      "value": "用于显示附近的历史遗迹"
    }
  ]
}

第三步:usedScene 配置

字段 说明
abilities 使用该权限的 Ability
when inuse(使用时)/ always(始终)

实战三:检查和申请权限

第一步:导入模块

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

第二步:检查权限状态

async function checkPermission(permission: Permissions): Promise<boolean> {
  try {
    // 获取应用信息
    const bundleInfo = await bundleManager.getBundleInfoForSelf(
      bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION
    );
    const tokenId = bundleInfo.appInfo.accessTokenId;
    
    // 创建权限管理器
    const atManager = abilityAccessCtrl.createAtManager();
    
    // 检查权限
    const grantStatus = atManager.checkAccessTokenSync(tokenId, permission);
    
    return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
  } catch (error) {
    console.error('检查权限失败:', error);
    return false;
  }
}

第三步:申请权限

async function requestPermission(
  context: common.UIAbilityContext,
  permissions: Permissions[]
): Promise<boolean> {
  try {
    const atManager = abilityAccessCtrl.createAtManager();
    
    const result = await atManager.requestPermissionsFromUser(context, permissions);
    
    // 检查所有权限是否都已授予
    return result.authResults.every(
      status => status === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
    );
  } catch (error) {
    console.error('申请权限失败:', error);
    return false;
  }
}

第四步:完整流程

async function ensurePermission(
  context: common.UIAbilityContext,
  permission: Permissions
): Promise<boolean> {
  // 先检查
  const hasPermission = await checkPermission(permission);
  if (hasPermission) {
    return true;
  }
  
  // 再申请
  return await requestPermission(context, [permission]);
}

实战四:封装权限工具类

第一步:创建 PermissionUtil

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

export class PermissionUtil {
  /**
   * 检查单个权限
   */
  static async check(permission: Permissions): Promise<boolean> {
    try {
      const bundleInfo = await bundleManager.getBundleInfoForSelf(
        bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION
      );
      const tokenId = bundleInfo.appInfo.accessTokenId;
      const atManager = abilityAccessCtrl.createAtManager();
      const grantStatus = atManager.checkAccessTokenSync(tokenId, permission);
      return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
    } catch {
      return false;
    }
  }

  /**
   * 检查多个权限
   */
  static async checkMultiple(permissions: Permissions[]): Promise<Map<Permissions, boolean>> {
    const result = new Map<Permissions, boolean>();
    for (const permission of permissions) {
      result.set(permission, await this.check(permission));
    }
    return result;
  }

  /**
   * 申请权限
   */
  static async request(
    context: common.UIAbilityContext,
    permissions: Permissions[]
  ): Promise<boolean> {
    try {
      const atManager = abilityAccessCtrl.createAtManager();
      const result = await atManager.requestPermissionsFromUser(context, permissions);
      return result.authResults.every(
        status => status === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
      );
    } catch {
      return false;
    }
  }

  /**
   * 确保权限(检查+申请)
   */
  static async ensure(
    context: common.UIAbilityContext,
    permission: Permissions
  ): Promise<boolean> {
    const hasPermission = await this.check(permission);
    if (hasPermission) return true;
    return await this.request(context, [permission]);
  }

  /**
   * 打开应用设置页
   */
  static async openSettings(context: common.UIAbilityContext): Promise<void> {
    const want: Want = {
      bundleName: 'com.huawei.hmos.settings',
      abilityName: 'com.huawei.hmos.settings.MainAbility',
      uri: 'application_info_entry',
      parameters: {
        pushParams: context.abilityInfo.bundleName
      }
    };
    
    try {
      await context.startAbility(want);
    } catch (error) {
      console.error('打开设置失败:', error);
    }
  }
}

第二步:使用工具类

import { PermissionUtil } from '../common/PermissionUtil';

@Entry
@Component
struct Lesson17Page {
  @State hasPermission: boolean = false;

  async aboutToAppear() {
    const permission: Permissions = 'ohos.permission.CAMERA';
    this.hasPermission = await PermissionUtil.check(permission);
  }

  async requestCameraPermission() {
    const context = getContext(this) as common.UIAbilityContext;
    const permission: Permissions = 'ohos.permission.CAMERA';
    this.hasPermission = await PermissionUtil.ensure(context, permission);
  }

  openSettings() {
    const context = getContext(this) as common.UIAbilityContext;
    PermissionUtil.openSettings(context);
  }
}

实战五:处理权限拒绝

第一步:设计引导页面

@Entry
@Component
struct Lesson17Page {
  @State permissionStatus: 'unknown' | 'granted' | 'denied' = 'unknown';
  @State showGuide: boolean = false;

  async aboutToAppear() {
    await this.checkPermissionStatus();
  }

  async checkPermissionStatus() {
    const permission: Permissions = 'ohos.permission.DETECT_GESTURE';
    const hasPermission = await PermissionUtil.check(permission);
    this.permissionStatus = hasPermission ? 'granted' : 'denied';
  }

  build() {
    Column() {
      // 头部
      Row() {
        Text('权限管理')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#1e293b')
      }
      .width('100%')
      .height(56)
      .padding({ left: 16, right: 16 })
      .backgroundColor(Color.White)

      // 内容
      Column({ space: 20 }) {
        if (this.permissionStatus === 'granted') {
          this.GrantedView()
        } else if (this.permissionStatus === 'denied') {
          this.DeniedView()
        } else {
          this.LoadingView()
        }
      }
      .width('100%')
      .layoutWeight(1)
      .justifyContent(FlexAlign.Center)
      .padding(20)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f8f6f5')
  }

  @Builder
  LoadingView() {
    Column({ space: 16 }) {
      LoadingProgress()
        .width(48)
        .height(48)

      Text('检查权限中...')
        .fontSize(14)
        .fontColor('#64748b')
    }
  }

  @Builder
  GrantedView() {
    Column({ space: 16 }) {
      Image($r('app.media.ic_check_circle'))
        .width(64)
        .height(64)
        .fillColor('#22c55e')

      Text('权限已授予')
        .fontSize(18)
        .fontColor('#1e293b')

      Text('您可以正常使用握姿感应功能')
        .fontSize(14)
        .fontColor('#64748b')
    }
  }

  @Builder
  DeniedView() {
    Column({ space: 16 }) {
      Image($r('app.media.ic_lock'))
        .width(64)
        .height(64)
        .fillColor('#64748b')

      Text('需要权限')
        .fontSize(18)
        .fontColor('#1e293b')

      Text('握姿感应功能需要手势检测权限')
        .fontSize(14)
        .fontColor('#64748b')
        .textAlign(TextAlign.Center)

      Button('授予权限')
        .width('80%')
        .height(44)
        .backgroundColor('#c41e3a')
        .margin({ top: 20 })
        .onClick(async () => {
          await this.requestPermission();
        })

      Text('如果无法授权,请前往设置手动开启')
        .fontSize(12)
        .fontColor('#9ca3af')
        .margin({ top: 12 })
        .onClick(() => {
          this.openSettings();
        })
        .decoration({ type: TextDecorationType.Underline })
    }
  }

  async requestPermission() {
    const context = getContext(this) as common.UIAbilityContext;
    const permission: Permissions = 'ohos.permission.DETECT_GESTURE';
    const granted = await PermissionUtil.ensure(context, permission);
    this.permissionStatus = granted ? 'granted' : 'denied';
  }

  openSettings() {
    const context = getContext(this) as common.UIAbilityContext;
    PermissionUtil.openSettings(context);
  }
}

@Builder
export function Lesson17PageBuilder() {
  Lesson17Page()
}

第二步:运行验证

hvigorw assembleHap --no-daemon

完整代码

完整代码见上方实战五。


本课小结

核心知识点

知识点 说明
system_grant 系统自动授权
user_grant 需要用户授权
checkAccessTokenSync 同步检查权限
requestPermissionsFromUser 申请权限
startAbility 打开设置页

权限申请最佳实践

  1. 只在需要时申请
  2. 说明权限用途
  3. 处理拒绝场景
  4. 提供设置入口
  5. 优雅降级

课后练习

练习1:批量权限申请

实现一次申请多个权限的流程。

练习2:权限状态监听

监听权限状态变化,实时更新 UI。


下一课预告

第18课我们将学习页面路由与导航架构,包括:

  • Navigation 路由系统
  • NavPathStack 路由栈
  • 路由参数传递
  • 导航架构设计

项目开源地址

https://gitcode.com/daleishen/gujinzhijian

Logo

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

更多推荐