HarmonyOS 上架审核闭环实战:权限、签名、测试与材料自检

很多应用不是功能做不出来,而是临近上架时被审核问题拖住:权限说明和实际场景对不上,签名配置混用了测试环境,截图材料不完整,弱网或拒绝权限场景一测就崩。

这篇文章按“提交前就把问题暴露出来”的思路整理一套 HarmonyOS 应用上架自检方法。文章不替代官方审核规则,而是给开发者一个工程化检查框架:从 module.json5、签名、功能回归、截图材料到提交前复盘,逐项收口。

请添加图片描述

1. 本文要解决的审核风险

风险典型表现提交前处理
权限不合理申请权限但页面看不到使用场景对照功能入口和权限说明
签名不一致本地能跑,上架包不可用区分 debug、release、profile
回归不足审核路径出现崩溃或空白做基础功能和异常场景测试
材料缺失截图、简介、隐私说明不匹配提交前按清单复核

请添加图片描述

请添加图片描述

2. 上架资料定位与审核边界

项目说明
应用模型HarmonyOS Stage 模型
配置文件entry/src/main/module.json5、签名配置、发布 profile
检查对象权限、Ability 声明、图标名称、隐私说明、测试报告、截图材料
工具DevEco Studio、AppGallery Connect、真机测试、日志
官方资料华为开发者文档中心、AppGallery Connect 上架与审核相关说明

参考入口:

不同应用品类、目标设备、权限类型和地区要求可能不同。本文提供的是工程自检方法,最终仍要以当前官方上架规则和后台提示为准。

3. 先画出提交包包含什么

上架不是只上传一个 .hap 包。一个可审核的提交包含代码包、签名、配置、权限说明、截图、应用描述、隐私相关材料和测试结论。建议在工程里建立一个发布清单,避免临时靠记忆。

// release/ReleaseChecklist.ets
export interface ReleaseCheckItem {
  id: string;
  title: string;
  owner: string;
  passed: boolean;
  note: string;
}

export const baseReleaseChecklist: ReleaseCheckItem[] = [
  { id: 'permission', title: '权限申请与使用场景一致', owner: 'client', passed: false, note: '' },
  { id: 'signing', title: '发布签名与 profile 匹配', owner: 'release', passed: false, note: '' },
  { id: 'smoke', title: '核心路径冒烟测试通过', owner: 'qa', passed: false, note: '' },
  { id: 'assets', title: '截图、简介、隐私材料完整', owner: 'product', passed: false, note: '' }
];

这段代码不是让你把发布流程写进应用运行时,而是给团队一个结构化清单。它的输入是检查项,输出是可追踪状态,防止“我以为你检查了”的责任空洞。

4. 权限要能对应到真实功能入口

权限审核最怕“申请了但看不到为什么用”。如果应用申请定位、相机、麦克风、文件等敏感权限,页面上必须有清晰的使用场景,用户拒绝后也不能直接崩溃。

下面是一个权限声明示例,实际权限名称以当前 SDK 和官方文档为准:

// entry/src/main/module.json5
{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "$string:module_desc",
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "description": "$string:EntryAbility_desc",
        "icon": "$media:app_icon",
        "label": "$string:app_name",
        "exported": true
      }
    ],
    "requestPermissions": [
      {
        "name": "ohos.permission.LOCATION",
        "reason": "$string:permission_location_reason",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "inuse"
        }
      }
    ]
  }
}

代码解释:

点说明
职责边界module.json5 只声明权限和使用场景
输入约束权限原因要写在资源文件中,便于多语言和审核查看
避免的问题防止权限声明和页面功能不一致
下一层连接页面层要处理授权、拒绝和再次引导

不要为了“以后可能用”提前申请权限。审核和用户体验都更关注最小化原则。

5. 页面层必须处理拒绝权限

权限通过不代表应用稳定。审核人员或用户可能拒绝权限,如果页面只写成功路径,就会出现空白、按钮无响应或崩溃。

// common/permission/PermissionGuard.ets
export type PermissionState = 'granted' | 'denied' | 'unknown';

export class PermissionGuard {
  static explainLocationUsage(): string {
    return '用于展示附近服务和路线推荐,不会在未使用相关功能时持续定位。';
  }

  static canEnterLocationFeature(state: PermissionState): boolean {
    return state === 'granted';
  }

  static deniedMessage(): string {
    return '未获得定位权限,仍可浏览基础内容;需要附近服务时可在系统设置中开启。';
  }
}

这段代码把权限文案和进入条件集中管理。它防止页面到处写不同版本的解释,也让拒绝权限后的体验保持一致。下一层页面只需要根据 PermissionState 展示正常内容或替代内容。

@Component
struct LocationFeatureEntry {
  @State private permissionState: PermissionState = 'unknown';

  build() {
    Column({ space: 12 }) {
      Text('附近服务')
        .fontSize(22)
        .fontWeight(FontWeight.Bold)

      Text(PermissionGuard.explainLocationUsage())
        .fontSize(14)
        .fontColor('#667085')

      Button('进入附近服务')
        .enabled(PermissionGuard.canEnterLocationFeature(this.permissionState))

      if (this.permissionState === 'denied') {
        Text(PermissionGuard.deniedMessage())
          .fontSize(13)
          .fontColor('#B42318')
      }
    }
    .padding(16)
  }
}

这段组件代码的重点是拒绝权限也有可读内容。审核时如果拒绝权限,页面仍然解释清楚功能边界,不会把用户卡死。

6. 签名配置不要混用测试环境

签名问题经常在最后阶段才暴露。开发阶段的 debug 配置、测试 profile、正式发布 profile 如果混在一起,容易出现本地能运行、提交包不符合发布要求的问题。

建议把发布签名检查写成独立脚本或清单,至少核对三件事:

检查项说明
包名与 AGC 后台应用信息一致
证书使用发布证书,不混用 debug
profile与目标应用、设备类型和发布渠道匹配

可以准备一个发布前配置摘要,不把敏感信息写进文章或日志:

// release/SigningSummary.ets
export interface SigningSummary {
  bundleName: string;
  buildMode: 'debug' | 'release';
  profileType: 'debug' | 'release';
  checkedAt: string;
}

export function assertReleaseSigning(summary: SigningSummary): string[] {
  const errors: string[] = [];

  if (summary.buildMode !== 'release') {
    errors.push('当前不是 release 构建模式');
  }

  if (summary.profileType !== 'release') {
    errors.push('当前 profile 不是发布类型');
  }

  if (summary.bundleName.length === 0) {
    errors.push('bundleName 为空');
  }

  return errors;
}

这段检查代码的边界是发布摘要,不读取证书内容,也不输出敏感路径。它防止提交前忘记切换构建模式。实际团队可以把它接到发布脚本或人工检查表里。

7. 冒烟测试覆盖用户会走的路径

审核人员不会帮你测所有边界,但常见路径出问题很容易被发现。提交前至少覆盖首页、登录、核心功能、权限拒绝、弱网和返回路径。

// release/SmokeCase.ets
export interface SmokeCase {
  name: string;
  steps: string[];
  expected: string;
}

export const releaseSmokeCases: SmokeCase[] = [
  {
    name: '首次启动进入首页',
    steps: ['清理应用数据', '冷启动应用', '等待首页出现'],
    expected: '首页可见,无白屏,无崩溃'
  },
  {
    name: '拒绝定位权限后继续使用',
    steps: ['进入附近服务', '拒绝权限', '返回首页'],
    expected: '页面给出解释,基础功能可继续使用'
  },
  {
    name: '弱网打开核心页面',
    steps: ['开启弱网或断网', '进入核心页面', '点击返回'],
    expected: '出现兜底内容或错误提示,不闪退'
  }
];

这段代码把测试用例结构化,便于复制到测试文档或发布记录里。它防止冒烟测试只说“测过了”,但没有步骤和预期结果。

8. 截图材料要和实际功能一致

截图材料不是装饰图。上架截图、应用简介、权限说明、隐私描述如果和实际功能不一致,容易降低审核通过率,也会误导用户。

建议按页面维度整理材料:

// release/SubmitAssetPlan.ets
export interface SubmitAsset {
  pageName: string;
  screenshot: string;
  description: string;
  relatedPermission?: string;
}

export const submitAssets: SubmitAsset[] = [
  {
    pageName: '首页',
    screenshot: 'home_light.png',
    description: '展示主要功能入口和推荐内容'
  },
  {
    pageName: '附近服务',
    screenshot: 'nearby_service.png',
    description: '展示基于位置的附近服务',
    relatedPermission: 'LOCATION'
  }
];

这段材料计划把截图和功能说明绑定起来。它防止截图展示了一个能力,但权限说明里没有提;也防止权限申请了,但材料里看不到对应功能。

9. 隐私与权限文案要说人话

权限原因不要写成“用于提升体验”这种空泛句子。更好的写法是说明具体功能、使用时机和不使用时的影响。

弱文案更清晰的写法
用于提升用户体验用于在你打开附近服务时展示周边地点
用于功能使用用于拍摄头像或上传反馈图片
用于数据统计用于记录崩溃信息,帮助定位稳定性问题

文案检查时可以问三个问题:

  1. 用户能不能看懂为什么要这个权限?
  2. 拒绝后还能不能继续使用非相关功能?
  3. 应用是否只在对应场景触发权限申请?

10. 提交前做一次发布复盘

发布复盘不是写长报告,而是把风险项收口。下面这个函数可以帮助团队把清单结果转成可读摘要。

// release/ReleaseReportBuilder.ets
import { ReleaseCheckItem } from './ReleaseChecklist';

export function buildReleaseReport(items: ReleaseCheckItem[]): string {
  const failed = items.filter(item => !item.passed);
  if (failed.length === 0) {
    return '发布自检通过,权限、签名、测试和材料均已确认。';
  }

  return failed
    .map(item => `${item.title} 未通过,负责人:${item.owner},备注:${item.note}`)
    .join('\n');
}

这段代码的输入是检查项列表,输出是发布摘要。它防止提交前还有未关闭的问题。真实项目中可以把结果写入 Markdown、飞书文档或发布记录。

11. 常见审核问题排查

现象可能原因检查方法修复建议
权限相关被驳回权限原因不清楚或场景不匹配对照 module.json5 和功能入口删除无用权限,补充真实使用说明
提交包不可用签名或 profile 混用核对构建模式、证书、包名重新使用发布配置打包
审核时页面空白弱网或接口异常未兜底断网打开核心页面增加缓存、错误提示和重试
截图与实际不一致使用了旧版本截图对照当前 release 包逐页检查重新截取真实页面
隐私说明被质疑文案过于笼统检查权限和数据使用说明写清楚用途、时机和影响

12. 提交前验收清单

  • module.json5 中的权限都有真实功能入口。
  • 敏感权限有清晰 reason 文案和拒绝后的页面处理。
  • 发布包使用 release 构建模式和正式 profile。
  • 首页、登录、核心功能、权限拒绝、弱网都做过冒烟测试。
  • 截图来自当前提交版本,不使用旧图。
  • 应用简介、隐私说明、权限说明之间没有冲突。
  • 提交前留存发布清单和测试结论。

这份清单建议由开发、测试、产品分别确认,不要只让一个人从头勾到尾。开发负责配置和签名,测试负责功能与异常路径,产品负责截图、简介、隐私说明和权限文案。每个角色只确认自己能负责的内容,最后再由发布负责人汇总。这样审核被驳回时,团队能快速定位是代码问题、配置问题、测试遗漏,还是材料问题。

负责人检查内容交付物
开发权限声明、Ability 配置、签名构建发布包和配置截图
测试冒烟路径、弱网、拒绝权限、返回路径测试记录和问题截图
产品应用简介、截图、隐私说明、权限文案上架材料清单
发布负责人汇总风险项,确认是否提交最终发布记录

13. 把审核变成发布前的固定动作

HarmonyOS 应用上架审核不是最后一步才开始的事情。权限要从功能设计阶段就考虑,签名要和发布环境保持一致,测试要覆盖审核可能走到的路径,截图和说明材料要与真实功能对应。把这些内容做成固定闭环,团队每次发布都按同一套清单检查,才能减少临近上线时的返工。

更实用的做法是给每次提交保留一份发布复盘:本次新增了哪些权限、变更了哪些页面、替换了哪些截图、修复了哪些审核风险。下一次版本迭代时,不需要重新理解整套上架流程,只要对比上一份复盘,就能快速看出风险增量。

复盘项记录内容
权限变化新增、删除或修改的权限与对应页面
包体变化包名、版本号、签名、profile 是否变化
功能变化影响审核路径的新增页面或核心流程
材料变化截图、简介、隐私说明是否同步更新
遗留风险暂未解决但已确认不影响提交的问题
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐