【知律|18】HarmonyOS ArkTS 权限与隐私实战:让 module.json5、功能说明和拒绝路径一致

权限与隐私最容易出现的审核问题,往往不是"权限申请得太多"这么简单,而是代码、配置、页面文案和上架材料各说各话。module.json5 声明了网络权限,产品说明却写成完全离线;设置页显示"提醒已设置",工程里却没有通知调度;页面提示数据已清除,持久化异常却被静默吞掉。这些细节单独看都不大,合在一起就会让审核人员无法判断应用真正做了什么。

知律当前是一个面向 HarmonyOS 5.0 及以上版本的本地法律学习应用。源码中的题库、收藏、笔记、错题、学习进度和考试记录都可以复核;同时,练习页的语音功能会先创建离线 TTS 引擎,失败后再尝试在线引擎。也正因为这条在线兜底存在,ohos.permission.INTERNET 不能被当成"多余配置",但也不能被扩写成账号、云同步或远程题库能力。

本文基于 D:\huawei\one19-11 的真实源码,重点复核 entry/src/main/module.json5EntryAbility.etsIndex.etsHomePage.etsPracticePage.etsSettingsPage.etsUserDataManager.ets。包名 com.jiaweikang.one19 是本文草稿回读时使用的唯一标记。全文只描述源码中能确认的行为,对在线 TTS 的具体数据处理范围、系统服务边界和网络实现不做未经官方文档或真机证据支持的推断。

本文会解决五个实际问题:

  • 从功能调用反推权限,而不是先堆一张权限表;
  • 解释 INTERNET 为什么存在,以及它不代表什么;
  • 区分权限拒绝、能力不可用和持久化失败;
  • 把本地学习数据、清理入口与隐私材料对齐;
  • 找出工程中仍会影响上架审核的一致性风险。

一、先画出四份必须一致的事实

权限审计不应只打开 module.json5 看一眼。真正需要相互校验的是四份事实:

事实来源 需要回答的问题 知律当前证据
产品功能 用户能触发什么 本地题库、练习、收藏、笔记、错题、统计、TTS
系统 API 代码实际调用什么 Preferences、TTS、窗口与路由能力
模块配置 安装包声明什么 INTERNET,无相机、麦克风、位置等敏感权限
用户与审核材料 对外怎样说明 本机存储、网络用途、删除方式、无账号与无云同步

只要其中一列变化,其他三列就要重新检查。例如以后加入云端题库,不能只补 HTTP 请求;还要更新隐私政策、网络用途、数据处理说明和离线失败路径。

二、module.json5 的真实权限面很小

当前 entry 模块只声明一项权限:

json5 复制代码
"requestPermissions": [
  {
    "name": "ohos.permission.INTERNET"
  }
]

源码没有声明相机、麦克风、位置、联系人、短信、日历或媒体读取权限,也没有发现 requestPermissionsFromUser() 一类运行时索权代码。首页和主导航只是组合本地页面、题库数据与学习记录,没有启动时集中申请权限的逻辑。

这里要避免两个相反误判:

  1. 不能因为只有 INTERNET 就写成"零权限应用";
  2. 也不能因为存在 INTERNET 就宣称应用具备远程题库、云同步、在线排行或账号服务。

权限只能证明模块被允许使用对应系统能力,不能替代对真实调用路径的说明。

三、INTERNET 对应的是在线 TTS 兜底

练习页导入了 HMS 文本转语音能力:

typescript 复制代码
import textToSpeech from '@hms.ai.textToSpeech'

初始化时先尝试离线引擎:

typescript 复制代码
this.ttsEngine = await textToSpeech.createEngine({
  language: 'zh-CN',
  person: 0,
  online: 0
})

只有离线创建失败,才进入第二条路径:

typescript 复制代码
this.ttsEngine = await textToSpeech.createEngine({
  language: 'zh-CN',
  person: 0,
  online: 1
})

因此,当前源码中最明确的网络意图就是在线 TTS 回退。INTERNET 只服务于在线 TTS 兜底,这是现阶段能够从配置与调用链共同确认的边界;工程中没有普通 HTTP 客户端、Web 组件、账号接口或远程题库下载代码。

上架说明可以写"语音示例优先使用设备离线语音能力,在离线引擎不可用时可能尝试在线语音服务",但是否需要进一步披露文本处理、服务提供方和网络条件,应以对应 TTS Kit 的官方说明及真机抓取结果为准,不能凭经验补写。

四、TTS 不等于录音,不需要麦克风

文本转语音的输入是应用已有的题目文本,输出是合成语音。它不录制用户声音,所以当前没有 ohos.permission.MICROPHONE 是合理的。

这一点直接影响失败文案。如果播放失败,页面不能提示"请允许麦克风权限",因为这会把无关权限引入产品认知,也会诱导开发者错误地增加权限。当前源码使用的文案是:

text 复制代码
当前设备语音引擎不可用
语音播放失败,请检查系统语音服务

这比"请开启录音权限"准确。更完整的文案还可以补充:文字提示仍可查看,答题流程不受影响。

五、当前没有运行时权限拒绝回调

标题中的"拒绝路径"需要按实际权限类型理解。知律没有请求相机、位置等需要用户在运行时选择允许或拒绝的敏感权限,因此源码中没有真实的授权对话框,也没有 granteddeniedblocked 等授权状态。

当前实际存在的是三类失败:

  • 设备没有可用的离线或在线 TTS 引擎;
  • TTS 引擎创建成功,但播放回调返回错误;
  • Preferences 初始化或写入失败。

它们都不应被叫作"用户拒绝权限"。术语准确很重要,因为排查动作完全不同:权限拒绝要检查授权状态,TTS 不可用要检查系统服务和设备能力,持久化失败则要检查存储初始化与写入结果。

六、语音能力已经有不阻断主流程的降级

用户点击听音题时,toggleAudioPreview() 先打开文字弹窗,再后台尝试语音:

typescript 复制代码
this.audioDialogText = this.audioText(question)
this.audioDialogStem = question.stem
this.showAudioDialog = true

const ready = await this.ensureTtsEngine()
if (ready && this.ttsEngine) {
  this.ttsEngine.speak(this.audioText(question), {
    requestId: `${question.id}_${Date.now()}`
  })
}

这条顺序设计得很稳。即使引擎创建失败,用户仍然能看到题干或发音提示,继续完成答题。它满足三个审核友好的原则:

  • 用户主动点击后才触发语音;
  • 失败有可见状态;
  • 附加能力失败不阻断核心学习流程。

还可以改进的一点是空 catch。错误日志不应包含完整题目或用户笔记,但可以记录非敏感错误码和阶段,例如 offline_engine_create_failed,帮助区分离线资源缺失与在线服务失败。

七、本地数据并不少,不能写成"不存储数据"

知律不要求登录,也没有云同步,但会处理多类设备内学习数据。UserDataManager 通过 Preferences 保存:

数据内容 用户价值
favoriteRecords 收藏题目 ID、题库 ID、时间 收藏复习
noteRecords 题目笔记文本 个性化学习记录
wrongRecords 错题与时间 错题复盘
bankProgress 已答数、正确数、章节 学习进度
chapterProgress 章节完成统计 章节追踪
examHistory 分数、题量、用时 模拟考试记录
dailyReminderTime 提醒时间偏好 设置显示
examDurationSec 考试时长 模拟考试配置
autoNextQuestion 自动下一题开关 交互偏好

隐私材料应写"学习数据保存在本机",而不是"应用不存储任何数据"。本地存储仍然是数据处理,只是当前没有证据表明这些数据会上传或分享。

八、Preferences 集中封装让审计更容易

EntryAbility.onCreate() 在应用启动时初始化数据层:

typescript 复制代码
UserDataManager.init(this.context)

随后 UserDataManager 从 Preferences 读取 JSON,再写入 AppStorage:

typescript 复制代码
UserDataManager.prefs =
  preferences.getPreferencesSync(context, { name: UserDataManager.STORE_NAME })

AppStorage.setOrCreate<FavoriteRecord[]>(
  'favoriteRecords',
  JSON.parse(favStr) as FavoriteRecord[]
)

页面只通过管理器更新数据,避免每个页面直接散落存储调用。这种结构让权限与隐私审计可以沿着一条稳定链路进行:

text 复制代码
页面操作
  -> UserDataManager
  -> Preferences
  -> AppStorage
  -> 页面刷新

不过,存储名仍是 dialect_quiz,而应用已经是知律。这不会直接改变数据范围,却说明项目迁移时留下了旧产品命名。发布前应把这类标识纳入一致性检查,并在需要迁移旧数据时设计兼容读取,不能直接改名导致用户记录丢失。

九、用户笔记是隐私审计的重点

收藏和错题主要是应用生成的结构化记录,笔记则是用户自由输入文本。用户可能在笔记中写入姓名、联系方式、案件线索或其他个人内容,即使应用没有主动要求,也不能把它当成普通统计值。

当前源码把笔记保存在本机,没有上传、分享和云同步代码。更稳的工程边界是:

  • 不把笔记正文写入 hilog;
  • 不在异常信息中拼接完整笔记;
  • 默认不导出、不上传;
  • 提供删除入口;
  • 未来增加备份、分享或同步时重新做隐私评估;
  • 对导出内容提供范围预览和明确的用户触发。

技术文章和商店材料只能说明现有边界,不能把"未来可能支持"写成已经具备的能力。

十、清除全部数据有二次确认

设置页的清理动作不是一次点击立即执行。第一次点击将 pendingClear 设为 true,第二次才调用:

typescript 复制代码
this.favRecords = UserDataManager.clearFavorites()
this.noteRecords = UserDataManager.clearNotes()
this.wrongRecords = UserDataManager.clearWrong()
this.progressList = UserDataManager.clearProgress()
this.chapterProgressList = UserDataManager.clearChapterProgress()
this.examHistory = UserDataManager.clearExamHistory()

二次确认降低了误删风险,清理范围也覆盖了主要学习记录。需要注意的是,学习设置键没有在"清除所有学习数据"中重置:提醒时间、考试时长和自动下一题仍会保留。

这不一定是错误,但产品说明必须明确"清除学习记录"还是"恢复全部设置"。两个动作的范围不同,按钮文案和审核说明不能混用。

十一、删除成功提示目前缺少落盘证据

persist() 使用同步写入和刷新:

typescript 复制代码
UserDataManager.prefs.putSync(key, JSON.stringify(value))
UserDataManager.prefs.flushSync()

但异常被空 catch 吞掉:

typescript 复制代码
try {
  // 写入与刷新
} catch (_) {}

清理方法无论写入是否成功都会返回空数组,页面随后显示"所有学习数据已清除"。这会产生一种隐私风险:当前界面看似清空,应用重启后旧数据可能重新出现。

更稳的方案是让持久化层返回结果:

typescript 复制代码
interface PersistResult {
  success: boolean
  reason?: string
}

function clearLearningData(): PersistResult {
  // 全部键写入成功后才返回 success
  return { success: true }
}

页面只有在磁盘写入确认成功后才显示完成;失败时保留明确提示并允许重试。隐私删除的验收标准不是"列表瞬间变空",而是重启后仍为空。

十二、"每日学习提醒"目前只是偏好值

设置页允许选择提醒时间,并调用:

typescript 复制代码
UserDataManager.saveDailyReminderTime(value)

但是当前源码没有通知发布、提醒代理、定时任务或通知授权逻辑。也就是说,页面只是保存了一个时间字符串,并没有在指定时间真正提醒用户。

因此"每日学习提醒已设置为 09:00"会让用户误以为系统调度已经完成。发布前有两种真实选择:

  1. 改成"提醒时间偏好",明确当前只保存设置;
  2. 完成真实提醒实现,再同步权限、授权时机、拒绝路径和隐私说明。

不能为了让文案看起来完整而提前申请通知权限。权限应在功能真实交付后、用户主动开启提醒时按需处理。

十三、HomePage 与 Index 没有隐藏索权

Index.ets 负责手机底部导航和宽屏侧边导航,HomePage.ets 负责本地题库、学习统计和每日一题。两者都没有权限请求、网络调用或 Web 内容加载。

这个事实很适合写进首次启动验收:

  • 启动页不应出现批量权限框;
  • 首页在飞行模式下应正常展示;
  • 本地题库和学习记录不依赖账号;
  • 切换 phone、tablet、2in1 布局不应触发额外能力;
  • 只有用户主动点击语音时才进入 TTS 路径。

把能力触发点缩到用户动作附近,比在 EntryAbility 中提前初始化所有服务更容易解释和测试。

十四、资源与包描述出现旧产品残留

权限一致性之外,当前工程还有一个更直接的上架材料风险:entry/oh-package.json5 和根 oh-package.json5 的描述仍写着"中国方言题库",而 AppScope 的正式包名是 com.jiaweikang.one19,应用内容和页面品牌是知律。

类似残留还包括 Preferences 存储名 dialect_quiz。这些值未必全部展示给用户,但会让代码审计、依赖包说明和后续维护产生混淆。

发布前至少应对照:

  • AppScope 应用名与图标;
  • bundleName、versionName、versionCode;
  • entry 模块描述与 Ability 标签;
  • oh-package 名称、描述、作者;
  • 应用内"关于"页面;
  • AGC 标题、简介、分类与截图;
  • 隐私政策中的产品名和包名。

上架审核看的是完整产品,而不是只看首页截图。

十五、在线 TTS 让"数据保存在本机"需要限定语境

设置页点击"框架"后提示:

text 复制代码
页面采用 ArkTS 与 ArkUI 构建,数据保存在本机。

对收藏、笔记、错题和学习进度而言,这句话有源码依据。但在线 TTS 会接收应用提供的待合成文本,具体由哪个进程或服务处理、是否经过网络、保存多久,需要依据官方文档和实际服务行为确认。

所以更严谨的表达是:

收藏、笔记、错题、进度和考试记录保存在本机;语音示例优先使用离线引擎,在线语音兜底的处理方式以系统服务说明为准。

这种按数据类别拆分的说明,比一句"全部数据都只在本地"更经得起复核。

十六、第三方与平台 Kit 要分开说明

entry 的包依赖只有本地 libraryalibraryb,没有广告、统计、登录或支付 SDK。但 PracticePage 直接使用 @hms.ai.textToSpeech,它仍然属于需要在能力清单中说明的平台服务。

依赖审计应覆盖:

  • oh-package.json5 和锁文件;
  • ArkTS import;
  • HAR/HSP;
  • 原生 so;
  • SDK 初始化代码;
  • 网络域名和 AGC 服务开关;
  • 隐私政策中的第三方或平台服务列表。

"没有外部 npm 包"不等于"没有任何平台服务"。对外材料应按真实依赖类型准确分类。

十七、未来新增敏感权限时的状态模型

当前知律不需要运行时敏感权限,因此不应为了通用性硬塞授权框架。若以后增加相机扫描、文件导入或位置能力,可以再引入明确状态:

typescript 复制代码
type PermissionState =
  | 'not_requested'
  | 'granted'
  | 'denied'
  | 'blocked'
  | 'unavailable'

页面处理应与用户动作绑定:

状态 页面行为
not_requested 在功能入口解释用途
granted 执行对应能力
denied 保留降级功能,不重复骚扰
blocked 提供一次系统设置引导
unavailable 隐藏入口或说明设备不支持

授权失败不能阻断题库、笔记和错题等无权限功能,也不能在启动时连续弹出。

十八、把权限回归检查放进发布流水线

权限与隐私不是写完一次报告就结束。每次新增 Kit、依赖或页面入口,都可能扩大数据与权限面。可以在发布脚本中加入最小扫描:

powershell 复制代码
rg -n "requestPermissions|ohos.permission" entry/src/main
rg -n "@hms|@kit.NetworkKit|requestPermissionsFromUser" entry/src/main/ets
rg -n "http|Web\\(|fetch\\(|socket|cloud|push" entry/src/main/ets

扫描结果不能代替人工判断,但能发现变更。新增 INTERNET、CAMERA、LOCATION、通知、账号、推送或第三方 SDK 时,应强制更新:

  • 权限清单;
  • 数据流图;
  • 失败与拒绝路径;
  • 隐私政策;
  • AGC 数据安全与 SDK 字段;
  • 真机测试用例。

十九、真机验证矩阵

权限与隐私的验收必须覆盖"配置正确"和"失败可用"两方面:

场景 预期结果
首次启动 不弹相机、麦克风、位置等无关权限框
飞行模式进入首页 本地题库、统计和导航可用
飞行模式点击语音 离线引擎可用则播放,否则显示文字降级
在线 TTS 可用 仅用户点击语音后触发
TTS 服务异常 不崩溃,答题可以继续
新建笔记后重启 笔记仍存在
清除全部学习数据 二次确认后主要学习记录为空
清除后杀进程重启 已删除记录不恢复
设置提醒时间 不能把仅保存偏好宣传为已调度通知
卸载重装 本地学习记录不应自动恢复

如果某项无法在当前设备验证,应记录为"未验证",不能写成已经通过。

二十、上架前权限与隐私核对表

  • module.json5 只声明实际需要的 INTERNET;
  • 没有误加麦克风、相机、位置等权限;
  • 首次启动不请求无关权限;
  • 在线 TTS 的用途、触发时机和说明一致;
  • 不把 TTS 错误描述成麦克风权限拒绝;
  • 收藏、笔记、错题、进度和考试记录列入本地数据说明;
  • 用户笔记不上传、不写日志;
  • 清除范围与按钮文案一致;
  • 清除结果经过重启验证;
  • 每日提醒未实现调度时不宣称系统通知;
  • 无账号、云同步、广告和支付时不勾选对应能力;
  • 中国方言题库dialect_quiz 等旧命名已评估并清理或迁移;
  • AppScope、模块描述、关于页、AGC 和隐私政策产品名一致;
  • 安装、启动、核心流程、退出和卸载正常;
  • 平台服务清单包含实际使用的 TTS Kit。

二十一、常见问题排查

症状 先查哪里 正确处理
审核质疑为何联网 PracticePage 的 online TTS 说明用途并核对官方数据处理规则
页面提示麦克风授权 错把 TTS 当录音 删除无关权限和误导文案
飞行模式语音失败 设备无离线引擎 保留文字提示,不阻断答题
清空后重启数据回来 persist() 异常被吞掉 返回落盘结果,失败时提示重试
提醒时间到了没通知 只保存 Preferences 完成真实调度或修改产品文案
隐私政策写"不存数据" 忽略本地学习记录 按 Preferences 键逐项说明
材料出现旧产品名 项目迁移残留 全量核对 AppScope、模块和包描述
宣称云同步或在线排名 没有服务端实现 删除未实现能力宣称
启动时批量索权 权限申请位置错误 改为用户触发功能时按需申请

二十二、总结:权限少不代表说明可以简单

知律当前的权限面并不复杂:模块声明 INTERNET,主要用于离线 TTS 失败后的在线语音兜底;没有相机、麦克风、位置等敏感权限,也没有启动时运行时索权。收藏、笔记、错题、学习进度、章节进度、考试历史和设置通过 Preferences 保存在本机,设置页提供二次确认的学习数据清理入口。

真正需要在上架前继续收紧的,是事实一致性:

  • 在线 TTS 与"本机数据"说明要按能力拆分;
  • 清理成功必须以真实落盘结果为准;
  • 每日提醒当前只保存偏好,不能描述成已调度通知;
  • 模块描述和存储名中的旧产品残留要处理;
  • 任何新增权限都要同时补齐用途、拒绝、降级、隐私材料和测试。

权限合规不是把 requestPermissions 写得越短越好,而是让每一项声明都能在源码中找到唯一用途,让每一条用户说明都能被配置和运行结果证明。代码、权限、页面与上架材料说同一件事,才是可复核的 HarmonyOS 隐私工程。

本文部分内容由 AI 辅助整理。现有权限、TTS、本地存储、设置与清理行为均依据上述源码复核;关于持久化结果返回、权限状态模型、自动扫描和材料改写的内容属于基于当前实现边界的工程增强建议。

相关推荐
Kevin Coding12 小时前
JsonConvert:适用于 Android、鸿蒙与 Flutter 的 JSON 转 Model 插件
android·flutter·harmonyos
m0_7496902314 小时前
【寻迹校园 HarmonyOS NEXT 实战 28】不交换手机号也能交接:固定校内交接点的隐私设计
华为·harmonyos·arkts·产品设计·隐私设计·安全交接
Magic-ZYJ14 小时前
HarmonyOS Stage 模型实战:UIAbility 生命周期如何驱动页面安全状态
安全·华为·harmonyos·鸿蒙·移动端开发·独立开发者·心晴手记
贾伟康14 小时前
【中国方言题库|09】HarmonyOS ArkTS 方言搜索实战:实现词语检索和无结果反馈
harmonyos·arkts·状态管理·arkui·本地搜索
Dovis(誓平步青云)15 小时前
从手机单栏到平板分栏:任务清单的筛选、选中态与不可变更新
华为·harmonyos
梦想不只是梦与想15 小时前
鸿蒙 AppGallery Connect:查看应用信息(三)
harmonyos·appgallery·client id·app id·developer id
贾伟康15 小时前
【中国方言题库|03】HarmonyOS ArkTS 四川话分库实战:复用题库组件并保持地区参数清晰
harmonyos·arkts·arkui·路由传参·组件复用
贾伟康16 小时前
【中国方言题库|10】HarmonyOS ArkTS 语音播放实战:管理读音播放与页面生命周期
生命周期·harmonyos·arkts·语音合成·texttospeech
YM52e1 天前
鸿蒙ArkTS实战项目 - 门店陈列巡检台:巡检卡片与多列切换实现
学习·华为·harmonyos