上架前核对隐私政策,很容易只检查“设置页有一个入口”。可审核人员或普通用户进入应用的路径并不一样:有人在首次引导页点进政策,有人在设置中心里寻找,有人从应用市场的详情页打开链接。页面里都写着“隐私政策”,并不表示它们指向同一版本,更不表示远端页面一定能打开。这篇以 PolicyBridge 演示一个很克制的策略:先把本地入口一致性做成确定性检查,再把真正需要联网和人工确认的事项明确留在待验区。

一、别把“按钮存在”理解成“政策可达”

华为 AppGallery 的公开审核指南要求应用内提供容易找到的隐私政策入口,也需要在 AppGallery Connect 提交相应政策链接。政策应说明个人信息处理的目的、方式和范围,并覆盖相关第三方处理场景;应用内和提交到平台的信息还需要保持一致。这不是“在页面上写一句我们重视隐私”就可以替代的工作。本文讨论工程检查,不对某个真实应用作出通过审核的判断。

演示工程叫 PolicyBridge,页面是 PolicyCheckPage,任务编号 POL-614。样例政策版本定为 v6,使用两个本地入口:welcome 对应首次引导,settings 对应设置中心。两个入口都映射到同一份本地政策快照;屏幕展示的 2/2 只代表这两个本地配置项都匹配。线上地址是否可达、AppGallery Connect 后台实际填写了什么、站点内容是否符合合规要求,在这个数字之外。

这层区分非常重要。如果把“本地路径都匹配”直接写成“审核通过”,之后一旦出现线上 404、重定向到旧版、不同地区语言不一致,就会找不到真正的责任边界。

二、给政策版本一个可复算的指纹

只记录版本号 v6 还不够。不同构建分支都可能声称自己属于 v6,却包含不同段落。因此在演示中,版本号用于人看,短指纹用于快速发现构建产物是否混用。指纹依据一个固定的、按约定顺序拼接的样例字符串生成:PolicyBridge|v6|目的:查看任务数据|方式:本地展示|范围:任务标识。这不是具有法律效力的隐私政策全文,也没有覆盖实际 SDK 清单;它只服务于本文的校验逻辑。

下面这段属于开发阶段的 Node.js TypeScript/JavaScript 工具代码,不是 ArkTS 系统 API。它解决的问题是:使不同构建人员能够对同一演示快照算出相同标识,而不是手工填一个看起来随机的哈希值。

import { createHash } from 'node:crypto'

const policySnapshot: string =
  'PolicyBridge|v6|目的:查看任务数据|方式:本地展示|范围:任务标识'
const fingerprint: string = createHash('sha256')
  .update(policySnapshot, 'utf8')
  .digest('hex').slice(0, 8)
console.info(`PolicyBridge version=v6 fingerprint=${fingerprint}`)
// 对该固定演示字符串,结果为 c275357f

八位十六进制串适合作为可读的短校验标记,但不能作为安全签名,也无法替代全量内容校验。正式项目应对规范化后的完整政策正文生成足够长度的摘要,保留完整值与签名来源,并把多语言版本纳入计算。如果源文件中混入动态时间戳、编辑器换行风格或不可见字符,也会影响哈希,必须先明确规范化规则,而不是为了让值相同随意去掉正文。

三、把两个入口的配置收敛为一份数据

做完指纹,我首先取消两处页面各写一遍 URL 的方式。把版本、指纹和入口名放进一份应用层映射,页面只传 source;检查逻辑按入口键读取目标版本。这里只是应用内的配置结构,不涉及读取 AppGallery Connect 私有接口,更没有调用所谓“审核结果 API”。

为了让文章可复现,下面给出最小 PolicyUtils.ets。其中 getOnlineVersion() 这个方法名是 Demo 中沿用的历史叫法,实际只读取本地的候选发布快照;生产版本应改名为 getReleaseCandidateVersion(),并由独立检查任务验证线上页面。不能把这个演示返回值理解为真的访问过网络。

export class PolicyUtils {
  static readonly VERSION: string = 'v6'
  static readonly FINGERPRINT: string = 'c275357f'
  private static readonly routes: Record<string, string> = {
    welcome: 'v6',
    settings: 'v6'
  }

  static getLocalVersion(): Promise<string> {
    return Promise.resolve(PolicyUtils.VERSION)
  }
  static getOnlineVersion(): Promise<string> {
    // 演示:返回待发布配置快照,不执行联网请求
    return Promise.resolve('v6')
  }
  static countMatchingRoutes(): number {
    return Object.values(PolicyUtils.routes)
      .filter((v: string) => v === PolicyUtils.VERSION).length
  }
}

业务端只接受 welcome 和 settings 两个来源。如果来源无效,应该报出缺失入口,而不是默默替用户跳转到首页。新增“关于我们”“游客模式”之类入口时,不应直接把分母仍写成 2;应该更新声明列表和回归用例。Promise.resolve 在这里刻意表示本地静态样例,不模拟网络成功率,也不会生产任何真实 AppGallery 审核数据。

四、页面只负责展示已知事实

PolicyBridge 的 PolicyCheckPage 保留三个容易理解的状态:当前来源、是否已进入政策流程、最近一次检查摘要。首次引导和设置中心两个按钮调用相同的 openPolicy,只在参数上区别入口。这样可以排除“两个按钮调用两份过时页面逻辑”的风险。

这里的 ArkTS 代码解决两个问题:单一跳转函数和明确的失败降级。PrivacyPage 应在真实工程的页面配置中注册,并通过路由参数显示完整、可访问的政策正文;示例仅展示主页面逻辑,并不附带一个冒充真实政策的 HTML 页面。

import { router } from '@kit.ArkUI'
import { PolicyUtils } from '../utils/PolicyUtils'

@Entry
@Component
struct PolicyCheckPage {
  @State policyVersion: string = 'v6'
  @State fingerprint: string = 'c275357f'
  @State source: string = 'settings'
  @State resultText: string = '未检测'
  @State visible: boolean = false

  openPolicy(source: string): void {
    if (source !== 'welcome' && source !== 'settings') return
    this.source = source
    this.visible = true
    console.info(`[PolicyBridge] source=${source} version=v6 fp=c275357f`)
    router.pushUrl({ url: 'pages/PrivacyPage', params: { source: source } })
      .catch(() => { this.resultText = '打开失败:请使用内置政策入口' })
  }

  async checkPolicy(): Promise<void> {
    try {
      const local: string = await PolicyUtils.getLocalVersion()
      const candidate: string = await PolicyUtils.getOnlineVersion()
      const count: number = PolicyUtils.countMatchingRoutes()
      this.resultText = local === candidate && count === 2 ? '本地一致性 2/2' : '本地配置不一致'
      console.info(`[PolicyBridge] result=LOCAL_MATCH_2_OF_2 agc=UNVERIFIED`)
    } catch (e) {
      this.resultText = '配置读取失败,线上状态未核验'
    }
  }

  build() {
    Column({ space: 16 }) {
      Text('隐私政策入口核验').fontSize(24)
      Text(`政策版本 ${this.policyVersion} / 指纹 ${this.fingerprint}`)
      Button('首次引导').onClick(() => this.openPolicy('welcome'))
      Button('设置中心').onClick(() => this.openPolicy('settings'))
      Button('检查入口').onClick(() => { this.checkPolicy() })
      Text(this.resultText)
      Text('线上地址待核验')
    }.padding(20).width('100%')
  }
}

visible 用来记录当前页面是否已发起入口动作,在扩展页面里可配合返回处理恢复界面状态;它不是路由成功的判据。路由失败时只展示降级文案还不够,生产应用最好保留一个真正可打开的内置政策页面,记录失败来源与时间,不应在未经测试的情况下直接跳过用户知情环节。checkPolicy 中的 candidate 依旧是本地候选版本,故日志明确写 agc=UNVERIFIED。如果后续增加异步网络校验,还应处理超时、重定向、证书和页面销毁后的过期回调。

DevEco 配图的目录、代码和 HiLog 均为人工组织的演示画面。底部安排两次入口记录 source=welcome、source=settings,两者统一为 version=v6、fp=c275357f;第三条是 LOCAL_MATCH_2_OF_2 agc=UNVERIFIED。部分代码在演示图中被简化,真正的逻辑以本文三个代码片段为准。它不能用作线上请求成功、实际应用审核通过或 IDE 编译通过的证明。

五、要验收的是三种不同的“相同”

第一种相同是本地路由相同:welcome 和 settings 指向同一候选政策版本。第二种是内容快照相同:编译后的内置正文、版本元数据与所提交的发布包对应;短指纹只能做快筛,正式核验应保存全量摘要。第三种是对外内容相同:AppGallery Connect 后台提交的 URL、用户真实访问的最终页面、应用内展示的政策内容和第三方数据处理说明一致。第三种需要在线访问、必要的地区语言切换及人工检查,前两种的 2/2 无法代替。

手机演示图对应任务 POL-614,日期 2026-10-11,版本 v6,短指纹 c275357f,本地入口一致性 2/2,两个入口均显示“已映射 v6”。下方橙色提示“线上地址待核验”是本篇刻意保留的重要状态,不是界面缺陷。14:40 为固定演示时间;这些数据不表示当日有真实审核记录。

实际验收还应打开三个入口,手动执行返回、断网、政策站点不可访问、地区语言切换及账号未登录等操作。关键不是让所有错误都变成“成功页”,而是失败时用户仍知道隐私政策在哪里、目前是否能读取,以及如何联系开发者。政策中对信息收集目的、方式、范围、权利行使和第三方 SDK 的说明,也应由产品与合规负责人员确认,不能靠本地哈希脚本替代。

六、它能减少发布失误,但不能替代审核

这个小工具最大的价值,是把容易被忽略的配置分叉提前暴露:入口加了却没登记、构建版本变了却没同步短指纹、页面可见但完整正文没有配置。代价也很明确——维护一份可靠的候选发布快照,保证生成环节的内容规范化,避免因为人为修改常量而把 2/2 做成自我证明。

我会把线上 URL 检查放在独立发布流程里:真正读取 AppGallery Connect 提交值后再做重定向、TLS、最终正文和语言版本核对;无法自动读取后台配置的部分就留作人工待验,绝不自动标绿。当前 Demo 只完成本地逻辑说明,不包含真实网页抓取、DevEco 编译结果或平台审核结论。能确认什么就标记什么,不把本地一致性包装成上架通过率,这才是这种小工具可以长期复用的边界。

参考资料:华为开发者 AppGallery Review Guidelines 第 7 节 User Privacy:
https://developer.huawei.com/consumer/en/doc/app/50104-07

华为开发者 App Review 常见问题“True and Valid Information Provided by Developers”:
https://developer.huawei.com/consumer/fr/doc/app/FAQ-faq-09

华为开发者 ArkTS 页面生命周期(含 router.pushUrl 示例):
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-v5/arkts-page-custom-components-lifecycle-V5

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐