【时光清单|18】HarmonyOS ArkTS 权限与隐私实战:让 module.json5、功能说明和拒绝路径一致
**本轮证据边界:**本文基于
D:\huawei\one8当前生产源码、清单与项目记录做静态复核。本轮没有执行构建、安装、模拟器、真机、备份恢复、包体解包、AGC 提交或平台审核;“源码中未发现”只描述本次限定扫描结果,不等于对所有产物和运行环境作绝对保证。阅读约定:“当前事实”来自本文点名的真实源码与配置;“历史证据”仅来自
PROJECT_ERRORS.md的既有记录;状态机、审计矩阵、文案修正、产物核验和设备回归均属于建议实现,不代表当前工程已经落地或通过。
权限合规最容易出现的误区,是把它当成“在module.json5里补几个字符串”。真正进入 HarmonyOS 5.0 及以上版本的开发和上架流程后,审核看到的是一条完整链路:安装包声明了什么能力,页面在什么时机触发系统接口,用户拒绝后还能不能继续使用,隐私政策对数据处理作了什么承诺,AppGallery 后台又勾选了哪些权限和数据类型。
这几份事实只要有一处不一致,就可能出现很具体的问题。例如,应用通过系统照片选择器只读取用户主动选中的一张图片,隐私政策却写成“申请相册权限”;数据实际由 Preferences 保存为 JSON 字符串,文案却承诺“加密存储”;应用启用了系统备份恢复扩展,政策仍绝对表述“卸载后数据自动清除”。这些句子看起来更“安全”,却未必更合规,因为它们超出了源码能够证明的边界。
时光清单的真实源码恰好提供了一个很适合复盘的样本:module.json5没有requestPermissions;通知由用户点击“通知提醒”后调用requestEnableNotification();时光相册使用PhotoViewPicker;纪念日、日记、愿望、相册记录等数据通过 Preferences 写入应用本地;同时项目注册了备份扩展,并在backup_config.json中允许备份恢复。本文不把现状包装成已经完成的合规方案,而是逐项对账,给出可复核的修正方向。
本文将解决:
- 如何从真实源码建立“能力、声明、触发、拒绝、披露”清单。
module.json5没有requestPermissions是否等于应用不涉及授权。PhotoViewPicker为什么不能简单写成“申请相册权限”。- 通知授权被拒绝时,页面和服务层分别应该承担什么职责。
- Preferences、应用沙箱、加密和备份恢复为何不能混为一谈。
- 上架前怎样用一套矩阵核对包、功能说明、隐私政策和实际行为。
本文唯一标记:
CSDN-SERIES:ALL-163210062
**历史证据说明:**项目记录显示,2026 年 5 月 20 日曾因相册只能使用预设图而接入系统照片选择器,并为相关页面补齐编辑、删除与即时刷新;同一批记录写有当时
assembleHap成功。它只能说明那次修复曾被记录,不能替代本文日期下的重新构建、设备验证与发布验收。
一、先建立五列权限台账,而不是先写隐私政策
权限与隐私的第一份产物应该是源码台账。它不是面向用户的长篇法律文本,而是一张工程事实表:
| 能力 | 静态声明 | 运行时接口 | 用户触发点 | 拒绝后的结果 |
|---|---|---|---|---|
| 本地通知 | 当前未见requestPermissions | requestEnableNotification() | “我的”页点击“通知提醒” | 提示去系统设置,核心页面继续可用 |
| 选择照片 | 当前未见相册权限声明 | PhotoViewPicker.select() | 时光相册点击选择照片 | 保留编辑页,提示未选择 |
| 本地持久化 | 无危险权限声明 | Preferences | 保存纪念日、日记、愿望等 | 初始化或读写失败时返回默认值 |
| 系统备份恢复 | EntryBackupAbility扩展 | BackupExtensionAbility | 由系统备份恢复机制管理 | 当前回调仅记录日志 |
| 应用内 JSON 备份 | 无额外权限声明 | fileIo写入filesDir | 本文复核范围内未发现页面调用 | 服务存在,但不能宣称用户已可导出 |
这张表会迫使团队回答三个问题:
- 文案里提到的每一项权限,源码中是否真有对应接口或声明?
- 源码中已经存在的数据路径,隐私文案是否准确披露?
- 用户拒绝、取消或系统接口失败后,应用是否仍有可预测状态?
如果回答不了,就先不要写“全部加密”“不会备份”“必须授权”这类绝对句。权限最小化不仅是少声明权限,也包括少作无法验证的承诺。
二、module.json5 是静态事实,但不是全部事实
项目当前的module.json5定义了入口 Ability、备份扩展和卡片扩展,关键部分可以概括为:
{ "module": { "name": "entry", "type": "entry", "deviceTypes": ["phone"], "abilities": [ { "name": "EntryAbility", "exported": true } ], "extensionAbilities": [ { "name": "EntryBackupAbility", "type": "backup", "exported": false }, { "name": "CountdownFormAbility", "type": "form", "exported": true } ] } }这里没有requestPermissions。这个结论只能证明“当前安装包清单没有声明常规运行时权限”,不能直接推导出“应用不会出现任何系统授权界面”。因为通知开关、Picker、系统设置跳转等能力可能通过各自 Kit 的系统界面完成。
反过来也一样:如果以后为了相机、定位、麦克风等功能新增权限,不能只在页面里调用 API。静态声明、运行时申请、用途说明和拒绝分支需要同时落地。例如确实需要用户授权的能力,应把名称与用途写清楚:
{ "module": { "requestPermissions": [ { "name": "ohos.permission.CAMERA", "reason": "$string:permission_camera_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } } ] } }这段示例是“未来确有拍摄功能时”的配置方法,不是对当前项目的能力声明。当前源码使用的是照片选择器,并未实现拍照,因此不能为了让文案看起来完整而擅自增加相机权限。
三、通知授权:从用户动作开始,而不是在启动页打断
ProfileView.ets把通知授权放在“我的”页中的明确入口:
this.MenuItemNav('通知提醒', 'ic_anniversary', () => { this.handleNotificationPermission(); })真正的授权动作发生在用户点击以后:
private async handleNotificationPermission(): Promise<void> { try { const granted = await ReminderService.requestPermission(); if (granted) { const repo = AnniversaryRepository.getInstance(); const items = await repo.getAll(); await ReminderService.sendDailyReminder(items); promptAction.showToast({ message: '通知提醒已开启', duration: 2000 }); } else { promptAction.showToast({ message: '请在系统设置中允许通知权限', duration: 2000 }); } } catch (_e) { promptAction.showToast({ message: '通知设置失败,请重试', duration: 2000 }); } }这条链路有两个正确方向。第一,系统授权由用户主动点击触发,不在应用启动时批量请求。第二,拒绝后只影响提醒功能,并没有关闭应用或阻止查看纪念日。
但从工程语义看还可以更精确。当前ReminderService.requestPermission()只要调用requestEnableNotification()没有抛出异常,就返回true:
static async requestPermission(): Promise<boolean> { try { await notificationManager.requestEnableNotification(); return true; } catch (e) { hilog.warn(0x0000, TAG, `requestPermission failed: ${JSON.stringify(e)}`); return false; } }因此页面上的“已开启”依赖接口成功语义。更稳的处理是把服务返回值定义成业务状态,而不是单一布尔值,避免以后把“系统界面正常关闭”“用户已授权”“系统异常”混成同一个结果:
export type NotificationEnableResult = 'enabled' | 'denied' | 'failed'; export class ReminderPermissionService { static async request(): Promise<NotificationEnableResult> { try { await notificationManager.requestEnableNotification(); return 'enabled'; } catch (error) { hilog.warn(0x0000, 'ReminderPermission', `request failed: ${JSON.stringify(error)}`); return 'denied'; } } }如果目标 API 还能查询当前通知开关状态,应在系统调用后再次读取真实状态,再决定展示“已开启”还是“请前往设置”。文章这里强调的是边界:页面负责解释结果与继续路径,服务负责系统接口和错误归一化。
四、通知拒绝路径不能只剩一条 Toast
当前拒绝分支已经避免阻断核心功能,但只有瞬时 Toast。用户看完后并不知道下一步从哪里打开系统设置,也不知道不开通知还能否正常管理纪念日。
适合的页面状态至少有三种:
type ReminderPermissionState = 'unknown' | 'enabled' | 'disabled'; @State reminderPermissionState: ReminderPermissionState = 'unknown';对应界面可以这样组织:
| 状态 | 主文案 | 操作 | 核心功能 |
|---|---|---|---|
unknown | 开启后接收临近纪念日提醒 | 开启通知 | 正常可用 |
enabled | 通知提醒已开启 | 发送测试提醒或关闭说明 | 正常可用 |
disabled | 通知未开启,不影响纪念日管理 | 前往系统设置 | 正常可用 |
如果系统允许直接拉起目标应用的权限设置,应使用官方文档支持的接口,并在跳转前明确说明用途。若没有可靠跳转能力,就保留静态指引,不要用循环弹窗逼迫用户授权。用户再次点击提醒功能时可以重新触发授权或给出设置入口,但启动、查看、编辑、删除等主流程必须继续工作。
还要注意,当前sendDailyReminder()是在授权成功后立即根据仓库数据发布一条通知:
const upcoming = items .filter((item: Anniversary) => { const days = calcDaysRemaining(item.targetDate); return days >= 0 && days <= 3; }) .sort((a: Anniversary, b: Anniversary) => a.targetDate - b.targetDate);这段实现验证的是“立即发送临近事项通知”,并不等于系统已经具备每天自动调度。隐私政策和功能介绍可以说“用于发送纪念日提醒”,但不应扩展成“后台每天自动扫描并精准推送”,除非项目确实实现并验证了后台任务、触发条件和生命周期。
五、PhotoViewPicker 是选择范围,不是申请整个相册
AlbumView.ets没有读取整个媒体库,而是在用户点击后拉起系统选择器:
private async pickPhoto(): Promise<void> { try { const picker = new photoAccessHelper.PhotoViewPicker(); const options = new photoAccessHelper.PhotoSelectOptions(); options.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; options.maxSelectNumber = 1; const result = await picker.select(options); if (result.photoUris.length > 0) { this.editorUri = result.photoUris[0]; } } catch (_e) { promptAction.showToast({ message: '未选择照片', duration: 1500 }); } }这套实现体现的是“用户选择哪一张,应用使用哪一张”。它比申请整库访问更符合最小必要原则。当前隐私政策中的“相册权限:用于时光相册功能中的照片选择”容易让用户理解为应用请求了读取相册的广泛权限,而源码并没有对应的requestPermissions声明。
更准确的用户文案可以写成:
照片选择:当您主动使用“时光相册”并点击选择照片时, 应用会调用系统照片选择器。应用仅接收您本次选择的照片 URI, 不会主动遍历您的整个媒体库。取消选择不影响纪念日、日记、 愿望清单等其他功能。注意“仅接收 URI”仍然需要后续验证。项目把result.photoUris[0]保存进 Preferences,但没有在本文复核范围内看到把照片复制到应用私有目录、验证 URI 长期可读性或处理媒体删除后的失效状态。因此隐私文案不应声称“照片已安全复制并永久保存”;页面也需要为 URI 失效准备占位图和重新选择入口。
六、把“取消选择”和“系统异常”拆开
当前pickPhoto()的catch统一提示“未选择照片”。这对轻量交互很友好,却会掩盖真实故障:用户取消、Picker 不可用、URI 返回异常、系统服务错误都得到同一句提示。
可以将结果收敛成可测试的状态:
type PhotoPickResult = | { kind: 'selected'; uri: string } | { kind: 'cancelled' } | { kind: 'failed'; message: string }; async function selectSinglePhoto(): Promise<PhotoPickResult> { try { const picker = new photoAccessHelper.PhotoViewPicker(); const options = new photoAccessHelper.PhotoSelectOptions(); options.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; options.maxSelectNumber = 1; const result = await picker.select(options); if (result.photoUris.length === 0) { return { kind: 'cancelled' }; } return { kind: 'selected', uri: result.photoUris[0] }; } catch (error) { return { kind: 'failed', message: JSON.stringify(error) }; } }页面拿到cancelled时可以安静保留编辑器;拿到failed时提示“暂时无法打开照片选择器,请稍后重试”;只有selected才更新 URI。这样既不把取消当错误,也不会用“未选择”掩盖真实系统故障。
隐私审查也会因此更清晰:照片处理发生在明确的用户动作之后;取消不会写入数据;失败不会偷偷扩大权限范围;拒绝或取消不影响其他核心功能。
七、Preferences 是本地存储,不等于业务加密
DataStore.ets使用 ArkData Preferences,并把复杂对象序列化成 JSON:
async putJson<T>(key: string, value: T): Promise<void> { await this.ensureReady(); if (!this.pref) return; try { const json = JSON.stringify(value); await this.pref.put(key, json); await this.pref.flush(); } catch (e) { hilog.error(DOMAIN, TAG, 'putJson [%{public}s] failed: %{public}s', key, JSON.stringify(e)); } }该实现能够证明:
- 数据通过 Preferences 保存在应用侧。
- 存储名称是
timelist_data。 - 纪念日、愿望、日记、习惯、情侣空间、相册 URI 等使用不同 Key。
- 代码在写入后调用
flush()。
但它不能证明“业务数据经过加密后落盘”。JSON.stringify()是序列化,不是加密;应用沙箱提供访问隔离,也不等于源码实现了额外的密码学保护。PrivacyPolicyView当前写有:
您的数据以加密方式存储在应用沙箱目录中, 仅限本应用访问。其中“应用沙箱目录中”与当前实现方向一致,“以加密方式存储”则缺少对应加密调用、密钥管理和解密链路的源码证据。发布前有两种选择:
- 没有真实加密需求:删去“加密方式”,准确描述为应用本地存储和访问边界。
- 确有敏感数据保护需求:先完成数据分级、加密算法、密钥生命周期、迁移和失败恢复,再保留加密承诺。
对普通纪念日和界面设置,Preferences 适合轻量键值数据;密码、Token 等短敏感数据不应直接混入同一个 JSON。是否使用 Asset Store Kit 或加密数据库,应由数据类型和风险决定,而不是为了让隐私政策出现“加密”两个字。
八、备份恢复会改变“卸载即清除”的绝对表述
项目不只有 Preferences,还注册了备份扩展:
{ "name": "EntryBackupAbility", "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", "type": "backup", "exported": false, "metadata": [ { "name": "ohos.extension.backup", "resource": "$profile:backup_config" } ] }对应配置是:
{ "allowToBackupRestore": true }而当前隐私政策写着“卸载应用后,本地数据将被自动清除”。如果系统允许备份恢复,用户重新安装或迁移设备后,数据是否可能被恢复,需要以真实备份范围和系统行为为准。因此更稳的表述应避免把“应用本地目录被卸载清理”扩大成“任何备份副本都会永久消失”。
此外,项目还有一个BackupService,它会在context.filesDir生成backup_<timestamp>.json:
const fileName = `backup_${Date.now()}.json`; const filePath = `${this.baseDir}/${fileName}`; const json = JSON.stringify(data, null, 2);不过全文检索只发现服务定义,没有发现页面调用exportBackup()、importBackup()或listBackups()。因此当前可以陈述“源码存在应用内备份服务”,不能陈述“用户已经可以导出和恢复备份”。审核资料、帮助页和更新说明都要以可触达功能为准。
九、隐私政策应从事实模型生成
把前面的源码证据收敛后,隐私政策不需要堆砌术语。适合当前项目的结构可以是:
interface PrivacyFact { feature: string; trigger: string; data: string; purpose: string; storage: string; optional: boolean; deniedEffect: string; } const PRIVACY_FACTS: PrivacyFact[] = [ { feature: '通知提醒', trigger: '用户在“我的”页点击“通知提醒”', data: '纪念日标题与剩余天数', purpose: '生成本地提醒内容', storage: '纪念日数据保存在应用本地', optional: true, deniedEffect: '不发送通知,其他功能继续可用' }, { feature: '时光相册', trigger: '用户点击“选择照片”', data: '用户本次选择的照片 URI 与说明', purpose: '展示用户保存的照片记录', storage: 'URI 和说明保存在应用本地', optional: true, deniedEffect: '不添加照片记录,其他功能继续可用' } ];这不是要求在产品代码里动态拼接法律文本,而是建议团队维护一份结构化事实源。隐私政策、权限用途字符串、AppGallery 隐私标签、测试用例和发布说明都从同一组事实核对。每次增加能力时,评审不再问“要不要补一段文案”,而是问“这条事实发生了什么变化”。
十、把拒绝路径做成可回归的产品状态
权限合规不能只在“同意”路径上测试。至少要覆盖以下场景:
| 场景 | 操作 | 预期结果 |
|---|---|---|
| 首次打开应用 | 不点击任何授权相关入口 | 不应批量弹出通知或照片授权 |
| 开启通知 | 点击“通知提醒”并允许 | 展示成功状态,临近事项可触发通知 |
| 拒绝通知 | 点击“通知提醒”并拒绝 | 核心页面可继续使用,有清晰设置指引 |
| 再次进入 | 已拒绝后重新点击 | 不死循环,不重复强制弹窗 |
| 选择照片 | 选择一张图片 | 只记录所选 URI,编辑器可保存 |
| 取消选择 | 在系统 Picker 中取消 | 不新增记录,不把取消显示成严重错误 |
| URI 失效 | 原照片删除或授权失效 | 显示占位状态,允许重新选择 |
| 本地存储失败 | Preferences 初始化或写入失败 | 页面保留可解释状态,不宣称已保存 |
| 卸载重装 | 在备份开启和关闭场景分别测试 | 结果与隐私政策、备份配置一致 |
尤其要检查“拒绝通知后,保存纪念日是否仍成功”和“取消照片选择后,日记与愿望清单是否仍可用”。这两条能直接验证“拒绝授权不影响核心功能”不是一句空话。
十一、日志本身也属于隐私边界
项目对异常使用hilog记录:
hilog.warn(0x0000, TAG, `requestPermission failed: ${JSON.stringify(e)}`);错误对象通常有助于排障,但不能默认认为其中永远没有用户数据、文件路径或系统上下文。当前BackupService还会记录完整filePath,ReminderService虽未打印纪念日标题,但未来修改时很容易把通知正文写入日志。
发布前建议按以下规则审查:
- 不输出照片 URI、日记正文、情侣空间消息、愿望内容。
- 不输出完整备份文件内容或用户选择的文件路径。
- 错误码可记录,错误对象按字段筛选后记录。
- 调试日志在 release 构建中降级或移除。
- 不把隐私数据标记为公开格式化参数。
一个更窄的日志包装可以只保留错误码:
interface BusinessErrorLike { code?: number; message?: string; } function logPermissionError( tag: string, error: BusinessErrorLike ): void { hilog.warn( 0x0000, tag, 'permission operation failed, code=%{public}d', error.code ?? -1 ); }这段代码的重点不是隐藏所有故障,而是让日志拥有明确的数据预算:排查需要什么就记录什么,不把整个对象无条件序列化。
十二、代码、文案和 AGC 后台如何逐项对齐
上架前可以用四向对账:
| 检查项 | 源码证据 | 应用内文案 | AGC 后台 |
|---|---|---|---|
| 通知 | requestEnableNotification()、本地publish() | 说明触发时机、用途和拒绝影响 | 按真实通知行为填写 |
| 照片 | PhotoViewPicker.select() | 写“用户主动选择的照片”,避免写整库读取 | 不虚报广泛相册访问 |
| 本地数据 | Preferences Key 与模型字段 | 列出纪念日、日记、愿望等实际类型 | 数据处理标签与本地处理一致 |
| 第三方 SDK | 依赖与初始化代码检索 | 无第三方 SDK 才能写“不集成” | SDK 清单保持一致 |
| 网络 | manifest 与 HTTP/Web/SDK 调用检索 | 无网络行为才写“不上传服务器” | 联网、服务端位置等按实际填写 |
| 备份恢复 | backup 扩展与配置 | 解释系统备份边界 | 发布说明与隐私材料一致 |
这里最容易被忽略的是时间差:代码已经增加照片上传,隐私政策仍是旧版本;AGC 已选择“收集照片”,应用实际只使用系统 Picker;又或者应用删掉某权限,安装包清单仍残留声明。每次发布候选包都应该重新从产物和源码出发核对,不能沿用上一个版本的结论。
十三、针对当前源码的修正优先级
不改业务功能的前提下,当前最小修正顺序是:
- 将“相册权限”改成“系统照片选择器”,写清只处理用户主动选择的照片。
- 在没有加密实现证据前,删除“以加密方式存储”的绝对承诺。
- 结合
allowToBackupRestore: true修正“卸载即全部清除”的表述,或明确关闭不需要的系统备份。 - 将通知拒绝从单次 Toast 提升为可见状态和可重试入口。
- 将照片 Picker 的取消与系统失败区分。
- 为 URI 失效、本地写入失败和恢复后的数据状态补充回归测试。
- 审查 release 日志,避免照片 URI、正文和备份路径泄露。
如果产品决定继续支持系统备份,隐私政策可以说明数据主要保存在本地,部分应用数据可能按系统备份恢复设置参与设备级备份;如果产品决定严格“卸载即不可恢复”,就需要关闭备份恢复并重新验证包内配置。两种方案都可以,关键是配置、行为和承诺只能选同一套事实。
十四、发布前硬检查清单
配置
- [ ] 解包或查看编译产物,确认
module.json中实际权限与源码一致。 - [ ] 核对所有
extensionAbilities的类型、导出状态和用途。 - [ ] 核对
backup_config.json是否符合隐私政策。 - [ ] 未使用的危险权限从清单移除。
触发
- [ ] 通知授权只在用户主动点击相关功能后出现。
- [ ] 照片选择只通过明确按钮触发。
- [ ] 不在启动时批量申请与首屏无关的权限。
- [ ] 系统弹窗前后不使用诱导式文案。
拒绝与异常
- [ ] 拒绝通知后仍可新增、编辑和删除纪念日。
- [ ] 取消照片选择不新增空记录。
- [ ] Picker 失败与用户取消使用不同提示。
- [ ] 本地写入失败不显示“已保存”。
- [ ] 重复点击不会连续拉起多个系统界面。
文案与后台
- [ ] 隐私政策中的每个数据类型都能在真实模型或存储 Key 中定位。
- [ ] “加密”“匿名化”“不上传”“永久删除”等承诺都有代码或配置证据。
- [ ] 第三方 SDK、网络、备份、通知和照片处理与 AGC 填写一致。
- [ ] 隐私政策在应用内可访问,返回路径和深浅色可读性正常。
十五、常见问题与排查顺序
| 现象 | 常见原因 | 优先排查 |
|---|---|---|
| 安装后立即弹权限 | 授权调用放在启动生命周期 | EntryAbility、首页aboutToAppear |
| 清单无权限但出现系统界面 | 使用了通知开关或 Picker 等独立系统能力 | 对应 Kit API 和用户触发点 |
| 用户拒绝后功能仍提示已开启 | 只判断调用未抛异常 | 调用后真实状态查询与结果建模 |
| 取消选图却显示系统故障 | 取消与异常共用catch | Picker 返回值和错误码 |
| 政策写加密但找不到密钥 | 把沙箱或 JSON 序列化当成加密 | Preferences 写入链路、Asset/加密库 |
| 卸载重装后数据出现 | 系统备份恢复仍开启 | backup 扩展、allowToBackupRestore |
| AGC 标签与包不一致 | 沿用旧版本材料 | 重新解包并按本次候选包核对 |
| 图片记录突然不可显示 | 仅保存外部 URI,访问条件变化 | URI 生命周期、占位和重新选择 |
排查时先看编译产物和真实 API,再看页面文案,最后看 AGC。只修改隐私政策而不核对安装包,或者只删清单权限却保留功能调用,都会让问题从一处移动到另一处。
十六、总结:隐私合规是一份可执行契约
时光清单的源码说明,HarmonyOS 权限治理并不是“权限越少越好”这么简单。当前源码的module.json5未出现requestPermissions字段,照片选择通过PhotoViewPicker收窄到用户主动选择范围,通知授权从明确入口触发,这些都是良好的最小化方向。
真正需要收紧的是事实表达:Preferences 本地 JSON 不能直接等同于业务加密;允许系统备份恢复时,不能绝对承诺卸载后所有副本都消失;Picker 不能泛化成读取整个相册;通知接口成功也需要和真实开关状态保持一致。把这些边界写清楚,再为拒绝、取消、失败和恢复补齐可见状态,权限与隐私就从一段静态文案变成了可测试、可审核、可持续维护的工程契约。
参考资料:
- HarmonyOS 应用隐私保护
- HarmonyOS 选择和同意规范
- HarmonyOS 应用配置文件概述
- Asset Store Kit 简介
AI 辅助声明:本文由 AI 辅助整理与润色,所有源码事实、配置结论和风险判断均基于文中列出的真实项目文件复核;涉及权限、隐私和上架材料时,请以当前 HarmonyOS 官方文档、实际编译产物与 AppGallery Connect 页面为准。