256 个字,没有申诉入口——鸿蒙受限权限被驳回之后
灵犀申请了两个鸿蒙受限权限,READ_PASTEBOARD 过了,READ_IMAGEVIDEO 被驳回:「场景不符合权限使用要求」。复盘下来驳回是合理的——功能明明是备份,所有文案却写着「浏览」。这篇讲重新申请的全过程:按审核的判断顺序花掉 256 个字、正面回应 PhotoViewPicker 的替代建议、以及最重要的一条——申请材料里的每句承诺,代码里都得真的兑现。
灵犀 Lynsi 的鸿蒙端要用到两个受限权限(restricted permission)——这类权限不是在 module.json5 里声明了就能用,要先向华为提交使用场景申请,批下来才能进发布 Profile 的 ACL 名单。2026-08-19 收到审批结果:剪贴板的 READ_PASTEBOARD 通过,相册的 READ_IMAGEVIDEO 驳回,理由一句话:「场景不符合权限使用要求」。
这篇是驳回之后的完整复盘。先说结论:驳回是合理的,问题出在我自己的文案上。
通过的那个也有坑:Profile 是快照
先记一个容易踩的坑。READ_PASTEBOARD 批下来了,但手里的发布 Profile(.p7b)是权限获批之前生成的,它的 allowed-acls 字段是空的。Profile 不会因为权限后来批了而自动更新——不去 AGC 重新生成一份,release 包里的剪贴板同步照样拿不到权限,而且是上架之后才会暴露的那种拿不到。
验证手里的 Profile 到底带了哪些 ACL,不用装任何工具,.p7b 就是个 PKCS#7:
openssl smime -verify -inform DER -in lynsi-release.p7b -noverify 2>/dev/null \
| python3 -c "import sys,json; raw=sys.stdin.read(); \
d=json.loads(raw[raw.find('{'):]); print(d.get('acls'))"
**教训:权限审批和 Profile 是两个独立的状态,批了 ≠ 包里有。**出包前验一遍 allowed-acls,一行命令的事。
驳回为什么是合理的
华为对 READ_IMAGEVIDEO 的口径写得很清楚:需要从用户公共目录克隆、备份、同步图片视频文件的应用方可申请。
灵犀的相册功能实质上就是备份:在 Mac 上翻手机相册、把选中的原图原视频经局域网加密链路拉到 Mac 本地保存。但看一眼当时提交的所有文案——权限用途说明写「在已配对的 Mac 上浏览和导出您的照片」,相册开关的说明写「可以浏览照片」,权限失败的提示写「Mac 无法浏览照片」。
「浏览照片」读起来是什么?是一个看图应用。而看图应用正是华为明确不给这个权限的类型——系统提供了 PhotoViewPicker,看图选图不需要整库读权限。**审核员按你写的字面归类,不按你心里想的功能归类。**驳回合理。
修正从文案开始:中英文的 string.json 全部改成备份口径。这一步必须先做——重新申请后审核方会实机装包验证,装上看到的还是「浏览」,改了申请表也没用。文案、代码、申请材料三者要在同一个版本里对齐。
256 个字怎么花
重新申请的表单只给 256 个字,而且这一轮已经确认没有申诉入口——驳回后唯一的动作就是再提交一次。这 256 个字就是全部的沟通带宽。
所以写法完全围绕「审核最先判断什么」来组织:
- 第一句直接落进华为认可的场景词:「本应用属备份场景:将 HarmonyOS 手机相册的照片、视频备份到用户本人的 Mac。」不铺垫,不讲产品愿景,先让归类正确。
- 第二段正面回应上次的驳回理由。上一轮审核建议用
PhotoViewPicker,那这段就必须说清它为什么支撑不了备份:它只返回用户当次手动勾选的 URI,应用无从得知全集——没有全集就没有「哪些是新增」,做不了增量备份,也做不了整册归档;而且它要求用户拿起手机在小屏上逐张点选,与「人坐在 Mac 前发起归档」的场景方向相反。 - 收尾给用户控制权:手机端有相册共享总开关,关闭即对所有相册请求不予答复。
- 功能细节一个不写——审核会实机验证,表格里的字要花在「归类」和「反驳」上。
最终稿 249 字,留 7 字缓冲;另备一版删掉中英文间空格的 236 字稿,防平台把空格算进限额。
申请材料里的每句承诺,代码都得兑现
这部分是整篇的核心。256 字里有两句话,写下去之前都得先让代码配得上它们。
第一句:「读取均由用户在 Mac 端主动发起,无后台扫描。」而灵犀有个截图直达功能——ScreenshotWatcher 用 registerChange 订阅整个媒体库的变更,这就是后台扫描。两句话不能同时为真。当时的选择是:功能保留、随首版上架,但在 AGC 的审核备注里主动披露——把它列进测试步骤,单独说明这是同一权限下的可选子功能、默认关闭、开启后才订阅变更。支撑这个决定的事实:默认值确实是 false,开关上还叠着一层 Pro 门控,回调里只对文件名以 Screenshot 开头的新增项做出反应。主动写出来和被查出来,在审核那里是两件事;若仍被质询,退路是删掉设置页里那一行开关重新提交,功能代码留着,日后补一次场景说明再加回来。
第二句:「关闭开关即对所有相册请求不予答复,Mac 端无从绕过。」写这句之前,它是假的。相册开关的检查做在 PhotoRequestService 里,但原图拉取的 onPhotoFetchRequest 绕过了这个服务直接发文件——开关关了,缩略图确实没了,原图却照样取得到。这个洞在提交申请前修掉了。驳回信里明写:上架时应用市场会按实际使用的场景复审。申请材料里的承诺被实机验证戳穿,比第一次驳回严重得多。
顺带一个鸿蒙 API 的坑,也是「承诺要在代码里兑现」的另一面:registerChange 在没有相册权限时静默成功——不抛异常,不返回错误,然后媒体库永远不发一条通知。失败在两端都不可见。所以代码里不假设、自己查:注册前用 checkAccessToken 验一遍权限,没有就在日志里写明「媒体库永远不会通知」,让这个不可见的失败至少有个名字。
顺带:演示视频的一个镜头设计
重新申请要配演示视频,其中「关闭相册共享开关」这一镜有个反直觉的细节:要用「浏览」演示,不要用「下载」。开关一关,相册列表和缩略图是当场变空的,镜头里一眼可见;而被拒绝的原图拉取,是靠 Mac 侧 20 秒超时才报错的——镜头会对着一个没有任何反应的界面空等 20 秒,看起来不像「保护生效了」,像「功能没生效」。
同一个安全行为,选错观察面,演示效果完全相反。
学到什么
- **权限申请是场景写作。**审核读的是你的文案,不是你的代码;「浏览」和「备份」在工程师眼里是同一个功能的两个描述,在审核的分类体系里是「不给」和「给」的区别。
- **但上架复审验证的是你的代码,不是你的文案。**申请里的每一句承诺——「无后台扫描」「无从绕过」——都要先在代码里成立。两头都得对齐,先改文案,再堵代码,最后才提交。
- **平台的静默失败要自己点名。**Profile 不随审批更新、
registerChange无权限时静默成功,这两个都是「不报错的不生效」。对付它们的办法一样:不假设,主动验证,把失败写进日志。
留言