一、鸿蒙 Next 权限申请概览
鸿蒙 Next 系统为了保护用户数据和系统资源,对应用的权限进行了细致的分类管理。其中,系统授权和用户授权是两种主要的授权方式,它们各自适用于不同类型的权限,开发者需要根据应用的具体功能需求来选择合适的授权方式。这就好比在建造一座大厦时,需要根据不同区域的功能需求选择合适的建筑材料和施工工艺,以确保大厦的稳固与安全。
1. 系统授权:直接配置文件配置申请 (不需要询问用户)
系统授权,正如其名,是由系统在应用安装过程中自动完成的权限授予操作。这种授权方式适用于那些对系统或其他应用影响较小、不涉及用户敏感信息的权限。
例如,应用获取网络信息、查询自身基本信息等操作所需的权限,通常都采用系统授权方式。
2. 用户授权:需要用户手动确认的权限(必须提示用户主动授权)
与系统授权不同,用户授权则更加注重用户的知情权和选择权。当应用需要访问用户的敏感信息或执行可能影响用户隐私的操作时,
如使用摄像头、麦克风、读取通讯录等,就必须通过用户授权方式获得用户的明确许可。这确保了用户始终对自己的数据拥有控制权,就像在自己的私人领地设置了一道道关卡,只有经过用户亲自授权的应用才能进入并使用相关资源。
二、鸿蒙 Next 授权流程解析
1. 系统授权流程解析
🐡 权限判断与申请准备
在进行系统授权之前,开发者首先要明确应用所需的权限,并判断这些权限是否属于系统授权类型。这需要开发者对鸿蒙 Next 的权限体系有深入的了解,熟悉各种权限的分类和适用场景。可以将其类比为航海前的航线规划,只有明确目的地和路线,才能确保航行的顺利进行。
🐡 系统自动授予权限的过程
一旦确定应用所需的权限为系统授权类型,开发者只需在应用的配置文件中正确声明这些权限。当用户安装应用时,系统会自动识别并授予相应的权限,整个过程无需用户进行额外的操作。这就像在一家自动化餐厅,顾客只需选择自己想要的菜品(声明权限),餐厅的自动化系统(鸿蒙 Next 系统)就会自动将菜品送到顾客面前(授予权限)。
🐡 系统授权权限列表展示
以下是一些常见的系统授权权限及其说明:
🐡 在配置文件中声明权限
配置文件的选择与定位
用户授权的第一步是在应用的配置文件中声明所需的权限。在鸿蒙 Next 项目中,通常使用“module.json5”配置文件来进行权限声明。这个配置文件就像是应用的“说明书”,告诉系统应用需要哪些权限才能正常运行。
权限声明的格式与规范在“module.json5”文件中,通过“requestPermissions”字段来声明权限。每个权限声明都包含“name”(权限名称)、“reason”(申请权限的原因)和“usedScene”(权限使用的场景)等属性。
🔊说明: 以下"ohos.permission.PERMISSION1"、"ohos.permission.PERMISSION2"仅为样例示意,不存在该权限。请开发者根据实际需要,参照上表要求填写对应属性。
{
"module" : {
// ...
"requestPermissions":[
{
"name" : "ohos.permission.PERMISSION1",
"reason": "$string:reason",
"usedScene": {
"abilities": [
"FormAbility"
],
"when":"inuse"
}
},
{
"name" : "ohos.permission.PERMISSION2",
"reason": "$string:reason",
"usedScene": {
"abilities": [
"FormAbility"
],
"when":"always"
}
}
]
}
}
其中,“name”必须是系统定义的有效权限名称,“reason”需要用简洁明了的语言向用户说明申请该权限的原因,并且要遵循一定的文案规范,如使用直白、具体、易理解的完整短句,避免使用被动语态,以句号结尾,同时要确保字符串长度适中,以适应多语言适配的需求。“usedScene”则用于指定权限使用的场景,包括使用权限的 UIAbility 或 ExtensionAbility 组件名称以及调用时机(“inuse”表示使用时,“always”表示始终)。
2. 用户授权流程详解
🐡 用户授权权限列表展示
以下是一些常见的用户授权权限及其说明:
🐡 在配置文件中声明权限
与“系统授权”一样,“用户授权”,在动态请求用户授权之前,应用应该先检查当前是否已经获得了所需的权限。在“module.json5”文件中,通过“requestPermissions”字段来声明权限。每个权限声明都包含“name”(权限名称)、“reason”(申请权限的原因)和“usedScene”(权限使用的场景)等属性。例如:
🔊说明: 以下"ohos.permission.PERMISSION1"、"ohos.permission.PERMISSION2"仅为样例示意,不存在该权限。请开发者根据实际需要,参照上表要求填写对应属性。
{
"module" : {
// ...
"requestPermissions":[
{
"name" : "ohos.permission.PERMISSION1",
"reason": "$string:reason",
"usedScene": {
"abilities": [
"FormAbility"
],
"when":"inuse"
}
},
{
"name" : "ohos.permission.PERMISSION2",
"reason": "$string:reason",
"usedScene": {
"abilities": [
"FormAbility"
],
"when":"always"
}
}
]
}
}
🐡 使用 API 动态请求用户授权
权限检查与准备
在动态请求用户授权之前,应用应该先检查当前是否已经获得了所需的权限。这可以通过调用“checkAccessToken()”函数来实现,该函数会返回“PERMISSION_GRANTED”或“PERMISSION_DENIED”,以指示当前权限的授予状态。这就好比在进入一个需要门票的场所之前,先检查自己是否已经购买了门票。
import { abilityAccessCtrl, bundleManager, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
const permissions: Array
async function checkPermissionGrant(permission: Permissions): Promise
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(`Failed to get bundle info for self. Code is ${err.code}, message is ${err.message}`);
}
// 校验应用是否被授予权限
try {
grantStatus = await atManager.checkAccessToken(tokenId, permission);
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`Failed to check access token. Code is ${err.code}, message is ${err.message}`);
}
return grantStatus;
}
async function checkPermissions(): Promise
let grantStatus: abilityAccessCtrl.GrantStatus = await checkPermissionGrant(permissions[0]);
if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
// 已经授权,可以继续访问目标操作
} else {
// 申请麦克风权限
}
}
动态请求授权的 API 调用
动态向用户申请权限是指在应用程序运行时向用户请求授权的过程。可以通过调用requestPermissionsFromUser()方法来实现。该方法接收一个权限列表参数,例如位置、日历、相机、麦克风等。用户可以选择授予权限或者拒绝授权。
可以在UIAbility的onWindowStageCreate()回调中调用requestPermissionsFromUser()方法来动态申请权限,也可以根据业务需要在UI中向用户申请授权。
应用在onWindowStageCreate()回调中申请授权时,需要等待异步接口loadContent()/setUIContent()执行结束后或在loadContent()/setUIContent()回调中调用requestPermissionsFromUser(),否则在Content加载完成前,requestPermissionsFromUser会调用失败。
应用在UIExtensionAbility申请授权时,需要在onWindowStageCreate函数执行结束后或在onWindowStageCreate函数回调中调用requestPermissionsFromUser(),否则在ability加载完成前,requestPermissionsFromUser会调用失败。
🐹:在UIAbility中向用户申请授权
// 使用UIExtensionAbility:将import { UIAbility } from '@kit.AbilityKit' 替换为import { UIExtensionAbility } from '@kit.AbilityKit';
import { abilityAccessCtrl, common, Permissions, UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
const permissions: Array
// 使用UIExtensionAbility:将common.UIAbilityContext 替换为common.UIExtensionContext
function reqPermissionsFromUser(permissions: Array
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
// requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
atManager.requestPermissionsFromUser(context, permissions).then((data) => {
let grantStatus: Array
let length: number = grantStatus.length;
for (let i = 0; i < length; i++) {
if (grantStatus[i] === 0) {
// 用户授权,可以继续访问目标操作
} else {
// 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
return;
}
}
// 授权成功
}).catch((err: BusinessError) => {
console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
})
}
// 使用UIExtensionAbility:将 UIAbility 替换为UIExtensionAbility
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage): void {
// ...
windowStage.loadContent('pages/Index', (err, data) => {
reqPermissionsFromUser(permissions, this.context);
// ...
});
}
// ...
}
🐹:在UI中向用户申请授权
import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
const permissions: Array
// 使用UIExtensionAbility:将common.UIAbilityContext 替换为common.UIExtensionContext
function reqPermissionsFromUser(permissions: Array
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
// requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
atManager.requestPermissionsFromUser(context, permissions).then((data) => {
let grantStatus: Array
let length: number = grantStatus.length;
for (let i = 0; i < length; i++) {
if (grantStatus[i] === 0) {
// 用户授权,可以继续访问目标操作
} else {
// 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
return;
}
}
// 授权成功
}).catch((err: BusinessError) => {
console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
})
}
@Entry
@Component
struct Index {
aboutToAppear() {
// 使用UIExtensionAbility:将common.UIAbilityContext 替换为common.UIExtensionContext
const context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
reqPermissionsFromUser(permissions, context);
}
build() {
// ...
}
}
!!!注意,如果用户拒绝了,再此调用requestPermissionsFromUser是不会再此弹出来的。如果还需要选项,就需要引导用户去设置里面授权。
二次向用户申请授权
requestPermissionOnSetting打开应用设置权限界面,可以参考打开应用设置权限界面。此节不详细说明。第一次被拒绝后,那么需要引导用户去设置中,授权。
const secondAuthResults = await atManager.requestPermissionOnSetting(context, permissions)
const isSecondAuth = secondAuthResults.every(s => s == abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)
!!!注意,在这里设置会有三种情况。
设置中授权界面一般会有三个选项:1、仅使用期间允许;2、每次使用询问;3、禁止;
只有第一个选项,结果会返回授权了。第三个选项,好理解,就是不授权。但是要注意了,第二个选项也是返回不授权。但是我们明显是知道用户是授权了的。所以上面的代码返回false之后,还需要再次调用第一次授权的代码,再次询问用户是否授权了。
下面是完整二次授权代码
const secondAuthResults = await atManager.requestPermissionOnSetting(context, permissions)
const isSecondAuth = secondAuthResults.every(s => s == abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)
if (!isSecondAuth) {
const isAuth = await firstReqPermissionFormUser(permissions, context)
resolve(isAuth)
} else {
resolve(true)
}
完整请求权限工具类代
import { abilityAccessCtrl, bundleManager, common, Permissions } from '@kit.AbilityKit';
import { UIContext } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
async function checkPermissionGrant(permission: Permissions): Promise
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(`Failed to get bundle info for self. Code is ${err.code}, message is ${err.message}`);
}
// 校验应用是否被授予权限
try {
grantStatus = await atManager.checkAccessToken(tokenId, permission);
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`Failed to check access token. Code is ${err.code}, message is ${err.message}`);
}
return grantStatus == abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
}
async function firstReqPermissionFormUser(permissions: Array
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
const data = await atManager.requestPermissionsFromUser(context, permissions)
let grantStatus: Array
return grantStatus.every(s => s == 0)
}
async function reqPermissionsFromUser(permissions: Array
uiContext: UIContext) {
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
try {
// requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
const isAuth = await firstReqPermissionFormUser(permissions, context)
if (isAuth) {
return true
}
return await new Promise((resolve, reject) => {
uiContext.showAlertDialog({
title: "系统提示",
message: "必须要授权才能使用,是否前往应用进行授权",
autoCancel: false,
primaryButton: {
value: "取消",
action: () => {
resolve(false)
}
},
secondaryButton: {
value: "前往授权",
action: async () => {
const secondAuthResults = await atManager.requestPermissionOnSetting(context, permissions)
const isSecondAuth =
secondAuthResults.every(s => s == abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)
if (!isSecondAuth) {
const isAuth = await firstReqPermissionFormUser(permissions, context)
resolve(isAuth)
} else {
resolve(true)
}
}
}
})
})
} catch (err) {
const error = err as BusinessError
console.error(`Failed to request permissions from user. Code is ${error.code}, message is ${error.message}`);
}
return true
}
export async function requestPermissions(uiAbilityContext: common.UIAbilityContext, uiContext: UIContext,
permissions: Permissions[]) {
const ps = permissions.map((s) => {
return checkPermissionGrant(s).then(isSuccess => {
if (isSuccess) {
return true
}
return Promise.reject(s)
})
})
const s = await Promise.allSettled(ps)
const rejectedPs =
s.filter(item => item.status == "rejected").map((item: PromiseRejectedResult) => item.reason as Permissions)
if (rejectedPs.length == 0) {
return true
}
return await reqPermissionsFromUser(rejectedPs, uiAbilityContext, uiContext)
}
🐡 处理用户授权结果
授权成功后的操作
当用户授权成功后,应用可以继续执行需要该权限的操作。例如,如果应用申请了相机权限并获得授权,就可以打开相机进行拍照或录像操作。这就像获得了进入宝库的钥匙,可以顺利取出宝藏(执行相应功能)。
授权失败后的应对策略
如果用户拒绝授权,应用需要友好地提示用户授权的必要性,并引导用户前往系统设置中手动授予权限。同时,应用应该确保在用户未授权的情况下,不会影响其他无关功能的正常使用。这就好比在一扇紧闭的门前,向用户解释门后的精彩内容,并引导用户找到打开门的正确方法,而不是强行推门或影响周围环境的正常秩序。
3. 不同权限类型申请方式总结
为了更清晰地展示不同权限类型的申请方式,我们通过以下表格进行总结:
三、示例代码:请求麦克风权限
以下是一个完整的示例代码,演示了如何在鸿蒙 Next 应用中请求麦克风权限:
import { abilityAccessCtrl, bundleManager, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
// 定义需要申请的权限列表,这里仅包含麦克风权限
const permissions: Array
// 检查当前应用是否已被授予指定权限
async function checkPermissionGrant(permission: Permissions): Promise
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(`Failed to get bundle info for self. Code is ${err.code}, message is ${err.message}`);
}
// 校验应用是否被授予权限
try {
grantStatus = await atManager.checkAccessToken(tokenId, permission);
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`Failed to check access token. Code is ${err.code}, message is ${err.message}`);
}
return grantStatus;
}
// 检查权限并根据结果进行相应操作
async function checkPermissions(): Promise
let grantStatus: abilityAccessCtrl.GrantStatus = await checkPermissionGrant(permissions[0]);
if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
// 已经授权,可以继续访问目标操作,这里可以添加使用麦克风的相关代码
console.log('已获得麦克风权限,可以进行录音等操作。');
} else {
// 申请麦克风权限
reqPermissionsFromUser(permissions);
}
}
// 使用 API 动态请求用户授权
function reqPermissionsFromUser(permissions: Array
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
atManager.requestPermissionsFromUser(globalThis.context as common.UIAbilityContext, permissions).then((data) => {
let grantStatus: Array
let length: number = grantStatus.length;
for (let i = 0; i < length; i++) {
if (grantStatus[i] === 0) {
// 用户授权,可以继续访问目标操作,这里可以添加使用麦克风的相关代码
console.log('用户已授权麦克风权限,可以进行录音等操作。');
} else {
// 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
console.log('用户拒绝授权麦克风权限,请前往系统设置中手动授予权限。');
return;
}
}
// 授权成功
}).catch((err: BusinessError) => {
console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
});
}
// 在应用启动或需要使用麦克风的地方调用 checkPermissions() 函数来检查和申请权限
checkPermissions();
在上述代码中,首先定义了需要申请的麦克风权限。然后,通过“checkPermissionGrant()”函数检查应用当前是否已获得该权限。如果未获得权限,则调用“reqPermissionsFromUser()”函数向用户发起授权请求。根据用户的授权结果,应用会在控制台输出相应的提示信息,并在授权成功后可以继续执行使用麦克风的相关操作。
总之,在鸿蒙 Next 应用开发中,正确处理权限申请是至关重要的。开发者需要深入理解系统授权和用户授权的机制与流程,根据应用的实际需求合理选择授权方式,并严格按照规范进行权限声明和请求操作。只有这样,才能确保应用在保障用户数据安全的前提下,提供稳定、优质的服务。