适用版本 :HarmonyOS NEXT / HarmonyOS 7,DevEco Studio 7.0(AppAnalyzer),AppGallery Connect(AGC)
写给谁看 :应用功能已经跑通、第一次走鸿蒙上架流程的开发者。
要解决的问题:驳回理由里"权限与隐私"常年高居榜首。本文把"从代码到上架"的合规动作拆成可执行项,并给出免权限的替代方案。
目录
-
- 一、为什么上架才是真正的"验收"
- 二、权限最小化:能不申请就不申请
-
- [2.1 最容易被"顺手申请"的是存储权限](#2.1 最容易被"顺手申请"的是存储权限)
- 三、数据本地化:数据不出设备
- 四、AppAnalyzer:把上架问题提前暴露
- 五、常见驳回原因自查表
- 六、真实界面(上架形态)
- 七、适用边界与限制
- 八、小结
- 参考
一、为什么上架才是真正的"验收"
应用开发完只是走了一半。上架这一半才决定你的应用能否被用户看到,而驳回理由里,权限与隐私合规常年高居榜首。
本文以一个真实的本地化专注类应用为例。它的核心特征只有一句话:整包只声明 2 项权限、零联网。整条链路可以概括为六步:

代码与资源检查 → 权限最小化与隐私声明 → AppAnalyzer 上架体检 → 签名打包 → 提交 AGC 审核 → 上架与版本迭代。
下面挑最容易驳回的三步展开,每一步都给可核对的代码与验证方式。
二、权限最小化:能不申请就不申请
隐私合规的第一性原则是最小必要 。以这个专注类应用为例,它在 module.json5 里声明的权限克制到只有两项:
json5
// entry/src/main/module.json5
{
"module": {
"name": "entry",
"requestPermissions": [
{
"name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
"reason": "$string:reason_keep_running", // 必须提供可展示的用途说明
"usedScene": {
"abilities": ["EntryAbility"],
"when": "always"
}
},
{
"name": "ohos.permission.NOTIFICATION_CONTROLLER",
"reason": "$string:reason_notification",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
}
配套的用途说明要写在资源文件里,审核会逐条核对"声明与行为是否一致":
json5
// entry/src/main/resources/base/element/string.json
{
"string": [
{
"name": "reason_keep_running",
"value": "用于在锁屏或退到后台时保持计时准确,仅在计时任务进行中使用"
},
{
"name": "reason_notification",
"value": "用于在通知栏展示当前计时剩余时间,便于用户随时查看进度"
}
]
}
2.1 最容易被"顺手申请"的是存储权限
很多应用习惯性申请读写用户存储的权限,然后在审核时被追问"为什么要读我整个相册"。这个应用把这类需求彻底绕开了:
- 保存文件 走
DocumentViewPicker:用户自己选目录并确认,用时授权、用完即弃,应用不持有任何静态存储权限; - 分享文件 走
systemShare系统分享面板:写入应用沙箱后把 URI 交给系统面板,应用从不下场碰用户文件。
typescript
// services/ReportExportService.ets ------ 保存:不需要任何静态存储权限
import { picker } from '@kit.CoreFileKit';
import { fileIo } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
export async function exportReport(
ctx: common.UIAbilityContext,
markdown: string,
fileName: string = 'focus-weekly-report.md'
): Promise<boolean> {
try {
const options = new picker.DocumentSaveOptions();
options.newFileNames = [fileName];
const documentPicker = new picker.DocumentViewPicker(ctx);
const uris = await documentPicker.save(options);
if (!uris || uris.length === 0) {
// 用户点了取消:这是正常路径,不要弹"导出失败"
return false;
}
const file = fileIo.openSync(uris[0], fileIo.OpenMode.READ_WRITE);
fileIo.writeSync(file.fd, markdown);
fileIo.closeSync(file);
return true;
} catch (err) {
// 部分版本会以错误码形式表达取消,同样按"非失败"处理
const e = err as BusinessError;
console.warn(`export canceled or failed: ${e.code} ${e.message}`);
return false;
}
}
typescript
// services/ShareService.ets ------ 分享:交给系统面板,应用不接触第三方
import { systemShare } from '@kit.ShareKit';
import { uniformTypeDescriptor as utd } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
export function shareText(ctx: common.UIAbilityContext, text: string): void {
const data = new systemShare.SharedData({
utd: utd.UniformDataType.PLAIN_TEXT,
content: text
});
const controller = new systemShare.ShareController(data);
controller.show(ctx, {
selectionMode: systemShare.SelectionMode.SINGLE,
previewMode: systemShare.SharePreviewMode.DETAIL
});
}
✅ 收益 :不需要
READ_USER_STORAGE这类静态权限,也就没有了"为什么读我整个存储"的审核追问。这是上架前最省事的一处架构决策。
三、数据本地化:数据不出设备
权限之外,"数据怎么存、怎么传"同样是审核关注点。这个应用的做法是全程本地化:
typescript
// services/historyStore.ets ------ 本地关系型数据库 + 参数化谓词
import { relationalStore } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
const STORE_CONFIG: relationalStore.StoreConfig = {
name: 'focus_history.db',
securityLevel: relationalStore.SecurityLevel.S1 // 本地最小安全等级,不上云
};
export async function querySessions(
ctx: common.UIAbilityContext,
dayStart: number,
dayEnd: number
): Promise<relationalStore.ResultSet> {
const store = await relationalStore.getRdbStore(ctx, STORE_CONFIG);
// 参数化谓词:不要拼接 SQL 字符串
const predicates = new relationalStore.RdbPredicates('session');
predicates.greaterThanOrEqualTo('start_time', dayStart);
predicates.lessThan('start_time', dayEnd);
predicates.orderByAsc('start_time');
return store.query(predicates, ['id', 'start_time', 'end_time', 'mode']);
}
三条纪律:
- 本地存储 :会话历史存关系型数据库(
relationalStore),参数化谓词查询,避免拼接注入; - 零联网:不声明任何网络权限,数据不出设备;
- 导出可控 :导出内容仅包含会话记录数组,不含任何个人标识,且是否导出完全由用户发起。
这套"本地算、本地存、按需导出"的模式,让隐私声明可以写得极其干净------没有需要向第三方共享的数据,也就没有复杂的数据流转要解释。
⚠️ 风险提示 :如果后续为了统计或云备份引入了任何网络权限,隐私声明必须同步更新实际采集字段。声明与实际行为不一致是最高频的驳回理由之一,且属于主观违规而非技术缺陷,申辩空间很小。
四、AppAnalyzer:把上架问题提前暴露
DevEco Studio 提供的 AppAnalyzer 会在开发阶段发现可能影响上架的兼容性、性能、功耗、稳定性问题,并支持场景化检测。把它放进提审前的固定动作里,能把大量"提交后才知道"的问题变成"提交前已修复"。
原则很简单:别让审核方替你发现你自己的 bug。
打包前先做一次干净构建,确认产物可复现:
bash
# 1) 干净构建并产出签名 HAP
hvigorw clean --no-daemon
hvigorw assembleHap --mode module -p product=default --no-daemon
# 2) 确认产物存在(路径以工程结构为准)
ls -lh entry/build/default/outputs/default/*.hap
预期输出:
text
> hvigor Finished :entry:default@CompileArkTS...
> hvigor Finished :entry:default@PackageHap...
BUILD SUCCESSFUL in 24s
-rw-r--r-- 1 user staff 3.2M entry-default-signed.hap
产物体积本身也是一项可核对指标:如果明显大于预期,通常意味着打进了无用资源或重复依赖。
五、常见驳回原因自查表
提审前对着这张表逐条过,能挡掉绝大多数低级驳回:
| 检查项 | 自查要点 | 对应动作 |
|---|---|---|
| 权限必要性 | 每项权限能否说明具体用途?有无可替代的免权限方案? | 逐条核对 requestPermissions |
| 隐私声明 | 是否与实际采集行为一致?有无超范围采集? | 对照 string.json 用途文案 |
| 网络行为 | 是否声明了非必要的网络权限? | 检查 ohos.permission.INTERNET |
| 数据流向 | 数据是否出设备?若出,是否明示且最小化? | 检查数据库与上传逻辑 |
| 兼容 / 性能 | 是否有 AppAnalyzer 未处理的告警? | DevEco Studio 内运行 AppAnalyzer |
| 签名与包 | 签名配置是否正确,包名与 AGC 一致? | 核对 AGC 应用信息与签名证书 |
六、真实界面(上架形态)
下面两张图来自该应用的统计与多屏形态,均为本地数据驱动、无任何联网请求:


七、适用边界与限制
- 仅 2 项权限是结果,不是目标 。如果你的业务确实需要定位或相机,正确做法是逐项说明用途,而不是硬删权限------删掉必要权限导致功能不可用,同样会被驳回。
- 本文方案适用于"数据不需要出设备"的本地类应用。涉及云端同步、账号体系的应用,需要额外准备数据流转说明与第三方共享清单。
- AppAnalyzer 覆盖的是技术类问题,不替代人工的合规自查。权限与隐私声明的语义正确性仍需人工确认。
八、小结
回到开头:上架不是开发完成后的"补作业",而是从第一行代码就要做的架构决策 。能靠系统能力(选择器、系统分享面板)解决的,就别申请权限;能留在本地的,就别上云。
当你的权限清单只剩两三项、隐私声明简洁到一页说完,上架这件事就从一个"拦路虎"变成了一个"走过场"。你在上架时被哪条理由驳回过?欢迎在评论区交流,我会整理成后续的避坑清单。
参考
- 应用上架全流程实战(官方社区话题):https://developer.huawei.com/consumer/cn/forum/topic/0201218124295377819
- HarmonyOS 开发助手(含上架前后提效能力):https://developer.huawei.com/consumer/cn/doc/start/hosdevassistant-intro-0000002588328126
- 本次征文活动页面:https://harmonyosdev.csdn.net/6ad1cde9536f457592b05595.html