这篇从一次“代码没问题为什么上架前还是不放心”的整理开始。Demo 项目叫ReleaseGuard目标不是模拟审核平台而是在提交版本前把三份最容易漂移的信息重新对齐包里声明了什么权限、代码实际在什么时候申请、AppGallery Connect 和隐私政策里又写了什么。很多上架问题并不复杂麻烦的是信息分散。module.json5在工程里运行时权限申请散在页面和业务服务里第三方 SDK 的数据处理说明又可能在另一份文档中。开发到后期一改需求最常见的情况不是功能坏了而是“代码已经不用了声明还在”“声明加了使用原因没同步”“SDK 换了版本隐私政策还是旧描述”。华为当前的应用发布指引明确要求上架前要检查应用信息与包是否完整、运行是否稳定、隐私合规相关内容是否满足要求对于检测到敏感隐私权限或受限权限的应用AppGallery Connect 的发布页面还会要求配置对应隐私说明。部分服务的官方上架说明也明确提醒集成第三方 SDK 时需要在隐私政策中逐一说明 SDK 收集个人信息的目的、方式和范围。我做 ReleaseGuard 的原因就是不想等到提交之后才把这些信息重新拼起来。一、我先把“审核前自检”缩成 7 个可回答的问题ReleaseGuard 首页没有做很多花哨功能只有一个PRECHECK 7/7 PASS。这 7 项不是平台官方固定清单而是我在项目内部定义的工程门槛当前版本号和待提交包一致权限声明都能找到业务用途实际需要用户授权的权限有明确触发入口不再使用的权限已经从包声明中移除第三方 SDK 清单与隐私披露一致AppGallery Connect 需要填写的隐私说明已经核对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}fromkit.AbilityKit;constCAMERA:Permissionsohos.permission.CAMERA;asyncfunctionrequestCameraFromScanButton(context:common.UIAbilityContext):Promiseboolean{constatManagerabilityAccessCtrl.createAtManager();constbeforeatManager.getSelfPermissionStatus(CAMERA);console.info([AUTH] before${before});constresult:PermissionRequestResultawaitatManager.requestPermissionsFromUser(context,[CAMERA]);constgrantedresult.authResults.length0result.authResults[0]0;console.info([AUTH] request sourcescan_button granted${granted});returngranted;}实际开发时GrantStatus的判断最好使用对应枚举而不是像演示代码一样依赖裸数字。这里把判断简化是为了突出调用链用户动作 → 查询状态 → 必要时请求 → 根据结果继续业务。还要注意用户拒绝并不是异常崩溃。用户不授权 CAMERA 时ReleaseGuard 会停在“需要相机权限才能扫描”的说明页同时提供手工录入入口。只有把“拒绝”当成一条正常产品路径权限申请才不会变成强迫用户通过的闸门。图 02 对应的就是这条调用链中间代码标了requestPermissionsFromUser()右侧模拟器显示相机已经声明而 LOCATION 没有声明底部日志能看到request sourcescan_button和授权结果。四、我写了一个轻量扫描器专门找“声明了但项目里没人用”的权限只靠人工搜代码很难长期稳定。我后来加了一个构建前脚本思路很简单读取module.json5的requestPermissions再对src/main/ets中的权限常量和授权调用做扫描得到一份“声明集合”和“引用集合”的差集。它不会百分之百理解业务也不负责判断某个权限是否法律合规但特别适合抓两类低级问题权限已经不用声明还留着新代码开始申请某个权限但包声明没同步。这段代码解决什么问题在提交包之前用脚本提前发现权限声明和代码引用的明显漂移。importfsfromnode:fs;importpathfromnode:path;importJSON5fromjson5;constmodulePathpath.resolve(entry/src/main/module.json5);constmoduleConfigJSON5.parse(fs.readFileSync(modulePath,utf-8));constdeclarednewSetstring((moduleConfig.module.requestPermissions??[]).map((item:{name:string})item.name));constetsRootpath.resolve(entry/src/main/ets);constsourceTextcollectEtsText(etsRoot);constreferencednewSetstring();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 项 SDK2/2 已披露。这也解释了为什么图 03 中第三方 SDK 卡片不是“检测到 2 项”就结束而是继续显示“隐私披露 2/2 完成”。只发现依赖不确认说明依然没有完成对账。六、AppGallery Connect 的隐私说明不能用“代码里有 reason”替代这是我最想强调的一点。module.json5里的reason解决的是包内权限使用原因AppGallery Connect 发布页面里的隐私说明是上架流程的一部分应用自己的隐私政策又是面向用户的公开说明。三者有关联却不是同一个字段的三个副本。当前 AppGallery Connect 文档说明如果应用包被检测到获取敏感隐私权限或使用受限权限需要在发布流程里配置对应隐私说明其中受限权限还可能需要额外的使用场景材料。具体哪些字段需要填写应以当前上传包扫描结果和发布页面提示为准。所以 ReleaseGuard 不会自作主张地说“有 CAMERA 就一定审核失败”而是做两件事告诉你当前包有哪些需要重点关注的权限记录本版本是否已经完成 AppGallery Connect 对应说明的人工确认。这种设计虽然不“全自动”却更可靠。因为上架规则、页面字段和权限分类可能变化工程工具不应该把平台规则永久硬编码成一个不会更新的 if/else。七、版本号、包名和 Release Profile要和权限一起看有一次我排查权限问题查了很久最后发现上传的根本不是刚才测试的那个包。开发环境、测试包、Release 包同时存在时这种低级错误一点也不少见。因此 ReleaseGuard 最后的对账页会把下面几项固定显示出来应用版本1.6.0 (106)包名com.example.releaseguard当前构建类型ReleaseRelease 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 最终没有做成一个庞大的审核模拟器而是变成三块很小的能力开发阶段权限和调用差异给 warningRelease 构建阶段版本、权限、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] version1.6.0(106) bundlecom.example.releaseguard [PERMISSION] declared1 referenced1 unused0 [SDK] detected2 disclosed2 [PRIVACY] appgalleryconfirmed policyconfirmed [PROFILE] releaseProfileMATCH [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华为开发者应用权限管理 FAQgetSelfPermissionStatus、requestPermissionsFromUser华为开发者服务上架说明第三方 SDK 隐私披露要求