ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

鸿蒙开发权限申请实战:从声明到用户授权的完整流程

鸿蒙开发权限申请实战:从声明到用户授权的完整流程 鸿蒙开发里权限这块可能是不少人第一个被卡住的地方。明明在module.json5里写了权限声明运行时却不弹窗弹窗弹了用户点了拒绝后续功能直接瘫痪还有的权限明明申请成功了换个页面再调还是报错。这篇继续聊鸿蒙权限重点放在申请授权这件事上。前面两篇把权限的基本概念和应用声明层的东西讲得差不多了这篇就专门把运行时怎么请求权限、怎么处理回调、怎么做状态校验一次性说透。适合正在用 Stage 模型做鸿蒙应用开发的同学看尤其是已经被权限弹窗和授权回调折磨过的。我自己在把工具类应用从 Android 往鸿蒙迁移的时候感触最深的一点是鸿蒙把“用户同意”这件事做得非常重你写下的每一句权限代码本质都是在跟用户对话。1. 先理清思路鸿蒙的权限机制到底在做什么1.1 权限不是“越多越好”而是“用户说了算”做 Android 开发的时候权限在我眼里更像一道闸门清单文件里写上了运行时申请一下系统弹个窗用户点了允许后面的代码就一路畅通。但到了鸿蒙里我的理解被强行纠正了权限本质上是一段“受保护资源的访问资格”。相机、麦克风、定位、相册这些敏感数据全部被系统锁在资源管理框架后面应用每次想要触碰都得证明自己有资格而这个资格的来源只有一个——用户授权。所以你在代码里做的所有事情都不是“我声明了所以我可以用”而是“我向用户出示了一张访问说明用户画了勾系统才把门打开”。这个机制决定了三件事第一权限声明只是报名不代表入场第二user_grant 类型的权限必须运行时弹窗征求用户同意没有绕过的方式第三用户后续可以到设置里反悔随时把权限收回。为什么偏偏是这种设计因为隐私合规已经是一条硬杠杠。系统把权限关卡设计得越重应用乱申请权限、偷偷采集信息的空间就越小。我见过不少开发者抱着“先把权限全声明了以后用得着”的心态结果上架审核被拒理由就是“申请的权限与业务场景不匹配”。在鸿蒙上这条路是走不通的你申请的每个权限都要有说得过去的理由。1.2 system_grant 与 user_grant哪些需要弹窗哪些不需要很多人刚接触鸿蒙时会混淆一个概念是不是所有权限都要动态申请答案是否定的。鸿蒙把权限分成了两大类一类是系统授权system_grant一类是用户授权user_grant两者差别非常大。对比项system_grant 系统授权user_grant 用户授权授予时机应用安装时由系统自动授予运行时弹窗用户点击同意后授予是否需要弹窗不需要需要能否被用户在设置中关闭一般不可以可以典型权限INTERNET 网络权限位置、相机、麦克风、相册理解了这张表很多“为什么不弹窗”的问题就解开了一半。你申请一个ohos.permission.INTERNET系统在安装应用的时候就直接把权限给了运行时自然不会有任何弹窗也不需要你写任何动态申请的代码。但你要用定位或者相机那就必须老老实实走“弹窗 → 用户同意 → 返回结果”这条路。为什么系统要把这两类分开核心考虑是“打扰成本”。网络这类权限给用户带来的感知很弱应用装上就要联网逐个弹窗反而烦人而相机、相册这种涉及个人隐私的能力用户有权在每次使用时知道并决定。这个设计逻辑理解透了你去规划功能模块的时候就清楚哪一步该写申请代码、哪一步只需要声明就够。2. 权限的分类与等级别等上线才发现权限申请不下来2.1 权限等级与 APL为什么有的权限你根本申请不到比 system_grant 和 user_grant 更隐蔽的一个坑是权限等级。鸿蒙里的权限本身分了三档normal普通、system_basic系统基础、system_core系统核心。普通应用默认的应用权限等级APL是 normal所以能申请的权限基本被限定在 normal 这一档里。system_basic 和 system_core 的权限普通应用很多时候是碰不到的。权限等级谁能申请典型例子normal普通应用网络、蓝牙、日历读取等system_basic系统应用或经过特殊授权的应用修改系统设置、读取设备状态等system_core系统应用重启系统、安装应用等高危操作这个机制可以类比成小区门禁普通住户的门禁卡只能刷开自己楼层和小区大门物业人员的卡能刷开设备间安保系统的卡能刷开核心机房。你拿着一张普通住户的卡却去刷设备间的门被拒不是因为密码不对而是权限等级压根不匹配。实操中的教训是如果某个权限在文档里标注是 system_basic而你的应用只是普通应用那就别死磕代码了该放弃就放弃或者换一个普通权限能覆盖的替代方案。我遇到过有同学费了半天劲申请一个系统基础权限反复确认代码没问题最后查文档才发现普通应用根本没资格。这是设计层面的问题不是代码层面的问题。2.2 常用权限速查按业务场景对照着选为了方便日常开发我把最常见的几个业务场景和对应权限整理成了一张速查表。这张表在立项排期的时候就能用上提前对照一遍能省掉很多后面改声明的麻烦。业务场景权限名授权类型补充说明定位模糊ohos.permission.APPROXIMATELY_LOCATIONuser_grant建议和精确位置一起申请让用户选择定位精确ohos.permission.LOCATIONuser_grant需要 Android 上类似 ACCESS_FINE_LOCATION 的能力相机ohos.permission.CAMERAuser_grant拍照、扫码等场景麦克风ohos.permission.MICROPHONEuser_grant录音、语音识别等场景读取相册ohos.permission.READ_IMAGEVIDEOuser_grant不同 API 版本命名可能不同老版本可能是 READ_MEDIA写入相册ohos.permission.WRITE_IMAGEVIDEOuser_grant保存图片、视频时常用网络ohos.permission.INTERNETsystem_grant声明后安装即授无需动态申请这张表只是常用场景的参考真正的权威来源永远是官方文档里的权限列表。我的习惯是每个权限在集成前都去文档里确认三件事——权限等级、授权类型、是否限制申请。文档没查清楚就动手写代码是后面踩坑的最大根源。3. 实操一段完整的申请授权代码是怎么写出来的3.1 在 module.json5 里先写对声明动态申请权限之前第一步永远是声明。鸿蒙应用清单文件module.json5里的requestPermissions字段就是应用的“权限报名表”。很多开发者在这里写错了一个细节导致后面运行时申请出了问题。我以一个定位场景为例字段长这样{ module: { requestPermissions: [ { name: ohos.permission.APPROXIMATELY_LOCATION, reason: $string:location_reason, usedScene: { abilities: [EntryAbility], when: inuse } }, { name: ohos.permission.LOCATION, reason: $string:location_reason, usedScene: { abilities: [EntryAbility], when: inuse } } ] } }这里有两个关键点。第一user_grant 类型的权限必须写reason也就是向用户解释“为什么需要这个权限”的文字说明而且规范做法是写到字符串资源里比如$string:location_reason方便多语言适配和统一修改。第二usedScene要写清楚在哪个 Ability 里用、是“仅使用期间”还是“后台也可用”。这一项不只是给系统看的应用市场上架审核也会看。我遇到过声明没问题但上架被驳回的情况原因就是用途描述太模糊把理由写具体之后顺利通过。还有一个很容易忽略的细节精确位置权限和模糊位置权限建议一起声明。它们对应的业务能力是一个连续的光谱你只声明 LOCATION用户可能无法选择仅返回模糊位置只声明 APPROXIMATELY_LOCATION业务又拿不到精确坐标。两个一起声明把选择权交给用户是体验最好的做法。3.2 动态申请权限的正确打开方式声明配好之后真正的动态申请代码就来了。鸿蒙里没有像 Android 那么多 Activity Result API 的花样核心就是通过abilityAccessCtrl里的requestPermissionsFromUser拉起系统弹窗。下面这段代码是常见的写法适用于 API 10 及之后的版本。如果你的 SDK 版本老一些导入路径可能是ohos.abilityAccessCtrl方法名基本一致差别不大。import { abilityAccessCtrl, Permissions, common } from kit.AbilityKit; function requestLocationPermission(context: common.UIAbilityContext) { const atManager abilityAccessCtrl.createAtManager(); const permissions: Permissions[] [ ohos.permission.APPROXIMATELY_LOCATION, ohos.permission.LOCATION ]; try { atManager.requestPermissionsFromUser(context, permissions) .then((result) { // authResults 数组和请求的 permissions 数组顺序是一一对应的 const authResults: number[] result.authResults; if (authResults.length 0 authResults[0] 0) { // 用户授权了模糊定位可以继续 console.info(APPROXIMATELY_LOCATION granted); } else { // 用户拒绝了 console.warn(APPROXIMATELY_LOCATION denied); } }) .catch((err) { console.error(request permission error: JSON.stringify(err)); }); } catch (e) { console.error(exception: JSON.stringify(e)); } }这段代码有几个地方需要特别注意。第一authResults数组的顺序与传入的permissions数组顺序一致所以判断结果时不能拿到第 0 个就当所有权限都过了要按业务需要对每个权限分别判断。第二authResults可能是空数组说明系统没有返回任何授权结果这种情况一定要做兜底处理别直接下标越界。第三整个调用要放在 try-catch 里权限弹窗的拉起可能因为上下文问题抛出异常不捕获的话很容易在线上出现未处理异常。更关键的是申请时机。很多新手喜欢在页面加载时立刻申请权限生怕之后没机会。但体验最好的方式是等用户真正要触发功能时再申请。比如界面上有个“开始定位”按钮用户点击后才调requestPermissionsFromUser。这样做的原因是用户在当前上下文里能清晰理解为什么需要这个权限拒绝率会明显降低。我自己测试过同样的应用启动即弹窗的权限拒绝率比触发式申请高出一大截这是实打实的用户心理差异。3.3 权限拿回来之后还要做什么权限申请成功不代表一劳永逸。用户随时可以到系统设置里关掉某个权限你再调用相关能力时就会直接失败。所以正确的使用姿势是每次在真正使用受保护能力之前先做一次权限校验。import { abilityAccessCtrl, common } from kit.AbilityKit; function checkLocationPermission(context: common.UIAbilityContext): boolean { const atManager abilityAccessCtrl.createAtManager(); const tokenId context.applicationInfo.accessTokenId; const result atManager.checkAccessTokenSync(tokenId, ohos.permission.LOCATION); return result abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED; }这也是为什么我前面强调要先拿 UIAbilityContext。校验权限的时候需要应用的accessTokenId这个值在 UIAbility 的applicationInfo里可以拿到。不同版本的 API 在获取方式上可能有微调但思路是一致的先拿 token 再检查权限。如果校验不通过别直接让功能报错而是要引导用户去打开权限。比较规范的做法是弹一个自定义对话框说明这个功能为什么需要权限然后跳转到应用的权限设置页。跳转设置页的写法在不同 SDK 版本里略有差异我建议封装成独立的工具函数后续要调整只改一个地方。为什么非得跳设置页因为如果系统已经判定用户“不再询问”你再调requestPermissionsFromUser也不会弹窗了只有设置页能救回来。4. 常见问题与排查技巧实录4.1 权限弹窗死活不弹这是群里问得最多的问题。弹窗不弹先别怀疑系统坏了按顺序排查这四件事。第一确认权限类型是不是system_grant如果是那本来就不弹不用做动态申请。第二检查module.json5的requestPermissions字段是否真的写上了尤其是仔细看权限名拼写鸿蒙权限名特别长少个下划线就废了。第三确认调用requestPermissionsFromUser时传的 context 是前台 UIAbility 的 context如果上下文不对或者页面还没就绪系统是无法拉起弹窗的。第四也是容易被忽略的用户之前拒绝过一次并勾了“不再询问”之后系统就不再弹了这时候只能通过设置页处理。我见过一个典型场景开发者把权限申请写在了启动时异步函数的回调里结果 context 已经失效了弹窗完全没反应。排查下来不是权限配置问题而是生命周期问题。所以申请权限这件事一定绑定在具体的用户操作上而不是生命周期回调里。4.2 用户拒绝了后面还有救吗有但要讲究方法。首先要区分“首次拒绝”和“永久拒绝”。用户第一次点拒绝系统还是允许你再次发起申请的但如果弹窗里出现过类似“不再询问”的选项且被勾选那requestPermissionsFromUser就基本失效了只能引导用户去设置页手动打开。这里要特别提醒一句不要为了实现某个功能就在用户拒绝后反复弹窗这种做法在鸿蒙上非常容易触发系统的频率限制甚至让应用被判定为骚扰。更好的做法是第一次拒绝后先用自定义对话框解释用途给用户一个缓冲如果用户仍然决定不授权就优雅降级比如隐藏相关功能或展示说明文案而不是一进来就围追堵截。体验好的应用授权流程一定是“讲道理”而不是“硬要”。4.3 申请成功了功能却还是不可用权限申请成功但功能不可用这个坑我也踩过。最常见的原因是权限链路上有遗漏。举个真实的例子拍照功能你只申请了相机权限CAMERA拍完照要保存到相册结果保存失败。原因就是写相册还需要WRITE_IMAGEVIDEO或对应的媒体权限你没有申请。权限这块要看“完整链路”从采集到落地每个环节需要的权限都要覆盖到。还有一种情况是权限顺序判断错了。动态申请的时候一次性传了多个权限回调里的authResults是按请求顺序排列的如果你只判断authResults[0]而业务真正依赖的是第三个权限那很可能误判了授权状态。我的习惯是把授权结果和权限名绑定在一起逐个判断不要偷懒只看一个值。4.4 定位权限的特殊处理定位权限值得单独说因为它在鸿蒙里被拆分成了模糊位置和精确位置两个权限。申请的时候建议把两个都传上系统弹窗会让用户选择“模糊位置”或“精确位置”。如果你只申请了精确位置用户可能被迫交出精确坐标这会放大隐私顾虑如果你只申请了模糊位置那拿不到精确坐标就没法做地图级定位。实操里有个小技巧通过checkAccessTokenSync分别检查两个权限的授权状态。如果APPROXIMATELY_LOCATION是授权状态而LOCATION是拒绝状态说明用户选择了模糊定位如果两个都授权就是精确定位。业务代码可以根据这个状态做不同处理比如地图类应用在模糊定位时提示用户“当前只能显示大致范围”。还有一个容易被忽略的场景用户在设置里改了定位模式从精确改成模糊应用回到前台后别假设权限状态没变在页面onShow生命周期里重新校验一遍才是稳妥的做法。常见问题可能原因解决思路权限弹窗不弹系统授权权限、声明缺失、context 不对按 4.1 的四步排查授权后功能不可用权限链路不全、判断错索引梳理完整权限链逐个判断结果定位只返回模糊结果用户选择了模糊定位分别检查两个定位权限授权状态设置页改了权限应用没反应页面未重新校验在 onShow 里重新执行权限检查5. 给刚开始做鸿蒙权限功能的小伙伴一点建议做了几个项目的权限功能之后我个人养成了三个习惯对排查和提效都有很大帮助。第一项目设计阶段就维护一张权限登记表把功能模块、涉及权限、授权类型、使用场景全部列出来后面所有开发都照着这张表走既不遗漏也不乱申请。第二把权限申请的逻辑统一封装成一个工具类包括动态申请、结果回调、状态校验、跳转设置页所有页面都调同一个入口这样出问题时只需要查一个文件而不是满工程搜索。第三不要在测试时只点“始终允许”要实打实把“拒绝一次”“永久拒绝”“设置页手动关闭”这些场景都跑一遍只有把这些分支都处理好了才敢说权限功能真正写完了。鸿蒙权限这套机制初看会觉得繁尤其是经历过 Android 那种相对自由的环境。但真正用顺手之后你会发现它反而帮助应用建立了更清晰的隐私边界每个权限都有名目、有理由、有用户授权记录。你搭好这套流程之后后续加新功能需要新权限时照着同一套模板走基本不会出岔子。这也是我为什么推荐大家在一开始就把基础打好别等到应用上架被审核打回、或者用户反馈功能不可用的时候才回头补功课。
返回列表