Harmony开发之轻量级数据存储——Preferences实战

Harmony开发之轻量级数据存储——Preferences实战

引入:用户设置的持久化保存

在日常应用开发中,我们经常需要保存用户的个性化设置,比如主题颜色、字体大小、通知开关等。这些数据虽然量不大,但需要在应用重启后依然保持有效。HarmonyOS提供的Preferences(用户首选项)正是解决这类问题的轻量级数据存储方案。

一、Preferences核心概念

什么是Preferences?

Preferences是HarmonyOS提供的轻量级键值对存储方案,具有以下特点:

  • 键值对存储:采用Key-Value形式,简单高效
  • 数据类型支持:支持数字、字符串、布尔值及其数组类型
  • 持久化存储:数据自动保存到应用沙箱,重启后依然存在
  • 轻量级设计:适合存储用户配置、应用设置等少量数据

适用场景

  • 用户个性化设置(主题、语言等)
  • 应用配置信息
  • 用户偏好记录
  • 简单的状态保存

二、基础使用:增删改查

1. 导入模块与获取实例

// 文件:EntryAbility.ets
import { preferences } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';

export default class EntryAbility extends UIAbility {
  private pref: preferences.Preferences | null = null;
  
  onWindowStageCreate(windowStage: window.WindowStage) {
    // 获取Preferences实例
    preferences.getPreferences(this.context, 'myAppPrefs')
      .then((pref) => {
        this.pref = pref;
        console.info('Preferences实例获取成功');
      })
      .catch((err: BusinessError) => {
        console.error(`获取Preferences失败: ${err.code}, ${err.message}`);
      });
  }
}

2. 写入数据

// 文件:pages/SettingsPage.ets
@Component
struct SettingsPage {
  @State darkMode: boolean = false;
  @State fontSize: number = 16;
  
  // 保存设置
  async saveSettings() {
    try {
      const pref = await preferences.getPreferences(getContext(this), 'myAppPrefs');
      await pref.put('darkMode', this.darkMode);
      await pref.put('fontSize', this.fontSize);
      await pref.flush(); // 必须调用flush才能持久化
      console.info('设置保存成功');
    } catch (err) {
      console.error('保存设置失败', err);
    }
  }
  
  build() {
    Column() {
      Toggle({ type: ToggleType.Switch, isOn: this.darkMode })
        .onChange((value: boolean) => {
          this.darkMode = value;
          this.saveSettings();
        })
      Slider({ value: this.fontSize, min: 12, max: 32 })
        .onChange((value: number) => {
          this.fontSize = value;
          this.saveSettings();
        })
    }
  }
}

3. 读取数据

// 文件:pages/Index.ets
@Entry
@Component
struct Index {
  @State darkMode: boolean = false;
  @State fontSize: number = 16;
  
  aboutToAppear() {
    this.loadSettings();
  }
  
  async loadSettings() {
    try {
      const pref = await preferences.getPreferences(getContext(this), 'myAppPrefs');
      this.darkMode = await pref.get('darkMode', false);
      this.fontSize = await pref.get('fontSize', 16);
    } catch (err) {
      console.error('读取设置失败', err);
    }
  }
  
  build() {
    Column() {
      Text('当前主题: ' + (this.darkMode ? '深色' : '浅色'))
      Text('字体大小: ' + this.fontSize)
    }
    .backgroundColor(this.darkMode ? '#333' : '#FFF')
    .fontColor(this.darkMode ? '#FFF' : '#000')
    .fontSize(this.fontSize)
  }
}

4. 删除数据

// 删除单个键值对
async deleteKey(key: string) {
  const pref = await preferences.getPreferences(getContext(this), 'myAppPrefs');
  await pref.delete(key);
  await pref.flush();
}

// 清空所有数据
async clearAll() {
  const pref = await preferences.getPreferences(getContext(this), 'myAppPrefs');
  await pref.clear();
  await pref.flush();
}

三、数据变更订阅

Preferences支持数据变更监听,当数据发生变化时可以触发回调:

// 文件:pages/SettingsPage.ets
@Component
struct SettingsPage {
  private pref: preferences.Preferences | null = null;
  
  aboutToAppear() {
    this.initPreferences();
  }
  
  async initPreferences() {
    this.pref = await preferences.getPreferences(getContext(this), 'myAppPrefs');
    
    // 订阅数据变更
    this.pref.on('change', (key: string) => {
      console.info(`数据发生变化: ${key}`);
      // 可以在这里更新UI或执行其他操作
    });
  }
  
  aboutToDisappear() {
    // 取消订阅,避免内存泄漏
    if (this.pref) {
      this.pref.off('change');
    }
  }
}

四、工具类封装

为了提高代码复用性,建议对Preferences进行封装:

// 文件:utils/PreferencesUtils.ts
import { preferences } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';

type ValueType = number | string | boolean | Array<number> | Array<string> | Array<boolean>;

class PreferencesUtils {
  private static instance: PreferencesUtils;
  private pref: preferences.Preferences | null = null;
  
  static getInstance(): PreferencesUtils {
    if (!PreferencesUtils.instance) {
      PreferencesUtils.instance = new PreferencesUtils();
    }
    return PreferencesUtils.instance;
  }
  
  // 初始化Preferences
  async init(context: common.Context, name: string = 'appPrefs'): Promise<void> {
    try {
      this.pref = await preferences.getPreferences(context, name);
    } catch (err) {
      const error = err as BusinessError;
      console.error(`初始化Preferences失败: ${error.code}, ${error.message}`);
      throw err;
    }
  }
  
  // 保存数据
  async put(key: string, value: ValueType): Promise<void> {
    if (!this.pref) {
      throw new Error('Preferences未初始化');
    }
    await this.pref.put(key, value);
    await this.pref.flush();
  }
  
  // 读取数据
  async get<T extends ValueType>(key: string, defaultValue: T): Promise<T> {
    if (!this.pref) {
      return defaultValue;
    }
    return await this.pref.get(key, defaultValue) as T;
  }
  
  // 检查是否包含某个key
  async has(key: string): Promise<boolean> {
    if (!this.pref) {
      return false;
    }
    return await this.pref.has(key);
  }
  
  // 删除数据
  async delete(key: string): Promise<void> {
    if (!this.pref) {
      return;
    }
    await this.pref.delete(key);
    await this.pref.flush();
  }
  
  // 清空所有数据
  async clear(): Promise<void> {
    if (!this.pref) {
      return;
    }
    await this.pref.clear();
    await this.pref.flush();
  }
}

export default PreferencesUtils.getInstance();

五、实战案例:主题设置

// 文件:pages/ThemeSetting.ets
import PreferencesUtils from '../utils/PreferencesUtils';

@Entry
@Component
struct ThemeSetting {
  @State currentTheme: string = 'light';
  @State fontSize: number = 16;
  
  aboutToAppear() {
    this.loadSettings();
  }
  
  async loadSettings() {
    try {
      this.currentTheme = await PreferencesUtils.get('theme', 'light');
      this.fontSize = await PreferencesUtils.get('fontSize', 16);
    } catch (err) {
      console.error('读取设置失败', err);
    }
  }
  
  async saveTheme(theme: string) {
    try {
      await PreferencesUtils.put('theme', theme);
      this.currentTheme = theme;
    } catch (err) {
      console.error('保存主题失败', err);
    }
  }
  
  async saveFontSize(size: number) {
    try {
      await PreferencesUtils.put('fontSize', size);
      this.fontSize = size;
    } catch (err) {
      console.error('保存字体大小失败', err);
    }
  }
  
  build() {
    Column() {
      // 主题选择
      Row() {
        Button('浅色主题')
          .onClick(() => this.saveTheme('light'))
        Button('深色主题')
          .onClick(() => this.saveTheme('dark'))
      }
      
      // 字体大小设置
      Row() {
        Text('字体大小:')
        Slider({ value: this.fontSize, min: 12, max: 32 })
          .onChange((value: number) => this.saveFontSize(value))
        Text(`${this.fontSize}px`)
      }
    }
    .backgroundColor(this.currentTheme === 'dark' ? '#333' : '#FFF')
    .fontColor(this.currentTheme === 'dark' ? '#FFF' : '#000')
  }
}

六、最佳实践与注意事项

1. 性能优化

  • 批量操作:尽量减少flush()调用次数,可以批量修改多个值后一次性flush
  • 避免频繁读写:频繁的磁盘IO会影响性能,建议将频繁修改的数据缓存在内存中
  • 合理分文件:根据功能模块将数据存储到不同的Preferences文件中

2. 数据安全

  • 不存储敏感信息:Preferences不支持加密存储,不要存储密码、token等敏感数据
  • 数据验证:读取数据时提供默认值,避免空指针异常
  • 错误处理:使用try-catch包装所有Preferences操作

3. 内存管理

  • 及时释放:页面销毁时取消数据变更订阅,避免内存泄漏
  • 控制数据量:建议存储的数据不超过一万条,避免内存占用过大

4. 数据类型限制

  • 键值类型:Key为string类型,长度不超过1024字节
  • 值类型:支持number、string、boolean及其数组类型
  • 字符串长度:string类型值长度不超过16MB

总结

Preferences是HarmonyOS中简单易用的轻量级数据持久化方案,非常适合存储用户设置和应用配置信息。通过本文的学习,你已经掌握了Preferences的基本使用方法、数据变更订阅机制以及工具类封装技巧。

行动建议

  • 在EntryAbility中初始化Preferences实例
  • 使用工具类封装提高代码复用性
  • 合理使用数据变更订阅机制
  • 遵循最佳实践,避免性能问题和内存泄漏
  • 不要存储敏感信息到Preferences中

通过合理使用Preferences,你可以轻松实现应用配置的持久化存储,提升用户体验。

posted @ 2025-12-24 10:35  wrystart  阅读(2)  评论(0)    收藏  举报