HarmonyOS应用实战-启示散页-18-隐私弹窗不要变成摆设:把本地数据说明和能力开关接到真实流程

这一组文章继续围绕 The_Book_of_Answers 展开。它不是一次性页面 Demo,而是一个小型 HarmonyOS 离线应用:默认题库从 rawfile 播种,题库、收藏、历史写进 Preferences,抽取流程经过 DrawingPage,发布前还要说明隐私、备份、包体和日志口径。

轻量应用最容易被误判为“页面写完就结束”。实际交付时,真正拖慢排障的通常不是某个 ArkUI 组件,而是状态从哪里来、数据由谁写、页面靠什么刷新、异常能不能恢复。下面每篇只拆一个问题,并尽量把它落到答案之书已有的页面、Service、Repository、AppStorage、资源或发布配置边界上。

在这里插入图片描述

这篇文章解决四件事:

  1. 复盘 隐私弹窗不要变成摆设 在答案之书项目里怎么出现。
  2. 把本地数据说明和能力开关接到真实流程 落到页面、Service、Repository 和 AppStorage 的具体边界。
  3. 给出能迁移到 HarmonyOS/ArkTS 项目的代码形态,并指出反例。
  4. 用验证清单和排障表收尾,避免只看源码不看实际入口。

在这里插入图片描述
在这里插入图片描述

1. 从故障链路看:隐私弹窗不要变成摆设

答案之书没有账号和服务器,但会保存用户输入的问题、自建题库、收藏和历史。如果隐私弹窗只是启动时点一下,后续能力照常跑,就没有真正表达用户选择。 这个问题如果只在当前页面补一个判断,短期可能能跑,但下一次从历史、收藏、分享或恢复入口进入时,同样的问题还会出现。先把故障链路写出来,才能知道该修页面、服务还是持久层。

触发点:隐私弹窗不要变成摆设
错误写法:页面直接判断或直接读写持久化数据
放大后果:返回重进、前后台切换、删除/恢复、发布排查都会看到旧状态
收口位置:PrivacyGateService 处理业务规则,PrivacyGatePage 只消费结果态
刷新方式:业务动作成功后写 AppStorageKey.PrivacyAcceptedAt

2. 先把边界表写清楚

隐私选择属于启动门禁和设置页,不属于具体题库页面。未同意之前,不应该播种、写历史或保存收藏;同意后才进入完整数据链路。 这张表的作用是防止后面写代码时顺手越界。尤其是答案之书这种本地应用,很多问题看起来都能在页面里临时解决,但页面一旦知道太多存储细节,发布后的排障成本会明显升高。

层级 在这篇里的职责 不应该做的事
PrivacyGatePage 展示、点击、跳转、订阅刷新信号 直接拼 Preferences key 或修复脏数据
PrivacyGateService 校验输入、组装 PrivacyConsentState、决定空态和恢复路径 持有 ArkUI 组件状态
PrivacyRepository 稳定读写本地实体和索引 判断页面怎么展示
AppStorage 传递 AppStorageKey.PrivacyAcceptedAt 这类轻量刷新信号 保存完整业务对象

3. PrivacyConsentState 只表达页面结果,不照搬存储实体

PrivacyConsentState 是给 PrivacyGatePage 消费的结果模型,不是 Preferences 里的原始结构。这样做的好处是:Repository 可以继续按本地存储优化字段,页面仍然拿到稳定、可渲染、可判断动作的结果。

interface PrivacyConsentState {
  accepted: boolean;
  acceptedAt?: number;
  policyVersion: number;
  declinedReason?: string;
}

4. PrivacyGateService 才是规则 owner

PrivacyGateService 负责把 PrivacyRepository 读出的数据转成页面能用的结果。空值、损坏、回退和默认值都应该在这一层处理,页面不需要知道底层为什么缺字段。

class PrivacyGateService {
  async load(deckId: string): Promise<PrivacyConsentState> {
    const state: PrivacyConsentState = await PrivacyRepository.load();
    if (!state.accepted || state.policyVersion < CURRENT_POLICY_VERSION) {
      return { accepted: false, policyVersion: CURRENT_POLICY_VERSION };
    }
    return state;
  }
}

5. PrivacyGatePage 不直接碰持久化

PrivacyGatePage 的职责应该保持轻:进入时加载,收到信号时重载,用户点击时发起明确动作。这样页面不会同时背上 Preferences、业务规则、错误恢复和发布排查四种职责。

@Component
struct PrivacyGatePage {
  @State private privacyConsentState: PrivacyConsentState | null = null;
  @StorageLink('privacyAcceptedAt') @Watch('reload')
  private changedAt: number = 0;
  private currentDeckId: string = '';

  async aboutToAppear(): Promise<void> {
    await this.reload();
  }

  private async reload(): Promise<void> {
    const deckId: string = BookRouteGuard.requireDeckParam({ deckId: this.currentDeckId });
    this.privacyConsentState = await new PrivacyGateService().load(deckId);
  }
}

6. 反例:把所有逻辑塞回页面会怎样

这个反例在第一版开发时很常见,因为写起来快;但它把题库读取、空态判断、展示模型转换和刷新信号都揉在组件里。后续一旦增加 Sheet、深链、恢复页或平板布局,就会出现多个入口行为不一致。

// 反例:页面同时知道数据结构、业务规则和刷新方式
const deck = await PrivacyRepository.loadDeck(this.currentDeckId);
if (deck === null) {
  promptAction.showToast({ message: '暂无数据' });
  return;
}
this.privacyConsentState = deck as unknown as PrivacyConsentState;
AppStorage.setOrCreate(AppStorageKey.PrivacyAcceptedAt, Date.now());

7. 刷新信号只写 AppStorageKey.PrivacyAcceptedAt,不要广播完整对象

AppStorageKey.PrivacyAcceptedAt 是运行期联动,不是数据仓库。业务动作成功后只写一个时间戳,页面收到后再按自己的 owner 重拉数据,可以避免跨页面共享可变对象。

class BookRefreshCenter {
  static notifyPrivacyConsentStateChanged(): void {
    AppStorage.setOrCreate(AppStorageKey.PrivacyAcceptedAt, Date.now());
  }

  static async afterBusinessAction(action: () => Promise<void>): Promise<void> {
    await action();
    this.notifyPrivacyConsentStateChanged();
  }
}

8. 入口参数要先校验,再进入业务

答案之书的入口不只首页一个:历史再次提问、收藏再次提问、分享导入、恢复页都可能进入 RouteName.Home。目标页先校验参数,错误进入可恢复路径,不要让空 deckId 流到 Service 深处才爆出难懂异常。

class BookRouteGuard {
  static requireDeckParam(param: object | undefined): string {
    const deckId = (param as Record<string, string> | undefined)?.deckId ?? '';
    if (!deckId) {
      throw new Error('缺少题库 id,不能进入 RouteName.Home');
    }
    return deckId;
  }
}

9. 排查命令围绕 owner 搜,不围绕页面猜

排查时不要只盯着出问题的 UI。先搜 Service、模型、刷新信号,再看页面是否绕过了 owner。这样可以分清是数据没写、结果没组装,还是页面没订阅刷新。

rg -n "PrivacyGateService|PrivacyConsentState|AppStorageKey.PrivacyAcceptedAt" D:\ProgramData\huawei\lesson\The_Book_of_Answers
rg -n "RouteName.Home|PrivacyGatePage" D:\ProgramData\huawei\lesson\The_Book_of_Answers
rg -n "PrivacyRepository|AppStorage.setOrCreate" D:\ProgramData\huawei\lesson\The_Book_of_Answers

10. 验证要覆盖正常路径和损坏路径

清数据首次启动、拒绝后退出、同意后进入、升级隐私版本后再次提示、撤回同意后重新打开,确认数据流程和提示一致。

建议至少按这四组走:

  1. 清应用数据后的首次进入。
  2. 有自建题库、收藏和历史后的返回重进。
  3. 手工制造空值、重复值或损坏数据后的恢复路径。
  4. 发布态检查日志和截图,确认没有把用户问题、答案全文或题库全文暴露出去。
验收口径:
1. 正常入口可用。
2. 异常入口有提示或恢复页。
3. 返回重进不显示旧数据。
4. AppStorageKey.PrivacyAcceptedAt 变化后只刷新对应 owner。
5. 发布态不输出敏感明文。

11. 落地时的取舍

这里没有把 把本地数据说明和能力开关接到真实流程 做成很重的框架能力,是因为答案之书的核心仍然是离线、轻量、可维护。过度抽象会让一个小功能穿过太多层;完全写在页面里,又会让数据修复、备份恢复、发布排障没有稳定入口。比较合适的取舍是:用户内容、持久化结构、跨页面刷新和发布自查进入 Service 或 Repository;只影响当前视觉节奏的内容留在页面。

适合抽出去:
- PrivacyConsentState
- PrivacyGateService
- AppStorageKey.PrivacyAcceptedAt

不急着抽出去:
- 当前页面的一次性动画状态
- 只影响局部样式的临时 UI 变量
- 不跨页面复用的按钮交互

12. 常见问题与处理

现象 先看哪里 处理
未同意也写入历史 启动顺序是否绕过 Gate 数据初始化放到同意之后
升级隐私不再提示 policyVersion 是否比较 版本变化重新进入说明页
AGC 说明不一致 发布文案是否同步 隐私政策写明本地数据和系统备份
复查顺序:
1. PrivacyRepository 是否返回可信数据。
2. PrivacyGateService 是否统一处理空值和异常。
3. PrivacyGatePage 是否绕过 Service。
4. AppStorageKey.PrivacyAcceptedAt 是否在业务动作成功后写入。
5. 发布态日志是否隐藏用户输入和答案全文。

验证清单

  • 清应用数据后进入 PrivacyGatePage,确认默认题库、页面状态和刷新信号都能闭环。
  • 从首页、Sheet、历史、收藏、分享或恢复入口触发一次,确认 RouteName.Home 的参数校验稳定。
  • 手工制造空值、重复数据或损坏数据,确认错误停在 PrivacyGateService 或恢复页,而不是让页面崩掉。
  • 触发业务动作后观察 AppStorageKey.PrivacyAcceptedAt,确认只有相关页面重拉数据,没有全局乱刷新。
  • 如果涉及主题、资源、布局、隐私或发布态,必须用真机截图、构建产物或发布清单补充确认。

小结

隐私弹窗不要变成摆设:把本地数据说明和能力开关接到真实流程 不是一个孤立 API 问题,而是答案之书这种离线应用在长期维护里一定会遇到的边界问题。把 PrivacyConsentStatePrivacyGateServicePrivacyRepositoryPrivacyGatePageAppStorageKey.PrivacyAcceptedAt 分清以后,项目继续扩展题库、收藏、历史、分享、恢复和发布诊断时,才不会把每个入口都写成一次性的临时逻辑。
不是一个孤立 API 问题,而是答案之书这种离线应用在长期维护里一定会遇到的边界问题。把 PrivacyConsentStatePrivacyGateServicePrivacyRepositoryPrivacyGatePageAppStorageKey.PrivacyAcceptedAt 分清以后,项目继续扩展题库、收藏、历史、分享、恢复和发布诊断时,才不会把每个入口都写成一次性的临时逻辑。

Logo

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

更多推荐