【口算王|18】HarmonyOS ArkTS 权限与隐私实战:让 module.json5、功能说明和拒绝路径一致
证据边界:本文依据 D:/huawei/one16-11 当前可读取源码整理;本轮未执行构建、模拟器、真机权限弹窗、通知调度、TTS 网络抓包或 AGC 审核。文中对在线回退和学习提醒的描述均为源码层风险识别,不代表相关能力已经验证或上线。

权限合规不是在 module.json5 里"能少写就少写"。真正的工程目标是让四份事实一致:应用实际功能、调用的系统 API、声明的权限、对用户和审核人员展示的说明。如果页面写着"完全离线",代码却悄悄尝试在线服务;如果设置页提供"每日提醒",却没有通知调度能力;如果清理数据只清界面不清磁盘,都会形成审核和用户信任风险。
口算王当前的权限面非常小:entry 模块 requestPermissions 是空数组,没有 INTERNET、通知、麦克风、相机、位置、联系人等声明,也没有运行时请求权限的代码。题库由本地算法生成,收藏、笔记、错题、进度、挑战历史和设置通过 Preferences 保存在本机。与此同时,PracticePage 的文字转语音先创建离线引擎,失败后会以 online: 1 再试;SettingsPage 的"每日学习提醒"目前只保存时间,没有真正注册通知或提醒任务。
本文基于口算王项目 D:\huawei\one16-11 的真实源码,复核 module.json5、EntryAbility.ets、PracticePage.ets、SettingsPage.ets、UserDataManager.ets、MockBanks.ets 与题库详情说明。包名 com.jiaweikang.one16 是本文草稿核验使用的唯一标记。项目目标和兼容 SDK 均为 HarmonyOS 6.0 系列,本文方法适用于 HarmonyOS 5.0 及以上 Stage 模型应用。
本文将完成六件事:
- 从 module.json5 建立实际权限清单;
- 从源码 API 反查隐含能力与数据流;
- 区分"不需要权限""权限被拒绝"和"能力不可用";
- 复核本地存储、清理和隐私说明边界;
- 找出提醒占位与在线 TTS 回退的一致性风险;
- 给出上架前可执行的权限隐私核对表。
一、权限审计先以清单事实为准
entry/src/main/module.json5 中:
#"requestPermissions": []"requestPermissions": []
这意味着当前安装包没有在该模块声明系统权限。设备类型包含 phone、tablet 和 2in1,但设备类型不等于权限。
源码搜索也没有发现:
requestPermissionsFromUser();- INTERNET 权限;
- 相机、麦克风、位置、联系人、日历权限;
- 通知授权请求;
- Web 组件或普通 HTTP 客户端;
- 登录、账号、支付、广告、分析和推送 SDK。
因此当前应用不存在"首次启动弹出权限框"的真实行为。
二、零权限不等于零数据
应用不申请敏感权限,仍然会处理本地业务数据。口算王保存:
| 数据 | 用途 | 存储位置 |
|---|---|---|
| 收藏记录 | 收藏题目 | Preferences |
| 笔记记录 | 用户题目笔记 | Preferences |
| 错题记录 | 错题复习 | Preferences |
| 题库进度 | 学习统计 | Preferences |
| 章节进度 | 章节完成度 | Preferences |
| 挑战历史 | 分数与统计 | Preferences |
| 提醒时间 | 设置显示 | Preferences |
| 挑战时长 | 练习配置 | Preferences |
| 自动下一题 | 交互偏好 | Preferences |
这些数据没有因为"无需权限"就不属于隐私说明范围。特别是用户输入的笔记,应明确仅用于本机学习记录,不上传、不共享。
三、题库由本地算法生成
MockBanks 文件明确写着"单机题库,全部题目由本地算法生成"。加减乘除、混合运算、应用题和限时题都由本地函数构造。
题库详情页也向用户说明:
#所有题目本地生成,不需要联网;
错题会自动进入错题本,方便反复巩固。所有题目本地生成,不需要联网;
#所有题目本地生成,不需要联网;
错题会自动进入错题本,方便反复巩固。错题会自动进入错题本,方便反复巩固。
这项说明与题目生成源码一致。没有远程题库下载、接口分页或账号同步。
但"题目不需要联网"不应自动扩大为"应用所有能力绝不触网",因为语音引擎还需要单独核查。
四、本地数据入口集中在 UserDataManager
UserDataManager 使用 Preferences 实例 daily_math_drill,将数组序列化为 JSON,并把标量设置直接保存。
页面不直接操作文件系统,也没有数据库或云端 API。数据流是:
#用户操作
-> UserDataManager用户操作
#用户操作
-> UserDataManager -> UserDataManager
-> Preferences
#-> AppStorage
-> 页面刷新 -> AppStorage
#-> AppStorage
-> 页面刷新 -> 页面刷新
这种集中存储边界便于隐私审计:只需核对一个服务中的键、写入和清理逻辑,就能列出主要本地数据类型。
五、当前没有权限拒绝路径
标题中的"拒绝路径"不能机械套用。只有应用请求运行时权限,用户才会选择允许或拒绝。当前 requestPermissions 为空,也没有授权请求,所以不存在真实的系统权限拒绝回调。
当前更准确的是"能力失败路径":
- TTS 引擎不可用;
- TTS 播放失败;
- Preferences 写入失败;
- 路由参数缺失;
- 系统避免区读取失败。
这些失败不等于权限拒绝。文章和用户文案应使用正确术语,避免告诉用户"请授权麦克风",因为文本转语音播放不需要录音权限。
六、TTS 不需要麦克风权限
PracticePage 导入:
#import textToSpeech from '@hms.ai.textToSpeech'import textToSpeech from '@hms.ai.textToSpeech'
它把题目文本交给语音合成引擎播放。文本转语音是输出能力,不录制用户声音,因此当前没有申请 MICROPHONE 是合理的。
页面点击语音提示后:
- 展示文字提示弹窗;
- 尝试准备 TTS;
- 引擎可用则播放;
- 失败则保留文字提示;
- 不阻断答题主流程。
这是一条合理的能力降级链路。
七、离线引擎优先是真实实现
ensureTtsEngine 首先执行:
#textToSpeech.createEngine({
language: 'zh-CN',textToSpeech.createEngine({
#textToSpeech.createEngine({
language: 'zh-CN', language: 'zh-CN',
person: 0,
#online: 0
}) online: 0
#online: 0
})})
online: 0 表示优先使用离线引擎。成功后设置 onStart、onComplete、onStop 和 onError 监听,页面显示"正在播放示例"或失败说明。
如果当前设备缺少对应语音引擎,方法返回 false,页面显示"当前设备语音引擎不可用"。用户仍能在弹窗中阅读 audioHint 或题干。
八、online:1 回退需要合规决策
离线引擎创建失败后,源码会再尝试:
#textToSpeech.createEngine({
language: 'zh-CN',textToSpeech.createEngine({
#textToSpeech.createEngine({
language: 'zh-CN', language: 'zh-CN',
person: 0,
#online: 1
}) online: 1
#online: 1
})})
这明确表达了在线引擎意图。当前 module.json5 没有 INTERNET 权限,项目也没有普通网络客户端。具体网络由应用还是系统服务承担,需要依据该 TTS API 的官方说明和实机行为确认。
在确认前,不能同时无条件声称"所有功能完全离线"。有两个合规方向:
- 严格离线:删除 online:1 回退,只保留文字提示;
- 保留在线语音:确认权限、数据处理和隐私披露,说明发送的文本、服务提供方与网络条件。
九、语音失败不会破坏核心流程
toggleAudioPreview 先打开提示弹窗,再在后台尽力调用 TTS。调用被 try/catch 包围,即使失败也不会关闭弹窗或阻止答题。
这种降级满足三个原则:
- 用户主动点击后才触发;
- 失败时有可见状态;
- 核心功能仍可通过文字完成。
需要补强的是不要空 catch。至少应记录非敏感错误码,便于区分引擎缺失、资源缺失和服务错误;日志不应记录完整题目文本或用户笔记。
十、每日提醒当前只是设置值
SettingsPage 提供"每日学习提醒",可选 09:00、12:00、21:00 或关闭。selectReminder 会:
- 修改 dailyReminderTime;
- 更新显示值;
- 写入 Preferences;
- 显示"已设置"的提示。
但源码中没有通知权限、提醒代理、定时任务或通知发布 API。当前行为只是保存偏好,并不会在时间到达时真正提醒。
因此产品文案应改为"提醒时间偏好"或暂时隐藏该入口,直到实际提醒能力完成。
十一、不能为占位功能提前申请通知权限
即使未来计划实现提醒,也不应现在先加入通知权限。权限必须与已交付功能和用户场景同步。
正确顺序是:
- 明确提醒触发方式;
- 查阅 HarmonyOS 官方通知与提醒 API;
- 确认是否需要授权;
- 设计申请时机和用途说明;
- 实现允许、拒绝、不再询问、系统关闭等状态;
- 更新隐私材料;
- 再添加 module.json5 声明。
当前零权限清单与"没有真实通知调度"是匹配的。
十二、如果未来请求权限,必须按需触发
权限不应在启动页一次性请求。以未来可能的通知为例,合理时机是用户主动开启提醒开关后。
实现时还要把"用户动作、系统授权、界面状态"做成同一事务:只有授权成功才保留开关;拒绝或系统返回异常时立即回滚开关,并让用户继续使用练习、错题和笔记等无权限功能。这样既避免把拒绝误判为功能故障,也不会让设置页显示一个实际上无法兑现的提醒状态。
请求前说明:
- 为什么需要;
- 不授权会影响什么;
- 哪些功能仍可使用;
- 如何在系统设置中重新开启。
拒绝后页面应保持关闭状态,不能反复弹窗、不能阻断训练,也不能把普通功能包装成"必须授权"。
十三、权限状态需要结构化建模
若未来加入权限,可以定义:
#type PermissionState =
| 'not_requested'type PermissionState =
#type PermissionState =
| 'not_requested' | 'not_requested'
| 'granted'
#| 'denied'
| 'blocked' | 'denied'
#| 'denied'
| 'blocked' | 'blocked'
| 'unavailable'
页面据此展示:
- 未请求:说明用途;
- 已允许:功能正常;
- 已拒绝:提供降级;
- 已禁止再次询问:引导系统设置;
- 设备不支持:隐藏或说明。
当前项目没有这套模型,因为没有运行时权限;它属于未来扩展建议。
十四、数据保存在本机的说明有源码依据
SettingsPage 的"框架"说明中写着:
#页面采用 ArkTS 与 ArkUI 构建,数据保存在本机。页面采用 ArkTS 与 ArkUI 构建,数据保存在本机。
这与 UserDataManager 的 Preferences 实现一致。entry 的 oh-package 依赖只有本地 librarya 和 libraryb,没有第三方分析、广告或网络 SDK。
不过"保存在本机"还应明确生命周期:
- 数据随应用使用而更新;
- 用户可在设置页清理;
- 卸载应用后随应用数据删除;
- 当前没有账号备份与跨设备同步。
十五、用户笔记是最需要谨慎对待的数据
收藏、错题和进度主要由应用生成,笔记包含用户自由输入。即使只存在本机,也应遵循数据最小化:
口算练习可能由未成年人使用,笔记里还可能出现姓名、班级或学习安排等用户自行填写的内容。因此,默认不导出、不上传、不进入埋点和崩溃日志,比事后猜测文本是否敏感更稳妥;若未来增加备份或分享,应重新设计明确的用户触发、范围预览和删除机制。
- 不读取与笔记无关的设备信息;
- 不把笔记写入普通日志;
- 不上传;
- 提供删除;
- 存储异常时不在错误信息中展示全文;
- 未来增加导出或同步时重新做隐私评估。
当前源码没有上传和分享笔记的能力,这是可复核边界。
十六、清理入口是隐私闭环的一部分
SettingsPage 可以分别清除:
- 收藏;
- 笔记;
- 错题;
- 学习进度;
- 挑战历史。
还提供"清除所有学习数据"的二次确认。调用 UserDataManager 的 clear 方法后,把 AppStorage 中的数组设为空。
这让用户无需卸载应用就能删除学习数据。需要继续验证的是持久化失败场景:当前 persist 会吞掉异常,页面可能显示已清空而重启后数据恢复。隐私删除必须以真正落盘成功为准。
十七、隐私删除不能只清当前界面
完整删除验证包括:
- 清理前记录各类数据数量;
- 执行单项或全部清理;
- 当前页面为空;
- 返回其他页面仍为空;
- 杀进程并重启;
- 数据不恢复;
- Preferences 对应键已更新;
- 失败时向用户反馈而不是假装成功。
如果未来增加缓存、数据库、文件或云端,每个数据副本都要进入删除清单。
十八、模块导出能力也要审计
EntryAbility 的 exported 为 true,并声明 HOME 实体和 home action。这是桌面启动入口的正常配置。
审计时仍要确认:
- 没有额外隐蔽 Ability;
- 没有未使用的服务或 Extension;
- 外部 Want 参数经过校验;
- 导出入口不能执行敏感操作;
- 没有后台自启动、悬浮窗或跨应用干预。
当前 module.json5 只列出 EntryAbility,没有其他敏感扩展能力。
十九、零权限有助于降低 2.19 风险
App Review Guideline 2.19 关注对网络、系统机制和其他应用的滥用。当前项目没有:
- 隐藏后台任务;
- 自动启动链;
- 悬浮窗;
- 无障碍控制;
- 动态代码加载;
- 修改其他应用数据;
- 强制卸载或干扰系统;
- 未授权设备控制。
这与本地口算工具的产品范围一致。后续若加入推送、网络、账号或后台提醒,必须重新评估。
二十、隐私材料应从代码事实生成
上架材料至少应准确回答:
- 是否联网;
- 是否收集个人信息;
- 保存哪些本地数据;
- 是否使用第三方 SDK;
- 是否需要登录;
- 是否含广告或支付;
- 是否跨设备同步;
- 如何清除数据;
- 语音服务是否可能在线处理文本;
- 提醒是否真实可用。
当前最稳妥的描述是:题库本地生成,学习记录和设置保存在本机,无账号、广告和支付;语音功能优先使用设备离线引擎,在线回退需在发布前作出明确决策。
二十一、不能把 PV、账号数据或云同步写进材料
源码没有 PV 统计、点赞、收藏量上报、用户账号或云端同步。发布材料和技术文章都不能虚构这些能力。
本地"收藏记录"只是用户自己的题目收藏,不是平台点赞或公开社交数据。挑战历史是设备内记录,也不是排行榜服务。
ExamResultPage 目前展示的排名字符串不是由网络数据计算,不能宣传为真实在线排名。
发布核验时要把"本地功能名"和"平台运营指标"严格分开:本地收藏数量只能说明设备内保存了多少道题,静态排名文案也不能证明存在服务端榜单。PV、点赞、公开收藏、账号规模和云同步状态都必须来自可回读的平台或服务端数据;没有真实数据时就留空。
二十二、第三方依赖审计不能只看 entry
entry/oh-package.json5 只依赖本地 librarya 与 libraryb,但 PracticePage 直接导入系统或 HMS TTS Kit。合规审计应覆盖:
- 源码 import;
- HAR/HSP 依赖;
- oh-package lock;
- 原生 so;
- SDK 初始化代码;
- 网络域名;
- AGC 服务开关;
- 隐私政策中的 SDK 清单。
"没有 npm 依赖"不等于没有平台服务依赖。TTS 应作为单独能力说明。
二十三、功能说明要避免绝对化
"所有题目本地生成,不需要联网"是准确的题库说明。若把它改成"应用完全不联网",就会与 online:1 回退产生冲突。
这种按能力拆分的表达也更便于审核复核:审核人员可以分别对照题目生成、Preferences 存储和 TTS 初始化代码,不必依赖一句覆盖全应用的宣传语。以后某个能力发生变化时,只需同步修改对应说明、隐私材料和测试用例,不会让其他离线能力一起失真。
推荐把说明按功能拆开:
- 题库与训练:本地运行;
- 学习数据:本机保存;
- 语音提示:优先使用设备语音引擎,可用性取决于系统服务;
- 提醒:当前仅保存偏好,未提供系统通知;
- 同步:当前不支持。
分功能描述比一句绝对承诺更可核验。
二十四、拒绝与失败文案要可行动
当前语音失败文案包括:
- 当前设备语音引擎不可用;
- 语音播放失败,请检查系统语音服务。
它们没有错误地要求麦克风权限,方向正确。还可以增强:
失败提示应回答三个问题:哪项能力不可用、当前任务是否还能继续、用户下一步能做什么。对本应用而言,TTS 失败不应中断答题,也不应弹出引导用户授予无关权限的对话框;保留文字展示并提供一次明确重试,比笼统的"请检查权限"更符合真实故障边界。
- 明确文字提示仍可使用;
- 提供重试;
- 避免循环创建引擎;
- 若仅离线,提示下载系统离线语音资源;
- 若在线,说明网络条件与隐私处理。
失败文案不能暗示应用拥有实际不存在的修复能力。
二十五、自动化扫描建立权限回归门槛
可以在 CI 或发布脚本中检查:
- module.json5 的 requestPermissions;
- 新增
@kit、@ohos、@hmsimport; - HTTP、Web、Socket、账号和推送 API;
- Camera、Microphone、Location 等能力;
- 新增 ExtensionAbility;
- 第三方依赖和原生库;
- 隐私材料中的权限、SDK 和数据字段。
任何新增能力都要求开发者更新审计表,而不是等上架被拒后再补材料。
二十六、权限测试矩阵
当前零权限版本也要测试:
| 场景 | 预期 |
|---|---|
| 首次启动 | 不弹权限框 |
| 离线训练 | 全部核心题型可用 |
| 无语音引擎 | 文字提示可用,训练不中断 |
| TTS 播放失败 | 显示失败说明,可继续答题 |
| 设置提醒时间 | 仅保存显示,不宣称通知已调度 |
| 清空笔记 | 当前和重启后均删除 |
| 飞行模式 | 题库、记录和练习可用 |
| 系统暗色/多窗口 | 权限和隐私入口仍可触达 |
| 卸载重装 | 本地学习数据不应保留 |
二十七、上架前核对表
- requestPermissions 与实际 API 一致;
- 不申请未使用权限;
- 首次启动不批量索权;
- 题库本地生成说明真实;
- Preferences 数据类型已列入隐私说明;
- 用户笔记不上传、不写日志;
- 清理操作经重启验证;
- TTS online:1 已确认去留;
- 提醒入口与真实通知能力一致;
- 无登录、广告、支付和分析时材料不勾选;
- 无云同步时不宣传跨设备恢复;
- 第三方和平台 Kit 清单已复核;
- 安装、启动、核心流程、退出、卸载正常;
- 审核说明能快速找到本地题库和清理入口。
二十八、常见问题排查表
| 症状 | 可能原因 | 处理 |
|---|---|---|
| 首次启动出现权限框 | 新依赖或模块新增声明 | 对照 requestPermissions |
| 页面要求麦克风 | 混淆 TTS 与录音 | 删除错误引导 |
| 提醒时间到了无通知 | 只保存 Preferences | 完成真实调度或改文案 |
| 宣称完全离线但语音触网 | online:1 回退 | 删除或披露 |
| 清空后重启数据回来 | 持久化失败被吞掉 | 返回删除结果 |
| 隐私政策写"不存储数据" | 忽略本地学习记录 | 按实际键修正 |
| 说有跨设备同步 | 把 AppStorage 当云同步 | 明确单设备边界 |
| 说有在线排名 | 使用静态展示值 | 删除能力宣称 |
| 无第三方依赖却有 TTS | 只看 oh-package | 审计平台 Kit import |
| 用户拒绝后反复弹窗 | 权限状态未建模 | 按需请求并提供降级 |
二十九、总结:权限越少,事实越要准确
口算王当前的真实合规基础很好:模块权限清单为空,核心题库本地生成,学习记录保存在 Preferences,没有账号、广告、支付、推送和普通网络客户端;语音引擎失败时保留文字提示,核心训练不被阻断;设置页提供数据清理入口。
同样需要正视两个不一致点:每日提醒目前只是配置值,不能描述为已经发送系统通知;TTS 在离线失败后尝试 online:1,与绝对"完全离线"表述存在冲突,需要在发布前选择纯离线或完成在线服务合规。
权限与隐私不是一张静态表,而是一条持续核验链:
- 从功能列出真实能力;
- 从 import 和 API 反查数据流;
- 与 module.json5 权限逐项比对;
- 为不可用和拒绝设计降级;
- 验证本地存储与删除;
- 同步更新隐私政策、商店材料和审核说明。
只有代码、权限、页面文案和上架材料说的是同一件事,零权限才真正成为产品优势。
本文部分内容由 AI 辅助整理,所有现有权限、数据、TTS、提醒与清理行为均依据上述本地源码复核;运行时权限状态模型、自动审计、通知实现步骤与隐私材料建议属于基于当前边界的工程增强方案。


两个容易被忽略的一致性风险
当前 module.json5 的 requestPermissions 为空,这与工程主体采用本地题库、算法计算和 Preferences 存储的实现是吻合的。但"没有声明权限"不等于可以跳过隐私核验:凡是存在在线回退、系统服务或尚未完成的设置入口,都要继续追踪真实调用链。
第一处是语音播报。现有练习页会优先尝试离线 TTS,并在离线引擎失败后使用 online: 1 重试。这里不能仅凭参数名断言一定产生网络请求,也不能把它写成已经验证的在线服务;正确做法是继续核对所用 TTS Kit 的官方文档、运行条件、数据处理方式和真机网络行为。如果最终确认会联网,应用说明、隐私政策和网络能力配置必须同步更新。
第二处是"每日学习提醒"。当前设置页保存了提醒时间,但现有证据没有显示通知调度或定时任务已经接入。因此文章只把它视为设置占位和后续实现风险,不能宣传为已生效的提醒功能。真正实现时,应重新检查是否需要通知相关能力、用户开关、拒绝后的界面反馈,以及关闭提醒后是否会清理既有任务。
这两个例子说明,权限审计不能只搜索 requestPermissions。更可靠的方式是从用户可见功能出发,沿着页面事件、Service、Kit/API、配置文件和隐私文案逐层回溯,并把"已实现""待验证""规划中"分开记录。
AI 辅助声明
本文在人工复核口算王 ArkTS 源码、配置文件和本地数据实现后,使用 AI 辅助整理结构、润色表达并生成配图;未执行的构建、真机、网络行为和 AGC 审核均未写成已通过。