做 HarmonyOS 5.0+ 应用上架时,权限与隐私最容易出现的风险,并不是某一处代码写错,而是几个材料之间互相说不清:module.json5 里声明了一组权限,页面功能却没有对应入口;源码里没有联网,介绍文案却写成在线服务;本地只保存学习记录,隐私说明却含糊写成可能上传;运行时没有任何权限申请,测试报告却还在描述拒绝授权后的降级流程。审核和真实用户都不会只看一个文件,最终要看的是声明、功能、数据、拒绝路径是否一致。

本文复核的是「笔下生辉」真实源码,包名标记为 com.jiaweikang.one17。当前源码的权限边界非常明确:entry/src/main/module.json5 没有 requestPermissions,也没有 ohos.permission.INTERNET;入口 EntryAbility 只做窗口、安全区、浅色模式、断点系统和本地数据初始化;HomePageIndex 等页面使用本地题库、routerAppStorage 完成应用内部流程;UserDataManager 通过 Preferences 保存收藏、笔记、错题、进度、考试历史和偏好设置。也就是说,本篇讨论的是一个离线题库类 ArkTS 应用如何把“不申请权限”这件事讲清楚,而不是假装源码里已经有账号、云同步、推送、拍照或定位能力。

权限与隐私封面

1. 先定边界:权限不是越少越好,而是必须和功能一致

很多项目会把“无权限”当成一句宣传语,但工程上真正需要证明的是:应用没有声明权限、没有运行时请求、没有隐藏网络入口、没有把本地数据上传,也没有把用户引导到需要敏感能力的流程里。只有这些同时成立,“无权限”才是可复核的结论。

「笔下生辉」当前的源码边界可以拆成四层:

核验层 真实文件 复核结论
权限声明 entry/src/main/module.json5 未声明 requestPermissions,未声明 ohos.permission.INTERNET
入口能力 EntryAbility.ets 初始化本地数据、断点、安全区和窗口,不触发权限请求
页面功能 Index.etsHomePage.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"]
          }
        ]
      }
    ]
  }
}

这段声明传递出几个信息。

第一,应用面向 phonetablet2in1,权限和隐私说明要覆盖多设备体验,但并没有因为多设备就自动产生跨设备同步能力。多设备适配只说明 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 数据中读取 BANKSCATEGORIESTOTAL_QUESTIONSTOTAL_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 负责把应用拉起并初始化本地服务,HomePageIndex 只消费本地状态,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 没有 requestPermissionsohos.permission.INTERNET,入口没有运行时权限请求,页面使用本地题库和本地路由,数据通过 Preferences 保存学习状态与偏好。这个结论可以从源码复核,而不是从宣传语推断。

真正需要坚持的是一致性:功能说明不超过源码,隐私政策不超过数据事实,拒绝路径不虚构,未来新增敏感能力时同步修改声明、运行时代码、页面降级和发布材料。对 HarmonyOS 5.0+ 应用来说,这比堆砌一份通用隐私模板更可靠,也更容易通过后续维护。

本篇文章的封面使用当前文章独立的 media/cover.png,不同于同一软件合集复用的应用级 collection-cover.png;合集封面保持统一,合集内每篇文章封面保持不同,便于跨平台发布时区分文章和集合。

部分内容由AI辅助生成。

Logo

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

更多推荐