前言
客户端同学对app权限申请应该不陌生,鸿蒙系统中的权限管理有一个非常响亮帅气的名字:ATM
ATM (AccessTokenManager) 是HarmonyOS上基于AccessToken构建的统一的应用权限管理能力。
应用权限保护的对象可以分为数据和功能:
- 数据包含了个人数据(如照片、通讯录、日历、位置等)、设备数据(如设备标识、相机、麦克风等)、应用数据。
- 功能则包括了设备功能(如打电话、发短信、联网等)、应用功能(如弹出悬浮框、创建快捷方式等)等。
权限的申请需要严加控制,简单的说有以下几个要求:
- 真有必要才申请,而且得给出明确原因。
- 别一打开app就全给申请了,等真要用的时候再申请。
- 不能逼用户非得给权限,不给某个权限也不能不让用app。
- 用户明确拒绝授予权限不能反复弹窗,只能提示用户去系统设置里授予权限。
- 到底有哪些权限可以申请: 看官方文档
当前,ATM提供的应用权限校验功能是基于统一管理的TokenID(Token identity)。TokenID是每个应用的身份标识,ATM通过应用的TokenID来管理应用的权限。
TokenID这个东西等会在申请权限的代码里大家会看到,等会看到的时候就不会感觉太奇怪了。
说它奇怪是因为,在鸿蒙app代码里面申请权限的代码第一步是要拿到这个代表应用身份的TokenID。拿到这个东西再去做后面的操作。 有点像我回家开门之前,先从口袋里掏出自己的身份证。然后再进行后续的开门操作。
申请权限流程
权限的等级
应用的等级可以分为三个等级,分别是:
APL级别 | 说明 |
---|---|
system_core等级 | 该等级的应用服务提供操作系统核心能力。 |
system_basic等级 | 该等级的应用服务提供系统基础服务。 |
normal等级 | 普通应用。 |
默认情况下,应用的APL等级都为normal等级。
权限类型说明
根据授权方式的不同,权限类型可分为system_grant(系统授权)和user_grant(用户授权)。
简单来讲 system_grant类型的权限较user_grant类型的权限更安全一些。系统就能做主把权限给APP了。 system_grant类型的权限不会涉及到用户或设备的敏感信息。 而反之,涉及到用户或设备的敏感信息则属于user_grant, 当应用在运行到需要相关权限时,向用户进行申请。
打个比方,麦克风和摄像头对应的权限都是属于user_grant授权权限。
而常见的互联网权限是system_grant, 只需要在配置文件中申明即可,不需要让用户授权。
ohos.permission.INTERNET
允许使用Internet网络。
权限级别:normal
授权方式:system_grantohos.permission.INTERNET
不同类型权限的授权流程
- 先在配置文件里写清楚要哪些权限
- system_grant安装完就有了,user_grant得用到的时候弹窗让用户授权。
注意事项: 要申请或者要使用权限之前,先检查当前有没有该权限。没有的话才申请,有的话才用。
开干,申请个权限试试
按最常见的情况,我们申请一个APL等级都为normal等级, 权限类型属于user_grant的权限。 在官方文档中找到了一个比较适合的权限, 拿到这个权限之后可以在demo的页面中展示出模糊位置信息。
ohos.permission.APPROXIMATELY_LOCATION
允许应用获取设备模糊位置信息。
权限级别:normal
授权方式:user_grant
ACL使能:FALSE
申请条件:仅供使用API version 9及API version 9以上版本的SDK开发的应用申请。
那么接下来我们分几步走:
- 写个页面,展示当前是否具有该权限
- 写个按钮,点击这个按钮以后去申请该权限
- 用户授权通过后,拿到设备模糊位置信息并展示在页面中
代码
ts
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
import bundleManager from '@ohos.bundle.bundleManager';
import common from '@ohos.app.ability.common';
import geoLocationManager from '@ohos.geoLocationManager';
@Entry
@Component
struct Index {
permissions: Array<Permissions> = ['ohos.permission.APPROXIMATELY_LOCATION'];
@State hasAccess: boolean = false;
@State locationText: string = '';
@State accessText: string = '设备获取粗略位置权限: 未授权';
aboutToAppear() {
this.checkPermissions(this.permissions);
}
build() {
Column() {
Text(this.accessText)
.fontSize(20)
.fontWeight(FontWeight.Bold)
if (!this.hasAccess) {
Button('点击申请').margin({top: 12})
.onClick(() => {
this.reqPermissionsFromUser(this.permissions);
})
} else {
Text('设备模糊位置信息:' + '\n' + this.locationText)
.fontSize(20)
.margin({top: 12})
.width('100%')
}
}
.height('100%')
.width('100%')
.padding(12)
}
async checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
let atManager = abilityAccessCtrl.createAtManager();
let grantStatus: abilityAccessCtrl.GrantStatus;
// 获取应用程序的accessTokenID
let tokenId: number;
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 (err) {
console.error(`getBundleInfoForSelf failed, code is ${err.code}, message is ${err.message}`);
}
// 校验应用是否被授予权限
try {
grantStatus = await atManager.checkAccessToken(tokenId, permission);
} catch (err) {
console.error(`checkAccessToken failed, code is ${err.code}, message is ${err.message}`);
}
return grantStatus;
}
async checkPermissions(permissions: Array<Permissions>): Promise<void> {
let grantStatus: abilityAccessCtrl.GrantStatus = await this.checkAccessToken(permissions[0]);
if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
// 已经授权,可以继续访问目标操作
this.hasAccess = true;
this.accessText = '设备获取粗略位置权限: 已授权';
} else {
// 申请权限
this.hasAccess = false;
this.accessText = '设备获取粗略位置权限: 未授权';
}
}
reqPermissionsFromUser(permissions: Array<Permissions>): void {
let context = getContext(this) as common.UIAbilityContext;
let atManager = abilityAccessCtrl.createAtManager();
// requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
atManager.requestPermissionsFromUser(context, permissions).then((data) => {
this.hasAccess = true;
this.accessText = '设备获取粗略位置权限: 已授权';
let grantStatus: Array<number> = data.authResults;
let length: number = grantStatus.length;
for (let i = 0; i < length; i++) {
if (grantStatus[i] === 0) {
let requestInfo = {'priority': 0x203, 'scenario': 0x300, 'timeInterval': 0, 'distanceInterval': 0, 'maxAccuracy': 0};
let locationChange = (location) => {
console.log('locationChanger: data: ' + JSON.stringify(location));
};
try {
geoLocationManager.on('locationChange', requestInfo, locationChange);
} catch (err) {
console.error("errCode:" + err.code + ",errMessage:" + err.message);
}
try {
let location = geoLocationManager.getLastLocation();
this.locationText = 'latitude:' + location.latitude + '\n'
+ 'longitude:' + location.longitude + '\n'
+ 'altitude:' + location.altitude + '\n'
+ 'accuracy:' + location.accuracy + '\n'
+ 'speed:' + location.speed + '\n'
+ 'direction:' + location.direction;
} catch (err) {
console.error("errCode:" + err.code + ",errMessage:" + err.message);
}
} else {
this.hasAccess = false;
this.accessText = '设备获取粗略位置权限: 未授权';
// 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
return;
}
}
// 授权成功
}).catch((err) => {
console.error(`requestPermissionsFromUser failed, code is ${err.code}, message is ${err.message}`);
})
}
}
总结
权限概念在处理客户端业务逻辑时非常重要,对于越来越严格的用户信息保护要求来说这是不可避免的一个环节。需要在应用设计之初就完全掌握其概念。华为应用市场中对于权限隐私有一块专门的页面来要求开发者填写需要使用到的权限并且做出合理说明。所以需要以最小范围,非必要不申请的原则执行。贯彻对用户设备和信息的保护。