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,你可以輕鬆實現應用配置的持久化存儲,提升用户體驗。