版本快照整理完成后,下一张经常卡住交付的表是权限表。业务代码里已经删除拍照入口,module.json5却保留了相机权限;另外一个网络上传功能刚刚加入,开发者只检查了页面上能否点按钮,忘记核对模块权限配置。更隐蔽的问题是:同样叫“选择照片”,使用系统图片选择器和在应用内持续管理媒体库不是同一种接入方式,照搬另一套项目的权限清单往往会引入不必要的请求。

功能场景与权限矩阵

本篇延续上一节的交付审查工作台,不重新造一个应用。上一节已经固定了构建产品、模块集合与版本快照;现在增加一个“功能—权限—使用阶段”的映射层,既检查功能缺少的权限,也检查配置中多出来的权限。这里的审查原则不是看到requestPermissions里有某个字符串就算通过,而是问清楚它对应哪个启用功能、在何时使用、是否需要用户授权以及是否有可以直接采用的系统选择能力。

把能力需求与配置文本分离

我先把功能清单整理成业务事实。比如“扫码录入设备标签”需要相机采集;“将保修结果同步到服务端”要发起网络通信;“从相册选一张维修照片”可以通过系统选择器取得用户选定的文件。前两项的权限要求要依据所调用的具体API核对;第三项不能因为界面展示照片就直接推断需要宽范围相册读取权限。各项还要标记是否在当前构建产品中启用,避免审核已下线的功能。

需要注意,module.json5使用的是Stage工程的requestPermissions,不要把旧FA模型config.json中的reqPermissions当成同一字段。权限名也应以目标API文档为准;权限类型、使用条件或可用设备发生变化时,业务映射表必须跟着更新。将这些规则放在独立数据结构中,比在界面按钮点击时遍历配置字符串更容易维护。

export interface FeaturePermission {
  featureId: string
  label: string
  enabled: boolean
  required: string[]
  selectionBySystem: boolean
}

export interface PermissionRecord {
  name: string
  reason?: string
  abilities?: string[]
  when?: string
}

export const activeFeatures: FeaturePermission[] = [
  { featureId: 'camera-scan', label: '扫描设备标签', enabled: true,
    required: ['ohos.permission.CAMERA'], selectionBySystem: false },
  { featureId: 'cloud-upload', label: '同步维修结果', enabled: true,
    required: ['ohos.permission.INTERNET'], selectionBySystem: false },
  { featureId: 'photo-picker', label: '选择维修照片', enabled: true,
    required: [], selectionBySystem: true }
]

这个对象中的required是工程审查配置,不是系统向应用自动返回的能力清单。对需要权限的每个功能,维护者必须从其实际调用的Kit接口核查要求;遇到三方库,还要看库内部是否会调用受保护接口。这样才能避免把“某个团队以前这样写过”变成永久规则。另一个字段selectionBySystem专门记录由系统选择器完成的操作,它能提醒开发者不要为了方便而把用户的整个媒体库权限加进来。

module.json5需要检查哪些部分

Stage模块在entry/src/main/module.json5中配置权限集合。用户授权类型的权限通常需要说明用途,并给出使用场景;reason一般引用字符串资源,usedScene要与实际Ability和触发时机一致。下面保留了相机功能和网络访问的最小关联关系,其中相机的授权说明在资源文件里维护,以便跟随应用语言资源更新。

{
  "module": {
    "name": "entry",
    "type": "entry",
    "requestPermissions": [
      {
        "name": "ohos.permission.CAMERA",
        "reason": "$string:camera_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.INTERNET"
      }
    ]
  }
}

这里不要为了让所有权限看起来整齐,就给每个权限机械添加相同的reason和usedScene。相机与网络访问的授权方式不同,应按权限分类处理。检查器对当前必须提供原因和使用场景的项目执行严格检查;对其他项目则记录是否存在、是否有业务映射。需要额外申请或限制开放的能力,还应单独核查其适用条件,不能因为配置文件中写入名称就认定已经可用。

静态规则要同时抓缺项与冗余项

我把审查结果分成“缺少配置”“待清理配置”“用途不完整”三类,而不把所有问题归为一个笼统的失败状态。缺项影响功能是否可以正常申请权限,冗余项影响权限最小化和发布资料维护,用途不完整则影响申请时给用户的解释。三类问题对应不同负责人,输出时应当能给出相关功能名称,方便快速找到实际入口。

export interface PermissionIssue {
  kind: 'missing' | 'unused' | 'purpose'
  name: string
  detail: string
}

export function reviewPermissionMap(features: FeaturePermission[],
  declared: PermissionRecord[]): PermissionIssue[] {
  const issues: PermissionIssue[] = []
  const needed = new Set<string>()
  features.filter(f => f.enabled).forEach(f => {
    f.required.forEach(name => needed.add(name))
  })
  needed.forEach(name => {
    if (!declared.some(p => p.name === name)) {
      issues.push({ kind: 'missing', name, detail: '启用功能缺少权限配置' })
    }
  })
  declared.forEach(p => {
    if (!needed.has(p.name)) {
      issues.push({ kind: 'unused', name: p.name, detail: '未找到启用功能关联' })
    }
    if (p.name === 'ohos.permission.CAMERA' &&
      (!p.reason || !p.abilities?.length || p.when !== 'inuse')) {
      issues.push({ kind: 'purpose', name: p.name, detail: '相机用途或使用场景不完整' })
    }
  })
  return issues
}

基础检查先产生问题,再由维护者判断是否有跨模块、后台任务或第三方库的合理使用场景。比如某个权限没有当前界面入口,却可能用于有明确业务作用的服务模块,此时应补入功能清单而不是贸然删除配置。unused是“需要核对”,不是立即删除的命令。为了支持多个产品,权限检查应按产品启用功能集合计算,不应该拿所有产品的功能并集反推每一个安装包的权限需求。

开发环境中的权限用途检查

用户授权与静态配置不是一回事

权限写在文件里只解决“应用准备使用这个能力”的配置问题。实际调用相机前,还需要按照相关授权接口检查或请求权限;用户拒绝时,页面要留有说明与替代路径。网络权限的处理方式则不同,它不是在用户点同步按钮时照搬相机的授权弹窗流程。审查表因此可以分成配置检查和交互检查两列,不把两者相加成一个“已开启”的结果。

图片选择尤其需要单独看待。系统提供的PhotoViewPicker支持用户自主选取媒体文件,应用获得本次选择的URI后再执行需要的读取与私有保存。这样的场景和扫描整个相册不同。只做选择器操作的项目不应顺手保留READ_IMAGEVIDEO等更广泛媒体访问权限;如果业务确实需要管理用户媒体库,应查清对应权限与开放条件,并让功能清单明确记录这个额外需求。

界面记录审查状态与复查路径

我让工作台展示三列:功能名称、声明配置、需要复核的内容。重新加载配置时,新的结果替换旧结果;切换构建产品时,功能集合和权限集合都要跟着变更。页面不保存“永久通过”的布尔值,因为一个新版本可能删除了入口、变更了Kit或者调整了Ability名称。保留检查输入的版本摘要,比保存单一绿色状态更有价值。

@Entry
@Component
struct PermissionReviewPage {
  @State issues: PermissionIssue[] = []
  @State note: string = '请选择核对入口'

  private declared: PermissionRecord[] = [
    { name: 'ohos.permission.CAMERA',
      reason: '$string:camera_reason', abilities: ['EntryAbility'], when: 'inuse' },
    { name: 'ohos.permission.INTERNET' }
  ]

  private refresh(): void {
    this.issues = reviewPermissionMap(activeFeatures, this.declared)
    this.note = this.issues.length === 0 ? '当前规则未发现缺项' : '存在需要处理的权限配置'
  }

  aboutToAppear(): void {
    this.refresh()
  }

  build() {
    Column({ space: 14 }) {
      Text('权限用途核对').fontSize(24).fontWeight(FontWeight.Bold)
      Text(this.note).fontColor(Color.Grey)
      Button('重新核对').onClick(() => this.refresh())
      List({ space: 8 }) {
        ForEach(this.issues, (item: PermissionIssue) => {
          ListItem() {
            Column({ space: 4 }) {
              Text(item.name).fontWeight(FontWeight.Medium)
              Text(item.detail).fontSize(14)
            }.padding(12).alignItems(HorizontalAlign.Start)
          }
        }, (item: PermissionIssue) => item.kind + item.name)
      }.layoutWeight(1)
    }.padding(20).width('100%').height('100%')
  }
}

组件只体现审查结果的状态和列表更新方式。生产工作台还需要显示“命中的功能”“权限配置所在模块”“reason资源是否存在”等细节;特别是多Ability工程,usedScene.abilities不能靠硬编码默认值草率判断。对于权限用途文案,应让产品与研发共同确认语义,技术侧负责核对它是否与代码行为匹配,避免写成模糊的通用描述。

把异常输入放进交付核对

我通常至少准备四组输入:启用扫码而未配置CAMERA;已经关闭扫码却仍保留CAMERA;配置了CAMERA却没有用途资源或usedScene;使用PhotoViewPicker选择一张照片却多出广泛媒体读取权限。前三种检查器可以直接给出稳定结果,第四种需要结合具体调用路径与用户选择机制分析。再加上切换构建产品的用例,才能发现某个权限其实是另一个产品才使用的情况。

交付工作台的功能权限矩阵

当静态规则报告缺项时,先确认模块和产品是否选对,再检查JSON5解析后数组是否为空,最后看功能清单是否遗漏第三方调用。如果所有权限都突然变成“未使用”,多半不是源码一夜之间变干净,而是功能列表没有成功加载。反过来,如果某个功能取消后仍被多个页面引用,检查器也不该轻率把对应权限标成无关。把这些差异写入问题详情,开发者才能沿着输入逐级排查。

权限核对结束后,工作台应留下的是“功能、权限、理由、阶段与来源”的关系,而不只是权限名列表。下一步将检查三方依赖的具体版本和构建产品锁定情况,再结合包体摘要形成更完整的发布候选基线。技术细节可查华为开发者的权限申请配置实例、Media Library Kit选择器能力说明以及AppGallery Connect隐私描述配置。当权限与功能一一关联,审查才从“文件里有没有写”走向“为什么必须写”。

Logo

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

更多推荐