前言

在鸿蒙(HarmonyOS)应用开发中,数据持久化是保障用户体验的核心环节。当用户设置了深色主题、记住了登录状态或完成了首次引导后,即使应用关闭甚至设备重启,这些状态也不应丢失。Preferences(用户首选项) 正是为此类轻量级数据设计的官方存储方案。它类似于 Android 的 SharedPreferences 或 iOS 的 UserDefaults,专为保存用户的个性化设置等小规模数据而设计。


一、 核心概念与运作机制

Preferences 本质上是一个内存优先 + 异步持久化 + 线程安全的 Key-Value 键值对存储系统。

  • 支持类型:Key 必须为字符串;Value 支持 numberstringboolean 以及这三种类型的数组。
  • 运作机制:所有的读写操作(get/put)首先直接命中内存中的哈希表,速度极快(微秒级)。当需要落盘时,通过调用 flush() 接口将数据异步写入到应用沙箱内的文件中。

二、 基础使用实战

从 API Version 18 开始,推荐使用 @kit.ArkData 进行导入。以下展示一个完整的增删改查流程:

import { preferences } from '@kit.ArkData';
import { promptAction, getContext } from '@kit.ArkUI';

@Entry
@Component
struct PreferencesDemo {
  // 声明 Preferences 实例
  store: preferences.Preferences | null = null;

  // 页面即将显示时初始化仓库
  aboutToAppear(): void {
    this.store = preferences.getPreferencesSync(getContext(this), {
      name: 'user-settings' // 仓库名称,唯一标识
    });
  }

  build() {
    Column({ space: 15 }) {
      Button('保存主题设置')
        .onClick(() => {
          if (!this.store) return;
          // 1. 写入数据到内存
          this.store.putSync('theme', 'dark');
          this.store.putSync('fontSize', 16);
          // 2. 【关键】手动触发持久化到磁盘
          this.store.flush(); 
          promptAction.showToast({ message: '保存成功' });
        });

      Button('读取主题设置')
        .onClick(() => {
          if (!this.store) return;
          // 3. 读取数据(需提供默认值)
          const theme = this.store.getSync('theme', 'light');
          promptAction.showToast({ message: `当前主题为: ${theme}` });
        });

      Button('清除特定配置')
        .onClick(() => {
          if (!this.store) return;
          // 4. 删除指定键值对并持久化
          this.store.deleteSync('fontSize');
          this.store.flush();
        });
    }
    .width('100%').height('100%').justifyContent(FlexAlign.Center)
  }
}

三、 进阶封装:打造全局同步工具类

在实际工程中,为了避免在每个页面重复编写 try-catch 和获取实例的逻辑,建议封装一个基于同步接口的静态工具类:

import { preferences } from '@kit.ArkData';

export class PrefUtil {
  private static store: preferences.Preferences | null = null;

  // 初始化(建议在 EntryAbility 的 onCreate 中调用)
  static init(context: Context) {
    if (!this.store) {
      this.store = preferences.getPreferencesSync(context, { name: 'appConfig' });
    }
  }

  // 同步保存
  static save(key: string, value: preferences.ValueType) {
    this.store?.putSync(key, value);
    this.store?.flushSync(); // 立即同步落盘
  }

  // 同步读取
  static load<T extends preferences.ValueType>(key: string, defaultValue: T): T {
    return this.store?.getSync(key, defaultValue) as T;
  }
}

四、 UI 状态联动:结合 @StorageLink

在实际开发中,Preferences 往往需要直接驱动 UI 的更新(例如切换夜间模式)。由于 getSync 是一次性读取,无法自动刷新界面,因此官方推荐将 Preferences 与全局状态容器 AppStorage 结合使用。

// 1. 在 EntryAbility 或初始化阶段,将配置同步到 AppStorage
let store = preferences.getPreferencesSync(context, { name: 'appConfig' });
let isDarkMode = store.getSync('isDarkMode', false);
AppStorage.setOrCreate('isDarkMode', isDarkMode);

// 2. 在页面组件中使用 @StorageLink 实现双向绑定
@Entry
@Component
struct SettingsPage {
  // 建立双向绑定:UI 修改会自动触发 AppStorage 和底层存储更新
  @StorageLink('isDarkMode') darkMode: boolean = false; 

  build() {
    Row() {
      Text('深色模式')
      Toggle({ type: ToggleType.Switch, isOn: this.darkMode })
        .onChange((isOn: boolean) => {
          this.darkMode = isOn;
          // 写入本地持久化
          let store = preferences.getPreferencesSync(getContext(this), { name: 'appConfig' });
          store.putSync('isDarkMode', isOn);
          store.flush();
        })
    }
  }
}

五、 数据变更监听:on('change')

当某个配置项被修改时,如果需要在不重启应用的情况下让其他组件做出响应,可以使用 on('change') 订阅数据变化事件。

aboutToAppear(): void {
  const store = preferences.getPreferencesSync(getContext(this), { name: 'appConfig' });
  
  // 注册监听器:仅当指定的 key 发生变化时触发
  store.on('change', 'fontSize', (key: string, value: preferences.ValueType) => {
    console.info(`监听到 ${key} 发生变化,新值为: ${value}`);
    // 在这里执行 UI 刷新或重新计算布局等逻辑
  });
}

aboutToDisappear(): void {
  const store = preferences.getPreferencesSync(getContext(this), { name: 'appConfig' });
  // 【关键】页面销毁时务必取消监听,防止内存泄漏
  store.off('change'); 
}

六、 跨模块数据共享:跨 HAR/HSP 访问

在复杂的鸿蒙工程中,通常会有多个业务模块(如 entry 模块和 feature_user 模块)。如果需要跨模块共享首选项,需借助宿主模块的 Context。

import { common } from '@kit.AbilityKit';

// 在 feature_user 模块中读取 entry 模块的配置
let context = getContext(this) as common.UIAbilityContext;
// 注意:这里使用的是当前上下文的文件路径
let store = preferences.getPreferencesSync(context, { name: 'global_config' });

七、 ⚠️ 约束限制与避坑指南

在使用 Preferences 时,务必注意以下硬性限制,否则可能导致文件损坏或严重的性能问题:

  1. 容量限制:它是轻量级存储,强烈建议单个文件的数据量不超过 50MB(部分场景建议不超过 1万条数据)。若在主线程加载过大的 XML 文件,会导致严重的卡顿甚至 AppFreeze。
  2. 并发安全:默认的 XML 存储模式不支持多进程并发读写,强行使用会有文件损坏风险。如果是多进程场景,请使用 GSKV 存储模式(API 18+),该模式以二进制存储并支持并发实时落盘。
  3. 数据安全:Preferences 不支持底层加密。如果需要存储密码等敏感信息,请开发者自行加密后,再以 Uint8Array 形式存入。
  4. 生命周期:首选项文件保存在应用私有沙箱内,应用被卸载时数据会被彻底删除,因此绝不能将其作为关键数据的云端备份手段。

💡 架构选型决策树

虽然 Preferences 极其好用,但它有严格的边界。请遵循以下决策树进行选型:

  • 选 Preferences:用户设置、Token 缓存、引导页标记、简单的草稿箱(< 50MB)。
  • 选 RDB(关系型数据库):联系人列表、聊天记录、商品库(结构化强、数据量大、需要复杂 SQL 查询)。
  • 选 KV-Store(分布式键值对):需要多设备无缝流转、跨端实时同步的数据。
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐