HarmonyOS 7 AbilityAccessCtrl + ArkUI:权限弹窗重入治理与提审证据链【鸿蒙心迹】
这次提审前自测我故意连续点了三次“扫描讲义”。结果权限说明弹层叠了两层系统授权请求也被触发了两次。功能最终能用却很难向审核人员解释应用究竟在什么场景申请相机权限用户拒绝后会发生什么。Demo 叫Permission Gate Lab只有一个核心场景用户主动点击“扫描讲义”应用解释用途然后请求相机权限。请求编号固定为perm_20261001_03用途文案是“扫描纸质讲义并生成课堂笔记”。测试时间为 14:42权限状态按IDLE → EXPLAINING → REQUESTING → GRANTED推进拒绝时进入DENIED返回设置页后再刷新不自动重弹。一开始我以为这只是按钮防抖。加上 500 毫秒节流后慢一点连点确实不再叠层可应用从后台回来、页面重新构建或两个入口同时调用时还是会产生并发请求。真正的问题不是点击频率而是权限申请没有被当成一项有生命周期、有唯一所有者的任务。一、权限能申请不代表申请时机合理上架前检查权限时容易只看两个地方module.json5有没有声明调用requestPermissionsFromUser()有没有返回。实际产品还要回答另外几个问题权限是否由明确功能触发说明文案是否对应真实用途拒绝后是否仍能使用不需要权限的部分应用是否频繁索取第三方 SDK 是否在用户操作前提前启动采集。华为应用市场的发布说明把隐私、兼容性、稳定性和性能列为提交前测试内容。对第三方 SDK官方上架说明还要求在隐私政策中逐一明示其收集个人信息的目的、方式和范围。也就是说代码层的授权结果只是证据的一部分触发链路和披露内容还要能对得上。Permission Gate Lab 不在aboutToAppear()或onForeground()中请求相机权限。页面首次打开只显示功能说明用户点击“扫描讲义”后才进入申请链路。权限声明也把使用场景限制为使用时。下面的配置解决的是“代码会申请但清单无法解释申请场景”的问题。reason应使用项目资源正式文案要和页面说明、隐私政策保持一致。{ module: { requestPermissions: [ { name: ohos.permission.CAMERA, reason: $string:camera_permission_reason, usedScene: { abilities: [EntryAbility], when: inuse } } ] } }这里的inuse不是“应用一启动就用”而是说明该权限服务于前台使用场景。业务层仍需选择合理触发点。当前 Demo 的按钮文案、说明弹层和资源文案都使用“扫描纸质讲义并生成课堂笔记”避免清单写“拍照”页面写“识别”隐私政策又写“上传图片”这种三套说法。如果正式产品还会把照片上传云端就必须继续说明传输目的和处理范围不能因为系统相机授权已经通过就把后续数据处理一并视作用户同意。二、把一次申请收口成一个 Promise问题复现时三个入口都会触发同一个方法主页扫描按钮、空状态引导按钮、快捷操作卡片。每个入口都先查权限再各自弹说明、各自请求。按钮节流只能保护一个组件保护不了跨组件和生命周期重入。我把请求收进PermissionGate同时只允许一个inFlightPromise 存在。后续入口拿到同一个结果不再创建第二次系统请求。状态也从布尔值改成枚举避免isLoadingtrue和hasPermissionfalse组合出含义不明的页面。这段代码解决的是“多个入口重复发起权限申请”的问题。import{abilityAccessCtrl,Permissions}fromkit.AbilityKitimporttype{common}fromkit.AbilityKitexportenumGateState{IDLEIDLE,EXPLAININGEXPLAINING,REQUESTINGREQUESTING,GRANTEDGRANTED,DENIEDDENIED,FAILEDFAILED}exportclassPermissionGate{privatereadonlypermission:Permissionsohos.permission.CAMERAprivateinFlight?:PromiseGateStatestate:GateStateGateState.IDLErequest(context:common.UIAbilityContext):PromiseGateState{if(this.inFlight)returnthis.inFlightthis.inFlightthis.execute(context).finally((){this.inFlightundefined})returnthis.inFlight}privateasyncexecute(context:common.UIAbilityContext):PromiseGateState{constmanagerabilityAccessCtrl.createAtManager()this.stateGateState.REQUESTINGconstresultawaitmanager.requestPermissionsFromUser(context,[this.permission])constindexresult.permissions.indexOf(this.permission)this.stateindex0result.authResults[index]0?GateState.GRANTED:GateState.DENIEDreturnthis.state}}代码没有默认取authResults[0]而是先按权限名找索引。当前列表只有相机权限看起来多此一举当正式项目同时请求多个权限时这个细节能避免结果顺序变化后映射错位。finally()只负责释放“本次请求占用”不会把业务状态重置为IDLE。如果用户拒绝页面要稳定停在DENIED展示替代路径。异常则进入FAILED记录错误码并允许用户再次从明确入口发起不能用循环重试制造弹窗轰炸。三、说明层和系统弹窗不能同时抢状态权限说明层是业务 UI系统授权弹窗由系统管理。如果页面在说明层仍显示时就调用系统请求返回后又触发页面刷新很容易出现遮罩未关闭、按钮仍可点击或焦点落到错误组件的问题。当前页面把流程拆成两步第一次点击只进入EXPLAINING用户在说明层点击“继续申请”页面先关闭说明层再等待下一次 UI 状态稳定后进入REQUESTING。这并不是为了多做一步而是让用户知道权限与哪个功能有关。下面这段页面代码解决的是“业务弹层、系统弹窗和扫描任务同时启动”的次序问题。EntryComponentstruct PermissionGatePage{StategateState:GateStateGateState.IDLEStateshowExplanation:booleanfalseprivategate:PermissionGatenewPermissionGate()privateopenExplanation():void{if(this.gateStateGateState.REQUESTING)returnthis.gateStateGateState.EXPLAININGthis.showExplanationtrue}privateasynccontinueRequest():Promisevoid{this.showExplanationfalsethis.gateStateGateState.REQUESTINGconstcontextgetContext(this)ascommon.UIAbilityContextthis.gateStateawaitthis.gate.request(context)if(this.gateStateGateState.GRANTED){this.startScanner(perm_20261001_03)}}privatestartScanner(requestId:string):void{hilog.info(0x0000,PermissionGate,requestId${requestId}stateGRANTED scannerstart)}}这里还有一个容易被忽略的风险用户关闭页面时系统请求可能尚未返回。PermissionGate可以继续完成权限状态更新但页面对象不应该再启动扫描器。正式项目应让扫描任务由页面可见的控制器持有并在组件销毁时取消订阅权限结果本身则在下一次进入页面时重新查询不能依赖一个已经销毁的回调去更新 UI。DevEco Studio 画面中左侧目录把PermissionGate.ets、PermissionAudit.ets与页面分开中间正是inFlight去重逻辑右侧模拟器停在REQUESTING底部 HiLog 只出现一条perm_20261001_03请求。连续点击三次时日志中的deduplicated2表示两次入口复用了同一个 Promise而不是又弹两次系统窗口。四、拒绝以后返回前台只刷新不自动再申请我在自测脚本里专门加入了“拒绝—切到设置—回到应用”。早期版本在onForeground()中再次执行申请结果用户刚拒绝一次切回来又看到请求动作。即使系统没有再次展示弹窗这种实现也会让页面状态反复跳动。现在onForeground()只做一件事重新检查授权状态。如果用户已经在系统设置中开启相机权限页面从DENIED更新为GRANTED如果仍未授权继续显示说明和“前往设置”指引。绝不在生命周期回调中自动调用requestPermissionsFromUser()。下面这段代码解决的是“从设置返回后状态过期”和“前台恢复时再次索权”两个问题。import{bundleManager,abilityAccessCtrl}fromkit.AbilityKitexportasyncfunctionrefreshCameraGrant():PromiseGateState{constinfobundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION)consttokenIdinfo.appInfo.accessTokenIdconstmanagerabilityAccessCtrl.createAtManager()constresultawaitmanager.checkAccessToken(tokenId,ohos.permission.CAMERA)returnresultabilityAccessCtrl.GrantStatus.PERMISSION_GRANTED?GateState.GRANTED:GateState.DENIED}// EntryAbility.onForeground 只发出 refresh 事件// 页面收到事件后调用 refreshCameraGrant()不自动申请权限。checkAccessToken()适合做状态刷新不会制造授权弹窗。用户拒绝后的再次申请策略还要结合当前 API 行为和产品交互验证不能写一个死循环只要结果不是 0 就再次请求。若系统不再允许同一路径重复拉起应展示前往设置的明确操作并保留不需要相机权限的手动录入功能。五、证据链不记录隐私只记录流程是否守规矩提审自检需要证据但“证据”不等于把用户行为完整埋点。Permission Gate Lab 只记录请求编号、入口、声明用途版本、状态变化、时间与结果不记录拍摄内容也不保存设备中的图片路径。本次自测记录如下14:42:06 requestIdperm_20261001_03 sourcescan_note stateEXPLAINING 14:42:09 requestIdperm_20261001_03 stateREQUESTING deduplicated2 14:42:12 requestIdperm_20261001_03 resultDENIED 14:43:01 requestIdperm_20261001_03 sourceFOREGROUND_REFRESH resultGRANTED这组日志能回答审核人员最关心的工程事实请求由扫描讲义功能触发用途说明先于系统请求并发入口被合并用户拒绝后没有自动重弹从设置返回后只做状态查询。它不能替代隐私政策、页面录屏或第三方 SDK 清单却能让代码行为与提交材料相互校验。手机运行图展示的是设置返回后的最终状态请求perm_20261001_03已授权页面显示“相机权限已就绪”扫描入口恢复可用顶部状态栏保留时间、5G、Wi‑Fi、信号和电量。页面下方还有“拒绝后未自动重弹”和“前台刷新通过”两条验收结果而不是只放一个绿色对勾。六、上架前我会按这条链路再走一遍第一遍测全新安装。打开应用时不应出现相机请求进入 Permission Gate Lab 也不应自动申请只有点击“扫描讲义”并确认用途后系统弹窗才出现。第二遍测连续操作。快速点击三个入口页面只出现一层说明、一次系统请求和一个请求编号。旋转窗口、切后台再回来不新增请求。第三遍测拒绝。拒绝后扫描功能不可用但手动录入、查看历史笔记等不需要相机的功能仍可进入。页面解释如何重新授权不使用误导文案催促用户。第四遍测设置返回。用户在设置中开启相机权限回到应用后通过checkAccessToken()刷新为GRANTED不再拉起申请弹窗。关闭权限后返回也应立刻回到DENIED不能继续沿用缓存结果启动相机。第五遍核对材料。module.json5的 reason、页面说明、隐私政策、第三方 SDK 清单和实际网络行为要一致。如果 SDK 会采集个人信息应逐一披露目的、方式和范围不使用的权限从清单和代码中一起删除。七、把授权结果和业务可用性分开这次修改以后我不再用一个hasPermission控制整个页面。授权结果只决定扫描器能否启动页面其余能力仍按各自条件工作。这样用户拒绝相机权限时不会被挡在应用首页也不会陷入“授权才能退出说明页”的死路。更重要的是状态机让每个动作都有来源EXPLAINING是业务说明REQUESTING是系统交互DENIED是当前授权事实GRANTED只是允许进入扫描准备阶段并不代表应用可以随意处理后续图片。权限、隐私同意和业务数据处理不是同一个开关。一个权限弹窗能否正常出现只能证明 API 被调用了。能够经受上架检查的实现还要证明它没有提前调用、没有重复调用、拒绝后有替代路径、重新授权后能正确刷新并且代码行为与提交材料一致。把这些证据留在工程里比临近提审时人工点一遍更可靠。参考资料华为开发者联盟提交 HarmonyOS 应用与鸿蒙 APP华为开发者权限申请合规指南OpenHarmony 文档abilityAccessCtrl 程序访问控制管理
上一篇/下一篇内容由系统自动关联
返回资讯列表 →