【笔下生辉|18】HarmonyOS ArkTS 权限与隐私实战:让 module.json5、功能说明和拒绝路径一致
做 HarmonyOS 5.0+ 应用上架时,权限与隐私最容易出现的风险,并不是某一处代码写错,而是几个材料之间互相说不清:module.json5 里声明了一组权限,页面功能却没有对应入口;源码里没有联网,介绍文案却写成在线服务;本地只保存学习记录,隐私说明却含糊写成可能上传;运行时没有任何权限申请,测试报告却还在描述拒绝授权后的降级流程。审核和真实用户都不会只看一个文件,最终要看的是声明、功能、数据、拒绝路径是否一致。
本文复核的是「笔下生辉」真实源码,包名标记为 com.jiaweikang.one17。当前源码的权限边界非常明确:entry/src/main/module.json5 没有 requestPermissions,也没有 ohos.permission.INTERNET;入口 EntryAbility 只做窗口、安全区、浅色模式、断点系统和本地数据初始化;HomePage、Index 等页面使用本地题库、router 和 AppStorage 完成应用内部流程;UserDataManager 通过 Preferences 保存收藏、笔记、错题、进度、考试历史和偏好设置。也就是说,本篇讨论的是一个离线题库类 ArkTS 应用如何把“不申请权限”这件事讲清楚,而不是假装源码里已经有账号、云同步、推送、拍照或定位能力。

1. 先定边界:权限不是越少越好,而是必须和功能一致
很多项目会把“无权限”当成一句宣传语,但工程上真正需要证明的是:应用没有声明权限、没有运行时请求、没有隐藏网络入口、没有把本地数据上传,也没有把用户引导到需要敏感能力的流程里。只有这些同时成立,“无权限”才是可复核的结论。
「笔下生辉」当前的源码边界可以拆成四层:
| 核验层 | 真实文件 | 复核结论 |
|---|---|---|
| 权限声明 | entry/src/main/module.json5 |
未声明 requestPermissions,未声明 ohos.permission.INTERNET |
| 入口能力 | EntryAbility.ets |
初始化本地数据、断点、安全区和窗口,不触发权限请求 |
| 页面功能 | Index.ets、HomePage.ets |
本地页面切换、本地题库入口、学习统计和导航 |
| 数据存储 | UserDataManager.ets |
通过 Preferences 保存本地学习数据和偏好 |
这四层是一条链。只要其中一环改变,隐私材料和发布材料就要跟着改变。例如未来新增拍照识别错题,那么 module.json5 需要声明相机权限,页面需要解释相机用途,运行时需要处理允许、拒绝和再次说明,隐私政策也要说明图像是否只本地处理。如果未来新增云同步,即使没有使用敏感权限,也要重新检查网络权限、账号体系、服务器位置、数据上传字段、注销和清除路径。
当前源码没有这些能力,所以文章不能为了显得完整而编造“授权弹窗”“云端同步”“服务端加密”“拒绝后只读模式”。这类段落看起来像隐私合规,实际会制造更大的不一致。
2. module.json5:不声明 requestPermissions,就是第一条可验证证据
先看 entry/src/main/module.json5。这个文件是 HarmonyOS 模块能力的公开边界,审核和发布材料都会围绕它确认应用实际请求了什么能力。当前模块声明了应用入口、设备类型、Ability、图标和启动窗口资源,但没有 requestPermissions 字段。
核心结构可以概括为:
{
"module": {
"type": "entry",
"deviceTypes": ["phone", "tablet", "2in1"],
"mainElement": "EntryAbility",
"abilities": [
{
"name": "EntryAbility",
"exported": true,
"skills": [
{
"entities": ["entity.system.home"],
"actions": ["action.system.home"]
}
]
}
]
}
}
这段声明传递出几个信息。
第一,应用面向 phone、tablet、2in1,权限和隐私说明要覆盖多设备体验,但并没有因为多设备就自动产生跨设备同步能力。多设备适配只说明 UI 和包能力支持这些终端,不等价于数据跨端流转。
第二,当前只有一个主入口 EntryAbility,公开给系统启动。没有额外的后台服务、卡片服务、分享服务、文件服务或其他需要额外权限说明的 Ability。后续如果新增服务型 Ability,必须重新核对是否引入后台行为、外部唤起、跨应用调用或系统能力使用。
第三,没有 requestPermissions 是一个明确结论,不是“忘记写权限”。只有当源码功能确实需要敏感能力时,才应该添加权限。离线题库、收藏、错题、笔记、学习进度和本地设置不需要网络、定位、相机、麦克风、通讯录、日历或文件读取权限。当前不声明权限,反而是和功能匹配的做法。
这里还有一个容易误判的细节:源码的题库内容里可能出现“网络热词”等业务分类文字,但这不是 ohos.permission.INTERNET。权限复核不能只按关键词搜索 INTERNET 就下结论,需要看命名空间、文件位置和代码语义。出现在题目分类里的“网络”是学习内容,不是应用能力。
3. EntryAbility:入口初始化不等于权限申请
权限隐私复核的第二步,是看入口是否在启动时偷偷申请系统能力。EntryAbility.ets 的职责比较集中:设置应用浅色模式、初始化本地数据管理器、设置几个 AppStorage 默认值、注册断点系统、创建窗口、记录状态栏和导航栏安全区。
可以把启动过程压缩成下面的伪代码:
onCreate(want, launchParam) {
this.context.getApplicationContext().setColorMode(COLOR_MODE_LIGHT)
UserDataManager.init(this.context)
AppStorage.setOrCreate('currentTabIndex', 0)
AppStorage.setOrCreate('favoriteTabIndex', 0)
AppStorage.setOrCreate('topAvoidAreaHeightPx', 0)
AppStorage.setOrCreate('navigationIndicatorHeightPx', 0)
BreakpointSystem.register()
}
onWindowStageCreate(windowStage) {
const mainWindow = await windowStage.getMainWindow()
mainWindow.setWindowLayoutFullScreen(true)
updateAvoidArea(mainWindow)
windowStage.loadContent('pages/SplashPage')
}
这里没有权限申请 API,也没有网络初始化、账号初始化、推送注册、广告 SDK、分析 SDK 或 Web 容器启动。UserDataManager.init(this.context) 使用的是应用上下文来打开 Preferences,本质是本地轻量存储,不是对外传输数据。BreakpointSystem.register() 是布局断点系统,服务于不同屏幕宽度下的页面适配。安全区监听用于避免状态栏和底部导航遮挡内容,也不涉及用户隐私。
这类入口代码有两个合规价值。
一是它能证明应用启动不依赖敏感授权。用户第一次打开应用时,不会被相机、定位、通知、文件、通讯录等弹窗打断。对于离线学习工具,这符合最小权限原则,也降低了审核中“权限用途不明确”的风险。
二是它能证明拒绝路径当前不是必需项。因为没有运行时权限申请,就不存在“用户拒绝权限后如何继续”的产品分支。文案里如果还写“拒绝授权后进入基础模式”,反而不准确。正确表述应该是:当前版本不申请运行时权限;如果未来新增敏感能力,必须补齐拒绝说明和降级路径。

4. 页面层:HomePage 和 Index 只做本地流程,不制造隐形数据出口
Index.ets 是主页面聚合层,负责在首页、题库、考试、收藏、我的等页面之间切换,并根据断点和安全区控制布局。它使用 @StorageLink 连接当前 tab、错题数量、断点类型、顶部安全区和底部导航避让高度。页面切换是本地状态切换,不需要读取设备敏感信息,也不需要联网。
HomePage.ets 更能体现产品能力边界。它从本地 mock 数据中读取 BANKS、CATEGORIES、TOTAL_QUESTIONS、TOTAL_REGIONS,用 StatService.summarize 汇总本地学习数据,然后通过 router.pushUrl 进入分类、搜索、题库、考试、错题等本地页面。这个路径里没有 fetch、HTTP 客户端、WebView,也没有登录态、支付态或广告态。
从权限与隐私角度看,这意味着首页展示的数据来源是明确的:
| 页面数据 | 来源 | 是否需要权限 | 是否上传 |
|---|---|---|---|
| 题库数量、地区数量 | 本地题库常量 | 否 | 否 |
| 学习统计 | 本地记录汇总 | 否 | 否 |
| 分类入口 | 本地分类数据 | 否 | 否 |
| 搜索入口 | 本地题库搜索 | 否 | 否 |
| 错题、收藏、笔记入口 | Preferences 本地数据 | 否 | 否 |
这张表对发布材料很重要。应用介绍可以说“本地题库学习、错题整理、收藏笔记、学习进度记录”,但不能说“云端同步”“多端账号共享”“智能推荐服务端分析”。当前源码没有实现这些能力,写进去会让功能说明、隐私说明和真实代码冲突。
同样,隐私政策也不应该套用过宽的模板。比如“我们会收集设备信息、网络日志、位置信息用于优化服务”这类句子,如果当前应用并没有对应代码和权限,就会制造不必要的审核风险。离线应用的材料应该更收敛:说明本地保存什么、为什么保存、是否上传、用户如何清理或重置。
5. UserDataManager:Preferences 保存的是学习状态,不是身份数据
当前应用真正持久化的数据集中在 librarya/src/main/ets/utils/UserDataManager.ets。它通过 preferences 打开名为 language_correction_master 的本地存储,把 JSON 序列化后的数据写入 Preferences,并用 flushSync() 落盘。
可复核的数据键包括:
favoriteRecords
noteRecords
wrongRecords
bankProgress
examHistory
chapterProgress
dailyReminderTime
examDurationSec
autoNextQuestion
从职责结构看,module.json5 负责声明边界,EntryAbility 负责把应用拉起并初始化本地服务,HomePage 和 Index 只消费本地状态,UserDataManager 才是本地数据读写入口。这个结构能避免页面直接散落 Preferences 调用,也让后续做权限复核时有一个清楚的检查点。

这些字段可以分成三类。
第一类是学习行为记录:收藏、笔记、错题、题库进度、章节进度和考试历史。这些数据和题目学习体验直接相关,用于恢复用户上次学习状态、展示学习统计、支持错题复盘。它们不是账号身份,不包含通讯录、定位、相册、录音、支付或社交关系。
第二类是学习偏好:每日提醒时间、考试时长、自动下一题。这些是本地交互偏好,作用范围在应用内。当前源码没有看到推送注册或系统通知权限申请,因此“每日提醒时间”更适合被描述为本地偏好设置,不能扩展成已经具备系统通知能力。
第三类是 AppStorage 镜像状态。UserDataManager.init 会把 Preferences 中读出的记录写入 AppStorage,页面再通过 @StorageLink 使用这些数据。这个路径保持了页面和持久化层之间的边界:页面消费状态,数据管理器负责读取、解析、写入和刷新。
从写入路径看,可以简化为:
private static persist(key: string, value: object): void {
this.prefs.putSync(key, JSON.stringify(value))
this.prefs.flushSync()
}
从页面消费路径看,则是先由入口初始化,再把结果放入应用级状态:
UserDataManager.init(this.context)
AppStorage.setOrCreate('currentTabIndex', 0)
AppStorage.setOrCreate('favoriteTabIndex', 0)
这类本地存储仍然需要隐私说明,但说明重点不是“我们如何上传”,而是“我们在本机保存哪些学习数据,以及用途是什么”。如果产品提供清空错题、清空收藏、清空笔记等入口,文案应说明这些操作会影响本地记录;如果没有统一清除所有数据的入口,就不要在材料里承诺“一键删除所有本地数据”。
6. 拒绝路径:当前没有权限申请,就不要虚构拒绝分支
很多权限合规文章会强调“必须处理拒绝授权”。这个原则本身是对的,但要基于真实能力。当前版本没有运行时权限申请,所以它的拒绝路径结论应该写成:
| 场景 | 当前源码状态 | 正确处理 |
|---|---|---|
| 首次启动 | 不申请权限 | 直接进入本地学习流程 |
| 使用题库 | 读取本地数据 | 不需要授权弹窗 |
| 收藏/笔记/错题 | 写入 Preferences | 不需要授权弹窗 |
| 搜索题目 | 本地搜索 | 不需要网络授权 |
| 拒绝权限 | 无运行时权限申请 | 不虚构拒绝 UI |
这不是“少做了拒绝路径”,而是“功能不需要权限,因此没有拒绝路径”。如果未来新增敏感能力,拒绝路径才会成为必做项。例如:
| 新能力 | 可能新增声明 | 必须补齐的拒绝路径 |
|---|---|---|
| 拍照识别错题 | 相机权限 | 拒绝后允许手动输入或继续普通题库学习 |
| 系统通知提醒 | 通知相关授权 | 拒绝后保留应用内提醒设置,不显示误导性成功状态 |
| 云同步 | 网络权限、账号与隐私材料 | 拒绝登录或断网时保留本地学习记录 |
| 导入文件 | 文件访问能力 | 拒绝后说明无法导入,但不影响已有本地题库 |
因此,权限与隐私的核心不是写一个万能弹窗,而是让能力变化带动四个位置一起变化:module.json5、运行时代码、页面降级说明、隐私与发布材料。只改其中一个位置,就会出现不一致。
7. 文案怎么写:功能说明要跟源码一样克制
基于当前源码,应用发布说明可以围绕本地学习能力展开:
- 本地题库学习与分类练习;
- 收藏、笔记、错题和学习进度记录;
- 模拟考试和历史记录;
- 适配手机、平板、2in1 设备;
- 当前版本不需要登录、不包含支付、广告、推送、社交或 UGC。
隐私说明也应该对应这些事实:
- 应用在本机保存学习记录、笔记、收藏、错题、进度和偏好;
- 这些数据用于恢复学习状态、统计学习情况和支持复盘;
- 当前源码未发现上传同步、外部网络请求或账号绑定流程;
- 当前
module.json5未声明运行所需敏感权限; - 如果未来增加网络、账号、通知、相机或文件导入能力,应在版本说明、隐私政策和权限说明中同步更新。
不建议写的内容包括:
- “我们会收集设备唯一标识用于用户画像”;
- “错题数据会同步到云端”;
- “用户可通过账号在多端恢复数据”;
- “拒绝定位后将无法使用附近服务”;
- “应用会根据网络热度推荐内容”。
这些能力在当前源码中没有证据。尤其是“网络热词”这类题库主题,不应被包装成联网推荐能力。发布材料和技术文章都要避免把业务内容关键词误写成系统能力。
8. 工程检查方法:先扫声明,再扫代码,再对材料
我在复核这类 HarmonyOS 应用时,会按固定顺序走一遍,不直接从隐私政策模板开始。
第一步,看 module.json5。确认是否有 requestPermissions、是否有 INTERNET、是否有额外 Ability、Service、Extension 或后台入口。这里是硬边界。
第二步,扫入口和页面。重点看 EntryAbility、主页面、设置页、导入导出页、搜索页、分享页、反馈页、关于页。确认有没有运行时权限请求、HTTP、WebView、账号 SDK、广告 SDK、推送 SDK、支付 SDK、分析 SDK。
第三步,扫数据层。确认 Preferences、RDB、文件、缓存分别保存什么,是否包含身份信息、敏感内容、儿童信息或可上传内容。当前「笔下生辉」的数据集中在 Preferences,字段语义都指向本地学习状态。
第四步,对文档和发布材料。把源码事实映射到功能说明、隐私政策、权限说明、审核自检报告和 AGC 材料。如果文档说“已移除 INTERNET、离线题库定位、本地保存学习记录”,就要确认源码仍然维持这个状态。
第五步,记录未来变更触发条件。权限和隐私不是一次性工作。只要新增网络、账号、云同步、通知、拍照、文件导入、分享、反馈、统计或第三方 SDK,就必须重新做一轮。
9. 常见问题和修正方式
问题一:module.json5 没有权限,但文案写了在线服务。 修正方式是删掉或改写在线服务描述,除非源码真实加入了网络能力并同步更新权限、隐私和用户路径。不要为了文案好看而给离线应用加“云端”“智能服务”等词。
问题二:源码只保存本地学习记录,隐私政策却套用了收集设备信息的模板。 修正方式是把隐私政策缩小到真实数据字段。能说清“收藏、笔记、错题、进度、考试历史、偏好设置”就够了,不要引入没有代码支撑的数据类型。
问题三:新增了功能但忘了更新拒绝路径。 如果未来加入相机、通知、文件导入等能力,页面必须处理拒绝授权。拒绝不是错误弹窗结束,而是要让用户知道哪些功能不可用、哪些本地能力仍可继续使用。
问题四:把题库里的业务词当成系统能力。 例如题库分类中出现“网络热词”,这不是联网。工程复核要结合文件路径和语义,不能只按关键词做结论。
问题五:本地 Preferences 被忽略。 不联网不代表没有隐私材料。本地保存的学习记录仍然是用户数据,应该在材料中说明用途和范围,只是不应夸大成上传或云端处理。
10. 小结:权限与隐私一致性,是发布前的工程交叉检查
「笔下生辉」当前版本的权限与隐私边界比较清楚:module.json5 没有 requestPermissions 和 ohos.permission.INTERNET,入口没有运行时权限请求,页面使用本地题库和本地路由,数据通过 Preferences 保存学习状态与偏好。这个结论可以从源码复核,而不是从宣传语推断。
真正需要坚持的是一致性:功能说明不超过源码,隐私政策不超过数据事实,拒绝路径不虚构,未来新增敏感能力时同步修改声明、运行时代码、页面降级和发布材料。对 HarmonyOS 5.0+ 应用来说,这比堆砌一份通用隐私模板更可靠,也更容易通过后续维护。
本篇文章的封面使用当前文章独立的 media/cover.png,不同于同一软件合集复用的应用级 collection-cover.png;合集封面保持统一,合集内每篇文章封面保持不同,便于跨平台发布时区分文章和集合。
部分内容由AI辅助生成。
更多推荐


所有评论(0)