news 2026/9/30 11:54:41

HarmonyOS 7 AbilityKit + AppGallery Connect:权限声明、运行时授权与隐私说明对账机制【鸿蒙心迹】

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HarmonyOS 7 AbilityKit + AppGallery Connect:权限声明、运行时授权与隐私说明对账机制【鸿蒙心迹】

这篇从一次“代码没问题,为什么上架前还是不放心”的整理开始。Demo 项目叫ReleaseGuard,目标不是模拟审核平台,而是在提交版本前把三份最容易漂移的信息重新对齐:包里声明了什么权限、代码实际在什么时候申请、AppGallery Connect 和隐私政策里又写了什么。

很多上架问题并不复杂,麻烦的是信息分散。module.json5在工程里,运行时权限申请散在页面和业务服务里,第三方 SDK 的数据处理说明又可能在另一份文档中。开发到后期一改需求,最常见的情况不是功能坏了,而是“代码已经不用了,声明还在”“声明加了,使用原因没同步”“SDK 换了版本,隐私政策还是旧描述”。

华为当前的应用发布指引明确要求,上架前要检查应用信息与包是否完整、运行是否稳定、隐私合规相关内容是否满足要求;对于检测到敏感隐私权限或受限权限的应用,AppGallery Connect 的发布页面还会要求配置对应隐私说明。部分服务的官方上架说明也明确提醒:集成第三方 SDK 时,需要在隐私政策中逐一说明 SDK 收集个人信息的目的、方式和范围。

我做 ReleaseGuard 的原因,就是不想等到提交之后才把这些信息重新拼起来。

一、我先把“审核前自检”缩成 7 个可回答的问题

ReleaseGuard 首页没有做很多花哨功能,只有一个PRECHECK 7/7 PASS。这 7 项不是平台官方固定清单,而是我在项目内部定义的工程门槛:

  1. 当前版本号和待提交包一致;
  2. 权限声明都能找到业务用途;
  3. 实际需要用户授权的权限有明确触发入口;
  4. 不再使用的权限已经从包声明中移除;
  5. 第三方 SDK 清单与隐私披露一致;
  6. AppGallery Connect 需要填写的隐私说明已经核对;
  7. Release Profile、包名和版本信息完成最终对账。

它最重要的价值不是“自动替代审核”,而是让研发团队在提交前有一份能落到代码和包的证据。

图 03 就是这份自检结果。这里最显眼的不是 CAMERA,而是 LOCATION:页面明确显示NOT DECLARED。因为当前业务已经不需要位置能力,所以正确状态不是“授权关闭”,而是压根不要把不需要的权限继续留在声明里。

这点特别容易混淆。权限治理不是把所有权限都申请一遍然后让用户拒绝,而是业务需要什么就声明什么,真正要访问用户隐私信息或系统敏感能力时,再在合适的业务时机请求授权。

二、声明、授权、实际功能,是三件不同的事

我把权限问题拆成三层以后,很多排查会变得很直接。

第一层是包声明。比如相机权限是否出现在module.json5的requestPermissions中,使用原因是否写清楚。

第二层是运行时状态。即使声明了 CAMERA,也不代表用户已经授权。应用仍要查询当前状态,并在真正需要拍摄时申请。

第三层是业务触发。ReleaseGuard 的相机权限只在用户点击“扫描证件”后申请,而不是一打开首页就弹窗。

这段代码解决什么问题:让包内权限声明本身就带上明确使用原因,避免后期只看到权限名,不知道是谁加的。

{ "module": { "name": "entry", "type": "entry", "requestPermissions": [ { "name": "ohos.permission.CAMERA", "reason": "$string:permission_camera_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } } ] } }

在这份 Demo 里,原先测试阶段加过 LOCATION,后来“扫描证件”流程不再依赖位置。我的处理不是在代码里永远不调用它,而是把声明一起删掉,再让自检工具把“未使用权限数量”统计为 0。

项目里最好给每个权限维护一个 owner。不是为了流程复杂化,而是需求下线时能快速知道这条声明应该由谁确认。如果所有权限都没人负责,版本越迭代,历史残留越多。

三、运行时授权不要写成启动仪式,应该贴着用户动作发生

HarmonyOS 的权限接口已经提供了查询自身权限状态和向用户请求授权的能力。工程里真正要决定的是“什么时候调用”。

ReleaseGuard 的逻辑是:用户进入首页不会出现授权弹框;点击“扫描证件”后,先查询 CAMERA 当前状态,如果尚未获得授权,再调用requestPermissionsFromUser()。

这段代码解决什么问题:把相机授权和“扫描证件”这个明确动作绑定,避免页面启动时无上下文弹权限。

import{abilityAccessCtrl,common,Permissions,PermissionRequestResult}from'@kit.AbilityKit';constCAMERA:Permissions='ohos.permission.CAMERA';asyncfunctionrequestCameraFromScanButton(context:common.UIAbilityContext):Promise<boolean>{constatManager=abilityAccessCtrl.createAtManager();constbefore=atManager.getSelfPermissionStatus(CAMERA);console.info(`[AUTH] before=${before}`);constresult:PermissionRequestResult=awaitatManager.requestPermissionsFromUser(context,[CAMERA]);constgranted=result.authResults.length>0&&result.authResults[0]===0;console.info(`[AUTH] request source=scan_button granted=${granted}`);returngranted;}

实际开发时,GrantStatus的判断最好使用对应枚举,而不是像演示代码一样依赖裸数字。这里把判断简化,是为了突出调用链:用户动作 → 查询状态 → 必要时请求 → 根据结果继续业务。

还要注意,用户拒绝并不是异常崩溃。用户不授权 CAMERA 时,ReleaseGuard 会停在“需要相机权限才能扫描”的说明页,同时提供手工录入入口。只有把“拒绝”当成一条正常产品路径,权限申请才不会变成强迫用户通过的闸门。

图 02 对应的就是这条调用链:中间代码标了requestPermissionsFromUser(),右侧模拟器显示相机已经声明,而 LOCATION 没有声明,底部日志能看到request source=scan_button和授权结果。

四、我写了一个轻量扫描器,专门找“声明了但项目里没人用”的权限

只靠人工搜代码很难长期稳定。我后来加了一个构建前脚本,思路很简单:读取module.json5的requestPermissions,再对src/main/ets中的权限常量和授权调用做扫描,得到一份“声明集合”和“引用集合”的差集。

它不会百分之百理解业务,也不负责判断某个权限是否法律合规,但特别适合抓两类低级问题:

  • 权限已经不用,声明还留着;
  • 新代码开始申请某个权限,但包声明没同步。

这段代码解决什么问题:在提交包之前,用脚本提前发现权限声明和代码引用的明显漂移。

importfsfrom'node:fs';importpathfrom'node:path';importJSON5from'json5';constmodulePath=path.resolve('entry/src/main/module.json5');constmoduleConfig=JSON5.parse(fs.readFileSync(modulePath,'utf-8'));constdeclared=newSet<string>((moduleConfig.module.requestPermissions??[]).map((item:{name:string})=>item.name));constetsRoot=path.resolve('entry/src/main/ets');constsourceText=collectEtsText(etsRoot);constreferenced=newSet<string>();for(constpermissionofdeclared){if(sourceText.includes(permission)){referenced.add(permission);}}constunused=[...declared].filter(item=>!referenced.has(item));console.log(`[PREFLIGHT] declared=${declared.size}`);console.log(`[PREFLIGHT] unusedPermission=${unused.length}`);console.log(`[PREFLIGHT] unused=${unused.join(',')||'NONE'}`);

这里的collectEtsText()可以自己递归目录实现,也可以放到现有工程脚本里。更重要的是理解它的边界:如果权限名由三方库内部使用、动态拼接、Native 层引用,简单字符串搜索可能误判。所以这个工具输出的是“需要人工复核”,不是“自动删权限”。

我的做法是:脚本发现差异就让 CI 给出 warning;准备 Release 包时,如果仍存在未解释差异,再升级为阻断。这样平时开发不会太重,上架前又有一道明确门槛。

五、第三方 SDK 最容易漏的不是代码,而是说明文档版本

很多项目权限已经收得很干净,最后还是会在第三方 SDK 这里出现信息不一致。

原因很现实:SDK 升级通常发生在依赖文件里,隐私政策却可能由产品、运营或法务维护。研发把 SDK 从 1.x 升到 2.x 后,如果数据处理范围发生变化,隐私政策不一定有人同步改。

所以 ReleaseGuard 的 SDK 对账不扫描“有没有三方库”这么简单,而是维护一份发布清单:

SDK 名称 / 当前版本 / 使用目的 / 涉及数据 / 隐私声明链接 / 本版本是否变化

华为部分服务的上架说明明确提示:集成第三方 SDK 的应用,需要在隐私政策中逐一明示 SDK 收集个人信息的目的、方式和范围。我的工程做法是把这条要求落成一个可检查字段:2 项 SDK,2/2 已披露。

这也解释了为什么图 03 中第三方 SDK 卡片不是“检测到 2 项”就结束,而是继续显示“隐私披露 2/2 完成”。只发现依赖,不确认说明,依然没有完成对账。

六、AppGallery Connect 的隐私说明,不能用“代码里有 reason”替代

这是我最想强调的一点。

module.json5里的reason解决的是包内权限使用原因;AppGallery Connect 发布页面里的隐私说明,是上架流程的一部分;应用自己的隐私政策又是面向用户的公开说明。三者有关联,却不是同一个字段的三个副本。

当前 AppGallery Connect 文档说明,如果应用包被检测到获取敏感隐私权限或使用受限权限,需要在发布流程里配置对应隐私说明;其中受限权限还可能需要额外的使用场景材料。具体哪些字段需要填写,应以当前上传包扫描结果和发布页面提示为准。

所以 ReleaseGuard 不会自作主张地说“有 CAMERA 就一定审核失败”,而是做两件事:

  1. 告诉你当前包有哪些需要重点关注的权限;
  2. 记录本版本是否已经完成 AppGallery Connect 对应说明的人工确认。

这种设计虽然不“全自动”,却更可靠。因为上架规则、页面字段和权限分类可能变化,工程工具不应该把平台规则永久硬编码成一个不会更新的 if/else。

七、版本号、包名和 Release Profile,要和权限一起看

有一次我排查权限问题查了很久,最后发现上传的根本不是刚才测试的那个包。开发环境、测试包、Release 包同时存在时,这种低级错误一点也不少见。

因此 ReleaseGuard 最后的对账页会把下面几项固定显示出来:

  • 应用版本1.6.0 (106);
  • 包名com.example.releaseguard;
  • 当前构建类型Release;
  • Release Profile 校验MATCH;
  • 敏感隐私权限1 项(CAMERA);
  • 受限权限0 项;
  • 未使用权限0 项;
  • 第三方 SDK 信息披露2/2 MATCH;
  • 隐私说明CONFIGURED。

图 04 就是这张“提交前最后看一眼”的页面。红圈标出来的不是为了好看,而是两个最容易出现历史残留的位置:未使用权限和隐私说明状态。

这里的7/7 PASS只表示项目内部预检通过,并不代表华为应用市场已经审核通过。文章和工具都应该把这个边界说清楚,否则内部自检很容易被误解成平台审核结论。

八、权限问题最有效的排查方式,是沿着一条证据链往回找

当测试同学说“系统设置里看不到这个权限”,或者审核前发现权限说明对不上,我现在不会先翻 UI 页面,而是按固定顺序检查:

第一步:看包声明。module.json5有没有这个权限?reason 和 usedScene 是否仍符合当前业务?

第二步:看运行时调用。有没有真正调用requestPermissionsFromUser()?调用入口是什么?用户拒绝后怎么处理?

第三步:看日志。过滤权限请求日志,确认请求发生在预期操作之后,而不是应用启动就发生。

第四步:看发布配置。上传的是不是当前 Release 包?版本号、包名、签名配置是否一致?AppGallery Connect 是否扫描到需要填写的隐私说明?

第五步:看隐私政策与 SDK 清单。实际集成内容和公开说明是否仍是同一个版本。

这条链路最大的好处是不用猜。每一层都有明确证据,问题停在哪一层,就解决哪一层。

九、我更愿意把上架合规当成持续构建问题,而不是发布当天的文档问题

上架审核最难处理的情况,是所有人都等到发布当天才集中检查。那时任何一个字段不一致,都会变成“谁最后改过”的追责题。

ReleaseGuard 最终没有做成一个庞大的审核模拟器,而是变成三块很小的能力:

  • 开发阶段:权限和调用差异给 warning;
  • Release 构建阶段:版本、权限、SDK 清单生成报告;
  • 提交前:人工确认 AppGallery Connect 隐私说明和公开隐私政策。

真正能自动化的就自动化,必须依赖平台当前页面和人工判断的就保留人工确认。这样比假装所有规则都能被脚本准确判断更稳。

如果后续项目继续变大,我还会把相机、定位、通讯录等权限按业务模块归属,把三方 SDK 版本变更加入 Pull Request 模板,再把 ReleaseGuard 的报告作为发布附件保存。这样半年后回头看某个版本,也能知道当时为什么申请某个权限、由哪个功能触发、隐私说明是否同步。

十、我专门保留了一组“拒绝授权”测试,不让 PASS 只建立在顺利路径上

权限功能最容易出现一种假稳定:开发者自己的设备已经长期授权,于是每次测试都从GRANTED开始。代码看起来非常顺,直到新用户第一次安装,或者用户在系统设置里撤回权限,真正的问题才出现。

ReleaseGuard 因此固定跑三种状态:未决定、已授权、已拒绝。未决定状态下点击“扫描证件”,应该出现系统授权流程;已授权状态下再次点击,不应重复制造无意义请求;已拒绝状态下,页面应该解释为什么功能受限,并给出可继续使用的替代路径,而不是无限重复弹窗。

我把这三种情况写进测试记录:

NOT_DETERMINED -> 用户点击扫描 -> 请求 CAMERA -> 依据用户选择继续 GRANTED -> 用户点击扫描 -> 直接进入拍摄流程 DENIED -> 用户点击扫描 -> 展示说明 / 可用替代方式 / 必要时引导设置

这里尤其要避免“用户拒绝一次就马上再弹一次”。从产品体验看,这会让授权变成强制拦截;从排查角度看,也会让日志里充满重复请求,反而难以判断真正的触发来源。

测试时我还会主动从系统设置里撤回 CAMERA,再回到应用继续操作。这个动作能验证两个细节:应用是否在每次关键操作前重新确认状态,以及 UI 是否会把之前的“已授权”缓存成永久状态。权限属于可变状态,不能只在应用首次启动时读一次。

1. “设置页里能看到权限”也不是最终验收目标

官方权限 FAQ 提到,不同 API 阶段系统设置页面对权限展示行为存在差异,排查时应该回到module.json5声明和requestPermissionsFromUser()的真实调用。对项目来说,更可靠的验收不是“设置页出现某个开关”,而是“功能触发、系统授权状态、业务降级逻辑三者一致”。

因此 ReleaseGuard 的报告不会简单把“系统设置有开关”记成 PASS,而是保留请求来源scan_button,让测试人员知道这次授权是在哪个动作里产生的。

十一、CI 报告最好能被人读懂,而不是只返回一个 0 或 1

做成脚本以后,我一开始只让命令返回成功或失败。很快就发现这对排查不够友好。CI 红了以后,开发者还得重新下载日志,自己猜是哪一项没过。

后来输出改成结构化摘要:

[PACKAGE] version=1.6.0(106) bundle=com.example.releaseguard [PERMISSION] declared=1 referenced=1 unused=0 [SDK] detected=2 disclosed=2 [PRIVACY] appgallery=confirmed policy=confirmed [PROFILE] releaseProfile=MATCH [PREFLIGHT] PASS 7/7

如果失败,则必须写具体原因,例如:

[PREFLIGHT] FAIL - unused permission: ohos.permission.LOCATION - sdk disclosure missing: analytics-sdk 2.4.1

这种输出还有一个额外价值:它很适合作为 Release 构建产物的一部分归档。将来某个版本出现争议,不用依赖谁的记忆,直接看当时提交前生成的报告就知道包里是什么状态。

当然,这份报告仍然不能替代平台审核。它只是把团队能控制的工程信息做成可重复检查,让“提交前我应该看什么”从个人经验变成项目流程。

十二、隐私政策不是静态附件,最容易在版本迭代里落后半拍

我以前把隐私政策理解成“上线前准备一次”,后来发现这几乎注定会过期。真正的应用会不断加登录方式、统计 SDK、地图、支付、相机、文件选择等能力,每一次依赖或业务变化都可能让原来的说明失去准确性。

所以我把隐私政策当成版本配置的一部分。每次 SDK 版本、权限声明或数据处理流程变化时,都要求 PR 描述里回答一个问题:这次变化是否需要同步更新隐私说明?

如果答案是“否”,也要说明为什么。这样做会多几十秒,却能避免几个月后没人知道某条隐私描述对应的是哪个历史实现。

对于系统 Picker 这类能够减少额外权限申请的能力,也应该在设计阶段优先考虑。能通过系统提供的受控选择流程完成目标,就没有必要为了“代码方便”扩大长期权限范围。权限越少,业务边界越清晰,后续测试和上架材料也更容易维护。

十三、最后一次人工复核,我只看四种“不一致”

自动化脚本跑完后,我仍然会留一个人工确认步骤,但不会让大家重新通读所有文件,而是专门找四种不一致:

  • 代码与声明不一致:代码要用,包没声明;或者包声明了,代码已经不用;
  • 声明与触发不一致:权限用途写的是扫描证件,实际却在首页启动就申请;
  • 依赖与隐私政策不一致:SDK 已升级或新增,公开说明仍是旧版本;
  • 测试包与提交包不一致:开发机验证的是 Debug 包,真正上传的是另一个 Release 构建。

这四类问题一旦排除,剩下的审核工作就更接近平台规则本身,而不是项目内部信息漂移造成的返工。

十四、这套对账机制解决的不是“怎么过审”,而是“怎么让提交材料和真实应用保持一致”

应用审核不是一个可以靠技巧绕过的流程。真正能降低返工的,是让提交信息准确反映当前版本的真实行为。

这篇里我反复强调“对账”,原因就在这里:

声明要和代码对上,代码要和用户动作对上,用户动作要和隐私说明对上,隐私说明又要和当前上传包对上。

ReleaseGuard 的价值不是替开发者判断平台会不会通过,而是在提交前把明显的不一致暴露出来。对一个长期迭代的 HarmonyOS 项目来说,这比临发布时翻几十个文件、逐项回忆“这个权限还有没有用”靠谱得多。

参考资料

  • 华为开发者:提交 HarmonyOS 应用与鸿蒙 APP
  • 华为开发者:上架审核相关社区话题
  • 华为开发者:AppGallery Review Guidelines / Configuring a Privacy Description
  • 华为开发者:应用权限管理 FAQ(getSelfPermissionStatus、requestPermissionsFromUser)
  • 华为开发者服务上架说明:第三方 SDK 隐私披露要求
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 11:54:11

Claude Code实战:一小时内从零写出贪吃蛇游戏

1. 挑战本身&#xff1a;为什么非得是一小时&#xff0c;为什么非得是贪吃蛇 直接用 Claude Code 写个贪吃蛇&#xff0c;这事听起来不难。任何写过游戏的人都知道贪吃蛇的代码量撑死几百行&#xff0c;逻辑也不复杂&#xff0c;无非就是蛇身移动、食物随机生成、撞墙撞自己判定…

作者头像 李华
网站建设 2026/9/30 11:54:08

银河麒麟V11正式发布:内核升级、安装迁移与实操指南

银河麒麟V11正式开放下载的消息&#xff0c;这几天在圈子里算是炸开了锅。等了这么久&#xff0c;官网终于把安装镜像挂出来了。我第一时间下载了桌面版和服务器版&#xff0c;在物理机和虚拟机里分别折腾了一遍&#xff0c;也把之前V10上踩过的一些坑重新验证了一下。这篇文章…

作者头像 李华
网站建设 2026/9/30 11:52:55

智星云镜像共享全指南:让AI团队环境配置从一星期到半小时

团队里五六个小伙伴&#xff0c;每人一台机器&#xff0c;光是配环境就花了一个星期。有人在Windows上折腾CUDA&#xff0c;有人在Linux下编译PyTorch&#xff0c;版本对不上&#xff0c;代码跑出来的结果都不一样。后来我把智星云上的镜像共享给了所有人&#xff0c;整个流程从…

作者头像 李华
网站建设 2026/9/30 11:52:50

uni-app微信小程序登录页开发:视觉细节与登录状态机闭环

1. 从标题说开&#xff1a;这个登录页到底要解决什么问题做 uni-app 微信小程序的这几年&#xff0c;我发现一个挺有意思的现象&#xff1a;技术群里问得最多的往往不是复杂业务逻辑&#xff0c;而是"登录页怎么做才好看"。这看起来是个小问题&#xff0c;但真动手写…

作者头像 李华
网站建设 2026/9/30 11:52:50

手写Tomcat线程池改造:从每请求一线程到高并发可控

手写Tomcat绕不开的坎&#xff0c;就是线程池的引入。我自己写简易Servlet容器的时候&#xff0c;最开始根本没想这么深&#xff1a;一个ServerSocket循环&#xff0c;accept()之后直接new Thread处理&#xff0c;逻辑上特别通顺&#xff0c;压测到二三十并发也感觉还不错。直到…

作者头像 李华
网站建设 2026/9/30 11:52:48

基于PHP的微信AI智能客服系统:从部署到二次开发全攻略

做微信客服系统这块快六年&#xff0c;见过太多“能跑就行”的代码&#xff0c;这次整理仓库翻出一套PHP原创的微信AI智能客服系统源码&#xff0c;结构清爽&#xff0c;扩展点留得清楚&#xff0c;适合做二次开发的伙伴直接拿去改。它能解决的问题很具体&#xff1a;让微信公众…

作者头像 李华