一、问题现象

实现了完整的隐私政策流程:启动页检查 → 未同意就跳转隐私政策页 → 用户同意后保存状态 → 跳转登录页。看起来一切正常,但每次重新启动应用,又回到了隐私政策页面,之前的同意状态完全没有保存下来。

二、问题排查思路

遇到这种持久化失败的问题,我按照以下思路排查:

  1. 数据有没有保存? 检查保存代码是否执行,有没有异常

  2. 数据保存到哪里了? 确认使用的存储方式和路径

  3. 数据读取对不对? 检查读取时的key、默认值、类型转换

  4. 保存和读取是否一致? 确认使用的preferences名称是否相同

三、问题根源分析

经过排查,发现最常见的原因有三个:

原因一:忘记调用flush()方法

这是最容易忽略的!HarmonyOS的preferences不会自动持久化,必须显式调用flush()方法才能将数据写入磁盘。很多开发者以为调用了put()就完成了保存,但实际上数据还在内存中,应用退出后就丢失了。

错误写法:

await dataPreferences.put('hasAgreedPrivacy', true);
// 没有调用flush(),数据没有持久化!

正确写法:

await dataPreferences.put('hasAgreedPrivacy', true);
await dataPreferences.flush();  // 必须调用!

原因二:保存和读取使用了不同的preferences名称

preferences.getPreferences()的第二个参数是name,如果保存时用'AppPreferences',读取时用'appPreferences'(注意大小写),就会读取到一个全新的空数据。

错误示例:

// 保存时
const prefs = await preferences.getPreferences(context, 'AppPreferences');
await prefs.put('hasAgreedPrivacy', true);

// 读取时
const prefs = await preferences.getPreferences(context, 'appPreferences'); // 名称不一致!
const agreed = await prefs.get('hasAgreedPrivacy', false); // 永远返回false

原因三:异常被吞掉没有发现

如果保存或读取过程中抛出异常,但被try-catch捕获后只是打印日志,没有妥善处理,就会导致流程继续执行,但数据实际上没有保存成功。

四、完整解决方案

根据上面的分析,我总结了一套完整的解决方案:

第一步:修复保存逻辑

在隐私政策同意页面,添加完整的保存逻辑:

private async handleAgree() {
  if (!this.isAgreed) return;

  try {
    // 1. 获取正确的Context类型
    const context = getContext(this) as common.UIAbilityContext;
    
    // 2. 使用统一的preferences名称
    const dataPreferences = await preferences.getPreferences(context, 'AppPreferences');
    
    // 3. 保存数据
    await dataPreferences.put('hasAgreedPrivacy', true);
    await dataPreferences.put('privacyAgreeTime', Date.now());
    
    // 4. 关键:必须调用flush()持久化到磁盘
    await dataPreferences.flush();
    
    // 5. 验证是否保存成功(可选,用于调试)
    const saved = await dataPreferences.get('hasAgreedPrivacy', false);
    console.info('保存验证结果:', saved);
    
    if (saved) {
      // 延迟跳转,确保数据已写入
      setTimeout(() => {
        router.replaceUrl({ url: 'pages/LoginPage' });
      }, 500);
    }
    
  } catch (err) {
    console.error('保存失败:', JSON.stringify(err));
    // 提示用户重试
    promptAction.showToast({ message: '保存失败,请重试' });
  }
}

关键点说明:

  • 必须使用 UIAbilityContext 类型
  • preferences名称要保持一致,建议定义成常量
  • 保存完必须调用 flush()
  • 建议添加验证步骤,确保保存成功
  • 延迟跳转给系统留出写入时间

第二步:修复读取逻辑

在启动检查页面,确保正确读取:

private async checkPrivacyAgreement() {
  try {
    // 1. 获取Context
    const context = getContext(this) as common.UIAbilityContext;
    
    // 2. 使用相同的preferences名称
    const dataPreferences = await preferences.getPreferences(context, 'AppPreferences');
    
    // 3. 读取数据,注意类型转换
    const hasAgreed = await dataPreferences.get('hasAgreedPrivacy', false) as boolean;
    
    console.info('隐私政策状态:', hasAgreed);

    // 4. 严格判断
    if (hasAgreed === true) {
      router.replaceUrl({ url: 'pages/LoginPage' });
    } else {
      router.replaceUrl({ url: 'pages/PrivacyAgreementPage' });
    }
    
  } catch (err) {
    console.error('检查失败:', JSON.stringify(err));
    // 发生错误时,默认跳转到隐私政策页(更安全)
    router.replaceUrl({ url: 'pages/PrivacyAgreementPage' });
  }
}

关键点说明:

  • preferences名称必须与保存时完全一致
  • 使用 as boolean 明确类型转换
  • 使用严格相等判断(=== true)
  • 异常情况默认跳转到隐私政策页

第三步:添加调试日志

在关键位置添加日志,方便排查问题:

// 保存时
console.info('[Privacy] 开始保存...');
await dataPreferences.put('hasAgreedPrivacy', true);
await dataPreferences.flush();
console.info('[Privacy] 保存完成');

// 读取时
console.info('[Splash] 开始检查...');
const hasAgreed = await dataPreferences.get('hasAgreedPrivacy', false);
console.info('[Splash] 读取结果:', hasAgreed);

通过日志可以清楚看到:

  • 保存是否执行了
  • 保存的值是什么
  • 读取到的值是什么
  • 是否有异常发生

五、验证方法

修改完代码后,需要彻底验证是否解决:

验证步骤1:完全卸载重装

不要只是重新运行,要完全卸载后重装,确保清除所有数据。首次启动应该显示隐私政策页。

验证步骤2:同意并重启

点击"同意并继续"后,完全退出应用(不是切到后台),然后重新打开。这次应该直接进入登录页,不再显示隐私政策页。

验证步骤3:多次重启测试

连续重启3-5次,每次都应该直接进入登录页,确保状态持久化稳定。

验证步骤4:查看日志

在DevEco Studio的日志窗口筛选关键词,检查:

  • 保存时日志显示"保存完成"
  • 读取时日志显示"读取结果: true"
  • 没有任何异常信息

六、最佳实践建议

通过这次踩坑,我总结了一些最佳实践:

建议1:将preferences名称定义为常量

在一个专门的配置文件中定义:

export const PREFERENCES_NAME = 'AppPreferences';
export const KEY_PRIVACY_AGREED = 'hasAgreedPrivacy';
export const KEY_PRIVACY_TIME = 'privacyAgreeTime';

使用时直接引用常量,避免拼写错误。

建议2:封装preferences操作

创建一个工具类统一管理:

export class PreferencesUtil {
  private static prefsName = 'AppPreferences';
  
  static async savePrivacyAgreed(context: Context, agreed: boolean): Promise<boolean> {
    try {
      const prefs = await preferences.getPreferences(context, this.prefsName);
      await prefs.put('hasAgreedPrivacy', agreed);
      await prefs.put('privacyAgreeTime', Date.now());
      await prefs.flush();
      return true;
    } catch (err) {
      console.error('保存失败:', err);
      return false;
    }
  }
  
  static async getPrivacyAgreed(context: Context): Promise<boolean> {
    try {
      const prefs = await preferences.getPreferences(context, this.prefsName);
      return await prefs.get('hasAgreedPrivacy', false) as boolean;
    } catch (err) {
      console.error('读取失败:', err);
      return false;
    }
  }
}

这样做的好处:

  • 统一管理preferences名称
  • 统一处理异常
  • 确保flush()不会遗漏
  • 代码复用性强

建议3:添加测试功能

在开发阶段,可以在设置页面添加"重置隐私设置"按钮:

Button('重置隐私设置')
  .onClick(async () => {
    const context = getContext(this) as common.UIAbilityContext;
    const prefs = await preferences.getPreferences(context, 'AppPreferences');
    await prefs.delete('hasAgreedPrivacy');
    await prefs.delete('privacyAgreeTime');
    await prefs.flush();
    promptAction.showToast({ message: '已重置,重启应用生效' });
  })

方便反复测试首次启动的流程。

建议4:设置数据过期时间(可选)

如果需要定期让用户重新确认隐私政策,可以这样实现:

const agreeTime = await prefs.get('privacyAgreeTime', 0) as number;
const now = Date.now();
const sixMonths = 180 * 24 * 60 * 60 * 1000; // 6个月

if (now - agreeTime > sixMonths) {
  // 超过6个月,需要重新同意
  router.replaceUrl({ url: 'pages/PrivacyAgreementPage' });
}

七、总结

隐私政策每次都要同意的问题,核心原因就是数据没有正确持久化。记住三个关键点:

  1. 保存时必须调用 flush() 方法
  2. 保存和读取必须使用相同的 preferences 名称
  3. 添加充分的日志和异常处理
Logo

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

更多推荐