HarmonyOs应用权限申请,system_grant和user_grant区别。本文附头像上传申请user-grant权限代码示例
system_grant(体系授权)
system_grant指的是体系授权范例,在该范例的权限许可下,应用被允许访问的数据不会涉及到用户或设备的敏感信息,应用被允许执行的操作对体系大概其他应用产生的影响可控
user_grant(用户授权)
user_grant指的是用户授权范例,在该范例的权限许可下,应用被允许访问的数据将会涉及到用户或设备的敏感信息,应用被允许执行的操作大概对体系大概其他应用产生严重的影响。
权限APL等级
根据权限对于差别等级应用有差别的开放范围,权限范例对应分为以下三个等级,等级依次提高。
APL级别说明开放范围normal允许应用访问超出默认规则外的寻常体系资源,如设置Wi-Fi信息、调用相机拍摄等。这些体系资源的开放(包括数据和功能)对用户隐私以及其他应用带来的风险低。APL等级为normal及以上的应用。system_basic允许应用访问操作体系底子服务(体系提供大概预置的底子功能)相关的资源,如体系设置、身份认证等。这些体系资源的开放对用户隐私以及其他应用带来的风险较高。- APL等级为system_basic及以上的应用。- 部分权限对normal级别的应用受限开放,这部分权限在本引导中形貌为“受限开放权限”。system_core涉及开放操作体系核心资源的访问操作。这部分体系资源是体系最核心的底层服务,如果遭受粉碎,操作体系将无法正常运行。- APL等级为system_core的应用。- 仅对体系应用开放。
- 每次执行需要目标权限的操作时,应用都必须检查自己是否已经具有该权限。
如需检查用户是否已向您的应用授予特定权限,可以利用checkAccessToken()函数,此方法会返回Promise<[GrantStatus]>:PERMISSION_GRANTED或PERMISSION_DENIED。
- 每次访问受目标权限掩护的接口之前,都需要利用requestPermissionsFromUser()接口哀求相应的权限。
用户大概在动态授予权限后通过体系设置来取消应用的权限,因此不能将之前授予的授权状态恒久化。
- user_grant权限授权要基于用户可知可控的原则,需要应用在运行时主动调用体系动态申请权限的接口,体系弹框由用户授权,用户连合应用运行场景的上下文,识别出应用申请相应敏感权限的公道性,从而做出正确的选择。
- 体系不鼓励频繁弹窗打扰用户,如果用户拒绝授权,将无法再次拉起弹窗,需要应用引导用户在体系应用“设置”的界面中手动授予权限。
- API12新增可以二次向用户申请授权requestPermissionOnSetting()
- 体系权限弹窗不可被遮挡。
体系权限弹窗不可被其他组件/控件遮挡,弹窗信息需要完整展示,以便用户识别并完成授权动作。
如果体系权限弹窗与其他组件/控件同时同位置展示,体系权限弹窗将默认覆盖其他组件/控件。
代码示例:点击头像申请访问相机和相册媒体的权限,拒绝后二次点击半模态弹窗按钮向用户进行二次权限申请:
实现思路:
1.点击头像时利用checkAccessToken()检查当前是否已拥有权限,如果有则直接利用,如果没有则利用requestPermissionsFromUser()进行向用户扣问权限授权
2.如果用户全部拒绝,下一次利用时则利用requestPermissionOnSetting()进行二次用户扣问拉起弹窗(也可以引导用户去设置界面手动授权,在requestPermissionsFromUser()拒绝策略中即可实现)
//校验权限工具类
- import { abilityAccessCtrl,Context, bundleManager, common, Permissions } from '@kit.AbilityKit';
- import { BusinessError } from '@kit.BasicServicesKit';
- /**
- * 校验当前是否已经授权(已封装成工具类,如使用三层架构则可放入common中)
- * @param permission
- * @returns
- */
- export 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(`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;
- }
- /**
- *向用户二次询问拉起弹窗授权
- */
- export function reqPermissionTwice(permissions: Array<Permissions>, context: common.UIAbilityContext): Promise<Array<abilityAccessCtrl.GrantStatus>> {
- let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
- return atManager.requestPermissionOnSetting(context, permissions)
- }
复制代码 //主页面(其中涉及一些工具类未提供,不影响本次示例功能实现,结构会有影响。项目代码还在开发中,之后会开源)
- import {AppStorageEnum,NavigationBar,HdUser,hdHttp,cameraCapture,HdLoadingDialo,logger,authStore,reqPermissionTwice,checkPermissionGrant} from "@ss/basic"
- import { fileIo, picker } from '@kit.CoreFileKit'
- import { BusinessError, request } from '@kit.BasicServicesKit'
- import { promptAction } from '@kit.ArkUI'
- import { router } from '@kit.ArkUI'
- import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit'
- @Component
- export struct MineIndexView {
- pageInfos: NavPathStack = new NavPathStack()
- @StorageProp(AppStorageEnum.TOP_AVOID_HEIGHT)
- avoidTopHeight: number = 0
- @StorageProp(AppStorageEnum.LOGIN_USER)
- loginUser: HdUser = {} as HdUser
- @State isShowSheet: boolean = false
- dialog: CustomDialogController = new CustomDialogController({
- builder: HdLoadingDialog({ message: '更新中...' }),
- customStyle: true,
- alignment: DialogAlignment.Center
- })
- permissions: Array<Permissions> = ['ohos.permission.CAMERA', 'ohos.permission.WRITE_MEDIA'];
- isGrantFirst: boolean = false
- aboutToAppear(): void {
- console.log(JSON.stringify(this.loginUser))
- // this.checkPermissions()
- }
- async checkPermissions(permission: Permissions): Promise<void> {
- //通过checkAccessToken校验本次usergrant是否已授权
- let grantStatus: abilityAccessCtrl.GrantStatus = await checkPermissionGrant(permission);
- //根据返回结果处理业务
- const currentPermission: Array<Permissions> = [permission]
- if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) { //授权
- logger.info("已授权")
- if (currentPermission[0] == this.permissions[0]) {
- this.updateAvatarForCamera()
- } else {
- this.updateAvatarForPhotos()
- }
- } else { //未授权
- logger.info("未授权")
- if (this.isGrantFirst) { //因为二次询问授权之前必须调用一次requestPermissionsFromUser
- //二次向用户申请授权api12拥有
- reqPermissionTwice(currentPermission, getContext(this) as common.UIAbilityContext)
- .then((data: Array<abilityAccessCtrl.GrantStatus>) => {
- if (data[0] == abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
- if (currentPermission[0] == this.permissions[0]) {
- this.updateAvatarForCamera()
- } else {
- this.updateAvatarForPhotos()
- }
- }
- })
- .catch((err: BusinessError) => {
- console.error('data:' + JSON.stringify(err));
- });
- }
- }
- }
- //询问用户是否授权,只会调起一次弹窗
- reqPermissionsFromUser(permissions: Array<Permissions>, context: common.UIAbilityContext): void {
- let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
- // requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
- atManager.requestPermissionsFromUser(context, permissions).then((data) => {
- let grantStatus: Array<number> = data.authResults;
- let length: number = grantStatus.length;
- let count: number = 0
- for (let i = 0; i < length; i++) {
- count = count + 1
- if (grantStatus[i] === 0) {
- // 用户授权,可以继续访问目标操作
- this.isShowSheet = true
- } else {
- // 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
- if(count == length)
- this.isShowSheet = false
- }
- }
- // 授权成功
- }).catch((err: BusinessError) => {
- console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
- })
- }
- /**
- * 唤起相机上传图片
- */
- async updateAvatarForCamera() {
- //调用相机唤起工具类
- let URI = await cameraCapture(getContext(this) as common.UIAbilityContext)
- if (URI != undefined) {
- // 2. 存储到沙箱中(fileIo在API9之后就废弃了,使用fs替换)
- const context = getContext(this)
- const fileType = 'jpg'
- const fileName = Date.now() + '.' + fileType
- const copyFilePath = context.cacheDir + '/' + fileName
- const file = fileIo.openSync(URI, fileIo.OpenMode.READ_ONLY)
- fileIo.copyFileSync(file.fd, copyFilePath)
- // 3. 准备请求配置
- const config: request.UploadConfig = {
- url: hdHttp.baseURL + 'userInfo/avatar',
- method: 'POST',
- header: {
- 'Accept': '*/*',
- 'Authorization': `Bearer ${this.loginUser.token}`,
- 'Content-Type': 'multipart/form-data'
- },
- files: [
- {
- name: 'file', //multipart提交时,表单项目的名称,缺省为file
- uri: `internal://cache/` + fileName, //仅支持"internal"协议类型,"internal://cache/"为应用的私有目录,是必填字段
- type: fileType, //文件的内容类型,默认根据文件名或路径的后缀获取。
- filename: fileName //multipart提交时,请求头中的文件名。
- }
- ],
- data: []
- }
- // 4. 开始上传
- this.dialog.open() //自定义加载弹窗
- request.uploadFile(context, config, (err, data) => {
- if (err) {
- return logger.error('UPLOAD', err.message)
- }
- data.on('complete', () => {
- // 5. 更新头像
- hdHttp.get<HdUser>('userInfo').then(res => {
- this.loginUser.avatar = res.data.avatar
- authStore.setUser(this.loginUser)
- promptAction.showToast({ message: '更新头像成功' }) //提示弹窗
- this.dialog.close()
- })
- })
- })
- }
- }
- /**
- * 唤起相册上传图片
- */
- updateAvatarForPhotos() {
- const photoSelectOptions = new picker.PhotoSelectOptions()
- photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE
- photoSelectOptions.maxSelectNumber = 1
- const photoViewPicker = new picker.PhotoViewPicker()
- photoViewPicker.select(photoSelectOptions).then(result => {
- // 1. 得到文件路径
- const URI = result.photoUris[0]
- // 2. 存储到沙箱中(fileIo在API9之后就废弃了,使用fs替换)
- const context = getContext(this)
- const fileType = 'jpg'
- const fileName = Date.now() + '.' + fileType
- const copyFilePath = context.cacheDir + '/' + fileName
- const file = fileIo.openSync(URI, fileIo.OpenMode.READ_ONLY)
- fileIo.copyFileSync(file.fd, copyFilePath)
- // 3. 准备请求配置
- const config: request.UploadConfig = {
- url: hdHttp.baseURL + 'userInfo/avatar',
- method: 'POST',
- header: {
- 'Accept': '*/*',
- 'Authorization': `Bearer ${this.loginUser.token}`,
- 'Content-Type': 'multipart/form-data'
- },
- files: [
- {
- name: 'file', //multipart提交时,表单项目的名称,缺省为file
- uri: `internal://cache/` + fileName, //仅支持"internal"协议类型,"internal://cache/"为应用的私有目录,是必填字段
- type: fileType, //文件的内容类型,默认根据文件名或路径的后缀获取。
- filename: fileName //multipart提交时,请求头中的文件名。
- }
- ],
- data: []
- }
- // 4. 开始上传
- this.dialog.open() //自定义加载弹窗
- request.uploadFile(context, config, (err, data) => {
- if (err) {
- return logger.error('UPLOAD', err.message)
- }
- data.on('complete', () => {
- // 5. 更新头像
- hdHttp.get<HdUser>('userInfo').then(res => {
- this.loginUser.avatar = res.data.avatar
- authStore.setUser(this.loginUser)
- promptAction.showToast({ message: '更新头像成功' }) //提示弹窗
- this.dialog.close()
- })
- })
- })
- })
- }
- @Builder
- builderMenuBack() {
- Button({ type: ButtonType.Circle }) { //设置按钮的类型未圆形按钮
- Image($r("app.media.left"))
- }.onClick(() => {
- router.back()
- })
- .width(25)
- .height(25)
- .backgroundColor('rgba(255, 255, 255, 0)')
- }
- @Builder
- builderMenuMail() {
- Button({ type: ButtonType.Circle }) { //设置按钮的类型未圆形按钮
- Image($r("app.media.mail"))
- }.onClick(() => {
- this.pageInfos.pushPathByName("MailCenterView", false);
- })
- .width(25)
- .height(25)
- .backgroundColor('rgba(255, 255, 255, 0)')
- }
- /**
- * 半模态对话框
- */
- @Builder
- buildAvatarSheet() {
- Column() {
- Text("拍照")
- .width('100%')
- .height('50%')
- .lineHeight('45%')
- .textAlign(TextAlign.Center)
- .backgroundColor('#e5e7ed')
- .onClick(() => {
- this.isShowSheet = !this.isShowSheet
- this.checkPermissions(this.permissions[0])
- })
- Divider().color(Color.White)
- Text("从相册选择")
- .width('100%')
- .height('50%')
- .lineHeight('45%')
- .textAlign(TextAlign.Center)
- .backgroundColor('#e5e7ed')
- .onClick(() => {
- //并非多次一举,再调用reqPermissionsFromUser之前如果打开半模态化转场会阻塞主线程(原因不知)
- if(!this.isGrantFirst) {
- this.isGrantFirst = true
- this.reqPermissionsFromUser(this.permissions, getContext(this) as common.UIAbilityContext)
- }else{
- this.isShowSheet = !this.isShowSheet
- }
- })
- }
- .width('100%')
- .height(150)
- }
- build() {
- Navigation(this.pageInfos) {
- Column() {
- NavigationBar({
- buildButtonBackParam: () => {
- this.builderMenuBack()
- },
- buildMenuOneParam: () => {
- this.builderMenuMail()
- }
- })
- Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center, direction: FlexDirection.Column }) {
- Image($rawfile('kyrie.jpg'))
- .id("mine")
- .width(90)
- .height(90)
- .clip(true)
- .borderRadius(45)
- .margin({ top: 45 })
- .geometryTransition("avatar")
- .transition(TransitionEffect.opacity(0.99))
- .onClick(() => {
- this.isShowSheet = !this.isShowSheet
- if(!this.isGrantFirst)
- this.reqPermissionsFromUser(this.permissions, getContext(this) as common.UIAbilityContext)
- })
- Text(this.loginUser.username)
- .fontWeight(700)
- .fontSize(16)
- .margin({ top: 8 })
- Button({ type: ButtonType.Capsule, stateEffect: false }) {
- Text("终身大会员")
- .fontColor("#7f8085")
- .fontSize(12)
- }
- .width(150)
- .height(32)
- .margin({ top: 8 })
- .backgroundColor("#dbdce1")
- }
- .bindSheet(this.isShowSheet, this.buildAvatarSheet(), {
- height: 150,
- dragBar: false,
- showClose: false,
- onDisappear: () => {
- this.isShowSheet = false
- }
- })
- .width("90%")
- .height(200)
- }
- .width('100%')
- .transition(TransitionEffect.asymmetric(
- TransitionEffect.opacity(0.99),
- TransitionEffect.OPACITY
- ))
- .height('100%')
- }
- .backgroundColor("#e4e5ea")
- }
- }
复制代码 效果图:
1.第一次点击头像

2.假如全部拒绝,第二次访问继续扣问(详细业务权限)

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |