权限与隐私检查最容易陷入两个误区:看到module.json5没有权限声明,就认为项目一定不存在隐私问题;或者写了一页很长的隐私政策,就认为代码行为已经被完整覆盖。真正可靠的结论必须同时核对能力声明、源码调用、本地数据、用户控制、应用内说明和发布材料。
本文基于化学实验室 1.0.5的真实 HarmonyOS ArkTS 工程。当前entry/src/main/module.json5没有requestPermissions,源码扫描也没有发现应用直接创建 HTTP、Web、Socket、定位、相机、麦克风、通讯录或相册访问调用。应用使用Preferences保存实验记录、收藏、笔记、答题记录和学习统计;阅读朗读由CoreSpeechKit的textToSpeech完成,没有录音或麦克风采集,但创建引擎时明确传入了online: 1。
这意味着实验、答题、笔记等核心路径符合本地优先和最小权限方向,但现有证据不足以把朗读功能写成“确定完全离线”。进一步核对还发现两类可验证的不一致:隐私页声称“清除全部数据”,设置页却没有清除all_favorites、daily_quiz_answers和wrong_questions;隐私页写明可通过帮助页邮箱联系,帮助页实际只引导到应用市场评论区。它们不属于权限弹窗问题,却直接影响用户对数据处理、控制入口和联系方式的理解。
隐私合规不是多写一页说明,而是让应用实际行为、权限声明、数据生命周期、拒绝路径和用户可见文案形成同一份可验证契约。
CHEM-LAB-ONE12-PRIVACY-CAPABILITY-CONTRACT-20260730
本文将完成以下内容:
- 读取
module.json5,确认当前权限与 Ability 声明。 - 扫描工程中的敏感 Kit、网络和授权调用。
- 区分文本转语音、麦克风录音与在线处理边界。
- 建立 Preferences 数据键、用途和删除方式清单。
- 复现“清除全部数据”实际漏清收藏与答题数据的问题。
- 设计统一数据注册表和可测试的清除服务。
- 核对隐私政策、设置页、帮助页与应用行为。
- 设计未来新增权限时的授权、拒绝、永久拒绝与降级状态。
- 给出自动扫描、运行验证和发布材料同步清单。
一、module.json5 当前没有请求用户权限
当前模块配置包含入口 Ability 和桌面卡片 ExtensionAbility,但没有requestPermissions:
{ "module": { "name": "entry", "type": "entry", "deviceTypes": ["phone", "tablet", "2in1"], "abilities": [ { "name": "EntryAbility", "exported": true, "skills": [ { "entities": ["entity.system.home"], "actions": ["ohos.want.action.home"] } ] } ], "extensionAbilities": [ { "name": "DailyStudyFormAbility", "type": "form" } ] } }exported: true用于主入口被系统桌面拉起,它与相机、定位等用户授权不是同一概念。桌面卡片 ExtensionAbility 同样不意味着自动获得敏感能力。当前配置没有ohos.permission.INTERNET,也没有位置、相机、麦克风、通讯录或媒体读写权限。
因此本文不会虚构“首次启动授权弹窗”。对当前版本而言,最好的拒绝路径就是根本不申请不需要的权限,让核心实验、阅读和答题功能在零敏感授权下可用。
二、只看清单还不够,必须扫描真实调用
权限声明可能落后于代码,代码也可能通过 SDK 间接使用能力。第一轮静态扫描应覆盖授权接口、敏感 Kit、网络、Web 和第三方 SDK:
rg -n --glob '*.ets' ` 'requestPermissionsFromUser|checkAccessToken|camera|microphone|geoLocation|contacts|picker|http\.createHttp|fetch\(|Web\(|analytics|advert|push|socket' ` entry/src/main/ets对本项目的扫描结果,没有发现运行时授权请求,也没有发现应用直接使用 HTTP、Web、Socket、定位、相机、麦克风、通讯录、相册、广告、统计或推送接口。module.json5与核心实验、答题和本地数据代码在“无敏感权限、无应用自建网络请求”这一点上是一致的;但ReadingSpeechService使用系统CoreSpeechKit,且引擎参数包含online: 1,不能把静态扫描结论扩大成“所有能力绝对不联网”。
静态搜索并不是最终证明。后续还要检查依赖清单、Core Speech Kit 个人数据处理说明、构建产物、真实设备系统权限页和朗读场景的运行网络行为。它能快速指出显式 API 与声明之间的冲突,却不能替代系统服务的数据流核验。
三、文本转语音不等于录音,但在线边界必须核验
帮助页写有“化学阅读可以朗读”,源码通过:
import { textToSpeech } from '@kit.CoreSpeechKit' ReadingSpeechService.engine = await textToSpeech.createEngine({ language: 'zh-CN', person: 0, online: 1 })这是把应用内文本转换为语音播放,不是采集用户声音。代码中没有audioCapturer、语音识别或麦克风授权调用,因此隐私页“不访问麦克风”的表述与当前实现一致。
但online: 1是当前源码不可忽略的事实。仅凭这段代码和无INTERNET声明,不能进一步断言朗读文本绝不会经过在线处理;失败文案却写着“当前设备不支持离线朗读”,也与引擎配置形成了需要澄清的语义偏差。正确做法是核对当前 SDK 版本的官方 API 与个人数据处理说明,在断网和联网条件下实测,并据此决定是改为真正的端侧模式,还是在隐私页中披露朗读文本的处理边界。结论确认前,文案只能承诺“不录音、不读取麦克风”,不能承诺“朗读内容绝不联网”。
四、真正的数据边界在 Preferences
DataStore.ets使用:
import { preferences } from '@kit.ArkData' const PREF_NAME = 'physics_app_data' DataStore.prefInstance = await preferences.getPreferences(context, PREF_NAME)虽然应用名是“化学实验室”,Preferences 文件仍叫physics_app_data。这个名称不会把数据发送给其他应用,但会增加维护和迁移时的误解。若直接改名,旧版本数据会看似丢失;更安全的做法是保留旧名或实现一次性迁移,再删除旧存储。
隐私页说明数据保存在应用沙箱中,源码确实使用 Preferences,没有云同步、账号、导出或上传逻辑。这里的关键任务不是增加权限,而是把每一个键的用途、保留期和删除入口盘点清楚。
五、建立本地数据清单
从DataStore.ets、答题页和统计页可以整理出当前主要键:
| 数据键 | 内容 | 产生场景 | 用户可见用途 |
|---|---|---|---|
favorite_experiments | 旧版实验 ID 列表 | 收藏实验 | 收藏兼容 |
all_favorites | 多类型收藏对象 | 收藏实验、文章、公式 | 收藏列表 |
experiment_records | 实验记录数组 | 完成模拟 | 实验记录与统计 |
user_notes | 用户笔记 | 新建或编辑笔记 | 笔记管理 |
experiment_count | 实验次数 | 完成实验 | 学习统计 |
learning_seconds | 学习秒数 | 阅读或学习 | 学习统计 |
learning_minutes | 旧版分钟数 | 旧版本兼容 | 数据迁移 |
daily_quiz_answers | 每日答题历史 | 提交每日题 | 答题统计 |
exam_practice_records | 专项练习记录 | 提交试卷 | 正确率与进度 |
wrong_questions | 错题记录 | 回答错误 | 错题本 |
这些内容不包含姓名、手机号、位置或设备标识,但笔记内容由用户手动输入,仍应在隐私页中明确说明其本地保存和删除方式。不能因为数据只在本机,就忽略数据生命周期。
六、隐私页的本地数据说明基本正确
PrivacyPolicyPage.ets写明应用保存:实验次数、学习时长、收藏 ID、笔记和偏好设置,并声明数据只保存在本机 Preferences。这个方向与源码一致。
但当前文案遗漏了每日答题历史、专项练习记录和错题记录。统计页明确读取:
const answersJson = await DataStore.getString('daily_quiz_answers', '[]') const examJson = await DataStore.getString('exam_practice_records', '[]') const wrongJson = await DataStore.getString('wrong_questions', '[]')建议把“答题记录、专项练习进度、错题记录”加入本地数据说明,并解释用途是恢复学习进度与生成本机统计。这样政策文本不是笼统写“设置等数据”,而是能与真实键映射。
七、清除按钮没有覆盖全部本地数据
设置页当前的确定操作是:
await DataStore.putString('favorite_experiments', '[]') await DataStore.putString('experiment_records', '[]') await DataStore.putString('user_notes', '[]') await DataStore.putNumber('experiment_count', 0) await DataStore.putNumber('learning_minutes', 0) await DataStore.putNumber('learning_seconds', 0) await DataStore.putString('exam_practice_records', '[]')它遗漏了至少三个正在使用的键:
all_favoritesdaily_quiz_answerswrong_questions
其中all_favorites的影响最直观。loadAllFavorites()会读取新收藏列表,再把旧版favorite_experiments合并进去。只清空旧键、不清空all_favorites,用户返回收藏页后仍能看到收藏项目。界面提示“已清除本机学习数据”,隐私页又声称可以清除全部数据,实际行为却不符合这两个承诺。
八、把数据键集中为唯一注册表
最稳妥的修复不是在设置页再补三行字符串,而是把所有持久化键集中定义:
export class PreferenceKeys { static readonly FAVORITE_EXPERIMENTS = 'favorite_experiments' static readonly ALL_FAVORITES = 'all_favorites' static readonly EXPERIMENT_RECORDS = 'experiment_records' static readonly USER_NOTES = 'user_notes' static readonly EXPERIMENT_COUNT = 'experiment_count' static readonly LEARNING_MINUTES = 'learning_minutes' static readonly LEARNING_SECONDS = 'learning_seconds' static readonly DAILY_QUIZ_ANSWERS = 'daily_quiz_answers' static readonly EXAM_PRACTICE_RECORDS = 'exam_practice_records' static readonly WRONG_QUESTIONS = 'wrong_questions' }业务页面、统计页、清除服务和测试都引用同一常量。新增持久化数据时,代码审查可以要求同时更新注册表、隐私清单和清除用例。
九、清除逻辑应由 DataStore 负责
设置页不应知道每个存储键。把删除编排放到数据服务:
static async clearLearningData(): Promise<void> { const emptyListKeys: string[] = [ PreferenceKeys.FAVORITE_EXPERIMENTS, PreferenceKeys.ALL_FAVORITES, PreferenceKeys.EXPERIMENT_RECORDS, PreferenceKeys.USER_NOTES, PreferenceKeys.DAILY_QUIZ_ANSWERS, PreferenceKeys.EXAM_PRACTICE_RECORDS, PreferenceKeys.WRONG_QUESTIONS ] for (const key of emptyListKeys) { await DataStore.putString(key, '[]') } await DataStore.putNumber(PreferenceKeys.EXPERIMENT_COUNT, 0) await DataStore.putNumber(PreferenceKeys.LEARNING_MINUTES, 0) await DataStore.putNumber(PreferenceKeys.LEARNING_SECONDS, 0) await DataStore.refreshStatsSnapshot() }页面只调用DataStore.clearLearningData(),根据成功或失败显示结果。未来增加新数据键时,不必在 UI 里寻找零散清除代码。
十、清除操作必须返回真实结果
当前DataStore.putString()和putNumber()在异常时会吞掉错误,设置页无论是否写入成功都会显示“已清除本机学习数据”。隐私控制属于用户明确操作,不能用乐观提示替代结果。
可以让服务返回结构化结果:
interface ClearResult { success: boolean clearedKeys: string[] failedKeys: string[] } const result = await DataStore.clearLearningData() if (result.success) { this.showToast('已清除本机学习数据') } else { this.showToast(`部分数据未清除,请重试`) }失败提示不必暴露内部文件路径,但日志应记录失败键,便于定位存储异常。用户再次进入收藏、笔记、记录和统计页时,应看到一致的空状态。
十一、删除后要刷新内存快照
DataStore.notifyStatsChanged()会更新AppStorage中的收藏数、实验次数、学习秒数和版本号,但新增的all_favorites、错题和每日答题清除也要触发相关页面刷新。
清除完成后建议执行:
await DataStore.refreshStatsSnapshot() AppStorage.setOrCreate<number>('privacy_data_version', Date.now())依赖统计的页面监听版本变化后重新加载。否则 Preferences 已经清空,当前页面仍可能展示删除前的内存数组,用户会误以为删除失败。
十二、确认对话框要准确描述范围
当前对话框写着“将清除本机保存的实验记录、收藏、笔记和学习统计,且不可恢复”。修复后还应补充答题与错题数据,避免用户只想清实验记录却误删学习进度。
推荐文案:
将清除本机保存的实验记录、全部收藏、笔记、答题记录、错题和学习统计。 此操作不可恢复,不会删除应用内置实验与文章。是否继续?“取消”保持为主安全路径,“确定清除”使用危险状态色。删除开始后禁用重复点击,结束后再显示成功或部分失败结果。
十三、隐私页与帮助页的联系方式不一致
隐私页写明:可通过“我的-帮助与反馈”页面中的邮箱联系。但HelpPage.ets实际只有:
如遇到实验、阅读或答题异常,可在应用市场评论区反馈具体页面和操作步骤。页面没有邮箱地址,也没有邮件跳转。二者必须选择一个真实方案:
- 在帮助页提供有效邮箱,并说明用户不要在邮件中发送不必要的个人信息。
- 删除隐私页的“邮箱”表述,改为当前真实可用的应用市场评论渠道。
不能保留一个用户无法找到的联系入口。修改后还要核对 AGC 隐私声明中的开发者联系方式,避免应用内外再次不一致。
十四、政策版本与应用版本要建立更新规则
隐私页显示应用版本 1.0.5,但生效日期固定为 2024 年 4 月 1 日。仅从源码无法判断该日期是否仍代表最新政策,因此发布前应由负责人确认。
建议维护明确元数据:
export class PrivacyMeta { static readonly POLICY_VERSION = '1.0.1' static readonly EFFECTIVE_DATE = '2026-07-17' static readonly APP_VERSION = Constants.APP_VERSION }只有数据类型、权限、第三方 SDK、联网行为或联系方式发生变化时才更新政策版本和日期,不能每次构建自动改日期。更新记录应能说明“改了什么、为什么改、从何时生效”。
十五、无权限版本也需要拒绝路径设计
当前应用没有权限弹窗,但仍有能力不可用场景。例如朗读引擎创建失败时,ReadingSpeechService返回“当前设备不支持离线朗读”;由于同一配置包含online: 1,这条提示应改成不预设实现方式的“当前设备暂不支持朗读”。这是一种设备或服务能力降级,不是权限拒绝。
需要区分:
| 状态 | 当前例子 | 用户反馈 | 核心功能是否可用 |
|---|---|---|---|
| 无需权限 | 实验、笔记、收藏 | 直接使用 | 是 |
| 能力不支持 | 朗读引擎不可用 | 显示准确原因 | 是,仍可阅读 |
| 本地写入失败 | Preferences 异常 | 提示保存失败或重试 | 是,可能不持久化 |
| 用户取消危险操作 | 取消清除 | 保持数据不变 | 是 |
这种状态划分能避免把所有失败都误写成“请授权”。
十六、未来新增权限时使用四态模型
如果未来真的增加拍照记录实验、语音提问或位置服务,应先判断功能是否必要,再设计:
enum PermissionState { NotRequired, NotRequested, Granted, Denied } interface CapabilityDecision { state: PermissionState canContinueWithoutPermission: boolean userMessage: string }业务流程必须覆盖首次请求、用户拒绝、再次进入和系统设置中撤销。拒绝后应保留不依赖该权限的功能,不能让整个应用停在空白页,也不能循环弹出授权请求。
十七、权限申请要在功能触发时发生
未来需要敏感能力时,不应在冷启动批量请求。正确时机是用户主动点击对应功能后:
- 先用一句话说明用途和不授权的影响。
- 用户继续后调用系统授权界面。
- 授权成功进入功能。
- 拒绝后返回可用页面并提供替代方式。
- 后续撤销时再次检查,不缓存永久 granted。
例如拍照实验记录被拒绝后,可以允许用户继续保存纯文字笔记;语音输入被拒绝后,保留键盘输入。权限只决定增量功能,不应破坏实验、答题、笔记等本地核心学习路径。
十八、声明、调用和文案必须同批修改
新增能力时至少同步六处:
module.json5 权限声明 ArkTS 能力调用与授权状态 功能页用途说明和拒绝降级 PrivacyPolicyPage 数据与权限章节 HelpPage 使用说明和联系方式 AGC 隐私声明、权限与 SDK 字段若只改module.json5,会出现申请了权限却没有可达功能;若只写代码不加声明,功能运行失败;若代码和声明都改了但隐私页没改,用户看到的解释仍不真实。把六处作为同一个任务的验收项,比发布前临时补文案可靠。
十九、第三方 SDK 的结论也要有证据
隐私页声明未集成统计、广告或推送 SDK。源码扫描没有发现相关调用,但还应检查oh-package.json5与锁文件:
Get-Content .\oh-package.json5 Get-Content .\oh-package-lock.json5 rg -n 'analytics|advert|ads|push|track|oaid' .系统 Kit 不等于第三方 SDK,桌面卡片也不等于推送。结论要以依赖和调用为依据。未来引入任何 SDK 时,应记录 SDK 名称、版本、用途、处理数据、隐私链接和关闭方式。
二十、把隐私契约分成四层
推荐结构如下:
Capability Layer module 声明、Kit 调用、系统授权状态 Data Layer Preferences 键、用途、保留、迁移、删除 UX Layer 用途说明、拒绝降级、失败反馈、删除确认 Release Layer 应用内政策、用户协议、AGC 隐私与权限字段实际行为是最底层真源,上层说明不能超过或弱化代码行为。发布材料声称离线时,工程不能隐藏联网;政策声称可删除全部数据时,设置按钮必须覆盖所有键。
二十一、建立自动化一致性扫描
一个轻量脚本可以在每次发布前生成审计摘要:
interface PrivacyAudit { declaredPermissions: string[] sensitiveApiHits: string[] networkHits: string[] localDataKeys: string[] clearDataKeys: string[] missingClearKeys: string[] } const missingClearKeys = localDataKeys.filter( (key) => !clearDataKeys.includes(key) )当前项目的检查应能指出all_favorites、daily_quiz_answers、wrong_questions不在设置页清除集合。脚本只负责发现差异,最终仍要由开发者判断某个键是否属于用户数据、是否需要保留迁移标记以及删除后如何刷新 UI。
二十二、权限与网络运行验证
在真实设备或模拟器上执行以下步骤:
| 操作 | 预期结果 | 通过标准 |
|---|---|---|
| 首次安装并启动 | 不出现敏感权限弹窗 | 首页可直接使用 |
| 打开实验、答题、笔记 | 不要求联网或登录 | 本地核心功能完整 |
| 查看系统应用权限页 | 无相机、麦克风、位置等授权 | 与 module 声明一致 |
| 断网启动和切换页面 | 页面与本地数据可用 | 无网络错误阻断 |
| 断网点击文章朗读 | 记录引擎创建、播放和失败状态 | 不出现麦克风授权,文案不误导 |
| 联网点击文章朗读 | 与断网结果及网络日志对比 | 确认online: 1的真实数据流 |
| 前后台切换 | 不产生隐藏授权或异常弹窗 | 状态保持正常 |
运行时可以配合网络日志和系统权限页检查。若后续依赖升级后出现新权限或联网,应立即回到声明与隐私清单重新审计。
二十三、本地数据删除验证
删除测试要先造数据,再检查所有入口:
- 收藏一个实验、一篇文章和一个公式。
- 完成一次实验并创建一条笔记。
- 提交每日答题、专项练习并产生一个错题。
- 进入统计页记录当前数量。
- 在设置中点击清除,先取消,确认数据不变。
- 再次清除并确认。
- 重新进入收藏、记录、笔记、统计、错题和答题页。
- 杀进程后重启,再检查一次。
| 检查点 | 预期结果 |
|---|---|
| 取消清除 | 所有数据保持不变 |
| 确认清除 | 所有用户生成学习数据为空 |
| 内存页面 | 不显示旧数组或旧计数 |
| 冷启动 | 已清数据不会恢复 |
| 内置内容 | 实验、文章、公式仍存在 |
| 失败场景 | 不显示虚假成功提示 |
二十四、隐私页面验收
| 文案项 | 源码证据 | 验收标准 |
|---|---|---|
| 本地核心功能 | 无 INTERNET 与应用自建 HTTP/Web | 断网可完成实验、答题、笔记 |
| 朗读处理边界 | CoreSpeechKit且配置online: 1 | 依据官方说明和运行证据准确披露 |
| 不申请敏感权限 | module 无 requestPermissions | 系统权限页一致 |
| 本地保存 | Preferencesphysics_app_data | 数据不离开沙箱 |
| 数据类型 | 收藏、记录、笔记、答题、错题、统计 | 政策逐类覆盖 |
| 清除入口 | 我的-设置-清除实验数据 | 覆盖全部注册键 |
| 卸载删除 | 应用沙箱随卸载移除 | 重装不恢复旧数据 |
| 联系方式 | 帮助页或真实邮箱 | 用户能实际到达 |
| 第三方 SDK | 依赖与调用扫描 | 声明与依赖一致 |
二十五、构建与发布前检查
源码修改后运行项目既有构建:
$env:DEVECO_SDK_HOME='D:\huawei\DevEco Studio\sdk' $env:PATH='D:\huawei\DevEco Studio\jbr\bin;' + $env:PATH & 'D:\huawei\DevEco Studio\tools\hvigor\bin\hvigorw.bat' --no-daemon assembleHap发布前再核对:
- 包内
module.json5与源码声明一致。 - 应用内隐私政策版本和联系方式正确。
- 权限、联网、第三方 SDK 与 AGC 字段如实填写。
- 桌面卡片不引入隐藏网络或额外数据处理。
- 清除入口可达,确认、失败和成功状态完整。
- 安装、启动、核心流程与卸载正常。
华为官方发布指引强调应用上线前需要完成隐私、安全、兼容性与稳定性检查,并根据实际情况如实填写隐私声明。这里的“实际”应由源码与运行证据支持,而不是仅依赖产品描述。
二十六、常见错误与修复策略
没有权限声明,就不检查数据
本地笔记、收藏和答题记录仍有保留与删除生命周期。修复方式是建立逐键数据清单和清除验证。
清除按钮逐行写键
新增功能后容易漏清。修复方式是集中PreferenceKeys和DataStore.clearLearningData(),并由测试比较写入键与清除键集合。
把朗读误写成录音,或反过来直接写成离线
朗读是文本转语音,不读取麦克风;但当前引擎配置包含online: 1,也不能在没有证据时写成“离线处理”。修复方式是按实际 Kit、配置参数、官方数据说明和运行证据描述。
联系方式只存在于政策文字
用户找不到真实入口。修复方式是提供可达邮箱,或把政策改为帮助页实际支持的反馈渠道。
新增权限后只修改 module.json5
缺少用途说明、拒绝降级和隐私更新。修复方式是把代码、声明、UX、政策和 AGC 字段作为同一个变更交付。
二十七、源码事实与建议实现的边界
本文提出的数据注册表、四态权限模型和自动审计脚本是建议方案,并非当前工程已经实现的能力。下面将可直接复核的源码事实与后续目标分开。
| 维度 | 当前源码事实 | 本文建议实现 |
|---|---|---|
| 权限声明 | module.json5没有requestPermissions | 保持最小权限,新增能力时按用途同批声明 |
| 应用网络接口 | 扫描未发现应用直接创建 HTTP、Web 或 Socket | 构建产物和运行日志继续验证,结论不扩展到系统服务 |
| 文本朗读 | 使用CoreSpeechKit.textToSpeech,未读取麦克风 | 核对官方数据说明,明确端侧或在线处理边界 |
| 朗读配置 | createEngine参数包含online: 1 | 根据真实产品要求调整配置,并同步隐私与失败文案 |
| 本地存储 | DataStore使用 Preferences,名称为physics_app_data | 保留兼容或实施可验证迁移,不能直接改名丢失旧数据 |
| 清除入口 | 设置页逐项写入空值,漏掉多个实际数据键 | 建立键注册表,由DataStore返回结构化清除结果 |
| 清除反馈 | 写入方法吞掉异常,设置页仍提示已清除 | 只有持久化确认成功后显示成功,并刷新内存快照 |
| 联系方式 | 隐私页说帮助页有邮箱,帮助页只指向评论区 | 二者统一为真实可达渠道 |
| 数据说明 | 隐私页列出实验、收藏、笔记和偏好 | 补充答题、专项练习、错题与朗读处理边界 |
| 第三方 SDK | 当前源码和依赖扫描未见广告、统计、推送 SDK | 每次依赖变化重新生成 SDK 与数据处理清单 |
| 拒绝路径 | 当前版本没有敏感授权弹窗 | 未来按可申请、已拒绝、永久拒绝、能力不可用分别降级 |
| 构建与真机结果 | 本文未声称建议改造已构建或上机验证 | 修改后执行权限页、断网、朗读、清除和卸载回归 |
特别需要强调:当前源码能证明“朗读不采集麦克风”,却不能在online: 1存在时证明“朗读一定完全离线”。隐私结论必须停在证据边界内,待官方资料与运行验证完成后再更新政策。
二十八、结语
化学实验室当前没有敏感权限声明,实验、答题、笔记等核心数据保存在 Preferences,源码也没有应用自建 HTTP、Web 或 Socket。与此同时,朗读引擎配置包含online: 1,因此“完全离线”的政策表述需要单独核验,不能由无INTERNET权限直接推出。真正需要修复的并不是“少了一个授权弹窗”,而是朗读处理边界、本地数据清单与删除行为之间的证据偏差。
通过真实源码可以看到:新旧收藏同时存在,设置页只清空旧收藏键;统计页读取每日答题和错题,清除操作却没有覆盖;隐私页指向邮箱,帮助页没有邮箱。把这些差异修复后,再用数据注册表、结构化清除结果、内存刷新和回归表固化,隐私承诺才能长期跟随功能演进。
权限最小化只是起点。最终交付标准是用户能够理解应用做什么、拒绝或退出后会发生什么、哪些数据保存在本机、如何真正删除,以及应用内外所有说明都能被代码和运行结果证明。
参考资料
- Huawei Developer,提交 HarmonyOS 应用:
https://developer.huawei.com/consumer/cn/app/submit - Huawei Developer,在应用市场分发:
https://developer.huawei.com/consumer/cn/appgallery/devstart/ - Huawei Developer,数据管理概述:
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/data-mgmt-overview - 项目配置:
entry/src/main/module.json5 - 项目源码:
entry/src/main/ets/views/mine/PrivacyPolicyPage.ets - 项目源码:
entry/src/main/ets/views/mine/SettingsPage.ets - 项目源码:
entry/src/main/ets/views/mine/HelpPage.ets - 项目源码:
entry/src/main/ets/utils/DataStore.ets - 项目源码:
entry/src/main/ets/utils/ReadingSpeechService.ets - Huawei Developer,Core Speech Kit:
https://developer.huawei.com/consumer/cn/sdk/core-speech-kit/ - Huawei Developer,Core Speech Kit API:
https://developer.huawei.com/consumer/cn/doc/harmonyos-references/core-speech-api
AI 辅助声明:本文由 AI 辅助完成源码梳理、结构校对与配图设计,技术结论均以文中列出的本地 ArkTS 源码、模块配置和官方资料为依据,并由作者复核后发布。