摘要

Preferences 是鸿蒙轻量本地持久化存储,适合存储简单键值配置(登录 Token、主题开关、用户基础配置、页面缓存参数),区别于 RDB 关系数据库:仅支持字符串、数字、布尔、数组基础类型,无需建表,读写速度更快。API23 重构 Preferences 实例生命周期、多实例隔离、同步 / 异步读写、数据自动落盘逻辑,修复旧版多页面并发读写错乱、修改后重启丢失、实例未关闭占用文件、批量写入卡顿等问题。本文封装全局 Preferences 工具类,封装布尔、字符串、数字、数组存取、删除 key、清空全部数据全套方法,搭配登录缓存、深色模式两套实战页面,给出实例销毁、并发读写、持久化同步规范,是项目基础存储必备底层工具。

关键词

OpenHarmony;ArkTS;API23;Preferences;轻量存储;键值持久化;本地配置缓存

一、引言

1.1 Preferences 适用场景区分

  1. Preferences(优先):简单配置、开关、token、账号缓存、主题状态,数据量小、读写频繁;
  2. RDB:结构化多条业务数据(笔记、通讯录、商品列表),支持分页、条件查询;
  3. fs 文件:图片、日志、大文本二进制文件;
  4. AppStorage:运行时全局内存状态,重启丢失,需搭配 Preferences 持久化。

API23 Preferences 核心升级变更:

  1. 强制每个存储文件独立实例,禁止全局单例长期占用;
  2. 新增自动 flush 落盘机制,修改后实时写入本地,防止重启数据丢失;
  3. 优化并发读写锁,多页面同时存取不会出现数据覆盖错乱;
  4. 区分同步 get/set 与异步 getAsync/setAsync 两套 API,主线程推荐异步避免 UI 卡顿;
  5. 废弃不关闭 getPreferences 实例写法,页面销毁必须 release 释放文件句柄;
  6. 支持基础数组类型直接存取,无需手动 JSON 序列化转换。

1.2 模块与基础初始化

依赖模块:@ohos.data.preferences 无需动态权限,应用沙盒私有存储自动拥有读写权限 基础概念:

  • 存储文件名:区分不同业务配置,如user_configapp_setting
  • Preferences 实例:操作文件的句柄,用完必须 release 释放

二、API23 Preferences 原生基础示例

ets

import preferences from '@ohos.data.preferences'
import common from '@ohos.app.ability.common'

async function simplePrefDemo(ctx: common.UIAbilityContext) {
  // 获取配置文件实例
  let pref = await preferences.getPreferences(ctx, "user_config")
  // 写入布尔值
  await pref.putBool("is_login", true)
  // 强制落地本地文件
  await pref.flush()
  // 读取布尔值,默认false
  const login = await pref.getBool("is_login", false)
  // 释放实例
  await pref.release()
}

三、全局 Preferences 工具封装 utils/preference_util.ets

ets

import preferences from '@ohos.data.preferences'
import common from '@ohos.app.ability.common'
import promptAction from '@ohos.promptAction'

// 配置文件常量,拆分业务存储文件,避免单文件过大
export const PREF_FILE = {
  USER: "user_config",    // 用户登录、token、账号
  SETTING: "app_setting"   // 主题、系统开关配置
}

class PrefUtil {
  private static instance: PrefUtil
  private ctx: common.UIAbilityContext | null = null
  // 缓存已创建的pref实例,避免重复创建
  private prefMap: Map<string, preferences.Preferences> = new Map()

  static getInstance(): PrefUtil {
    if (!PrefUtil.instance) {
      PrefUtil.instance = new PrefUtil()
    }
    return PrefUtil.instance
  }

  // 注入页面/Ability上下文
  setContext(context: common.UIAbilityContext) {
    this.ctx = context
  }

  // 根据文件名获取pref实例,复用缓存
  private async getPref(fileName: string): Promise<preferences.Preferences | null> {
    if (!this.ctx) {
      promptAction.showToast({ message: "存储上下文未初始化" })
      return null
    }
    if (this.prefMap.has(fileName)) {
      return this.prefMap.get(fileName)!
    }
    const pref = await preferences.getPreferences(this.ctx, fileName)
    this.prefMap.set(fileName, pref)
    return pref
  }

  // 写入布尔值
  async putBool(file: string, key: string, value: boolean): Promise<boolean> {
    const pref = await this.getPref(file)
    if (!pref) return false
    try {
      await pref.putBool(key, value)
      await pref.flush()
      return true
    } catch (err) {
      console.error("putBool error", err)
      return false
    }
  }

  // 读取布尔值,def默认值
  async getBool(file: string, key: string, def: boolean): Promise<boolean> {
    const pref = await this.getPref(file)
    if (!pref) return def
    return await pref.getBool(key, def)
  }

  // 写入字符串
  async putString(file: string, key: string, value: string): Promise<boolean> {
    const pref = await this.getPref(file)
    if (!pref) return false
    await pref.putString(key, value)
    await pref.flush()
    return true
  }

  // 读取字符串
  async getString(file: string, key: string, def: string): Promise<string> {
    const pref = await this.getPref(file)
    if (!pref) return def
    return await pref.getString(key, def)
  }

  // 写入数字
  async putNumber(file: string, key: string, value: number): Promise<boolean> {
    const pref = await this.getPref(file)
    if (!pref) return false
    await pref.putNumber(key, value)
    await pref.flush()
    return true
  }

  // 读取数字
  async getNumber(file: string, key: string, def: number): Promise<number> {
    const pref = await this.getPref(file)
    if (!pref) return def
    return await pref.getNumber(key, def)
  }

  // 写入字符串数组
  async putStringArr(file: string, key: string, arr: string[]): Promise<boolean> {
    const pref = await this.getPref(file)
    if (!pref) return false
    await pref.putStringArray(key, arr)
    await pref.flush()
    return true
  }

  // 读取字符串数组
  async getStringArr(file: string, key: string, def: string[]): Promise<string[]> {
    const pref = await this.getPref(file)
    if (!pref) return def
    return await pref.getStringArray(key, def)
  }

  // 删除单个key
  async deleteKey(file: string, key: string): Promise<boolean> {
    const pref = await this.getPref(file)
    if (!pref) return false
    await pref.delete(key)
    await pref.flush()
    return true
  }

  // 清空当前文件全部键值
  async clearFile(file: string): Promise<boolean> {
    const pref = await this.getPref(file)
    if (!pref) return false
    await pref.clear()
    await pref.flush()
    return true
  }

  // 释放全部缓存实例(页面/应用退出调用)
  async releaseAll() {
    for (let [_, pref] of this.prefMap) {
      await pref.release()
    }
    this.prefMap.clear()
  }
}

export default PrefUtil.getInstance()

四、两大业务实战页面

4.1 实战一:设置页(深色模式持久化)

ets

import PrefUtil, { PREF_FILE } from '../utils/preference_util'
import { STORAGE_KEY } from '../model/Constant'

@Entry
@Component
struct SettingPage {
  @State darkMode: boolean = false

  async aboutToAppear() {
    // 绑定页面上下文
    const ctx = getContext(this)
    PrefUtil.setContext(ctx)
    // 读取本地持久化深色模式配置
    this.darkMode = await PrefUtil.getBool(PREF_FILE.SETTING, STORAGE_KEY.DARK_MODE, false)
  }

  // 切换深色模式,同步写入本地存储
  async toggleDark(val: boolean) {
    this.darkMode = val
    await PrefUtil.putBool(PREF_FILE.SETTING, STORAGE_KEY.DARK_MODE, val)
    // 同步更新全局AppStorage状态,所有页面UI自动刷新
    AppStorage.Set(STORAGE_KEY.DARK_MODE, val)
  }

  build() {
    Column({ space: 25 }) {
      Text("系统设置(Preferences持久化演示)")
        .fontSize(22)
        .fontWeight(FontWeight.Bold)

      Row() {
        Text("开启深色模式").layoutWeight(1).fontSize(18)
        Toggle({ isOn: this.darkMode })
          .onChange((val: boolean) => this.toggleDark(val))
      }
      .width("90%")
      .padding(16)
      .backgroundColor(Color.White)
      .borderRadius(12)
    }
    .width("100%")
    .height("100%")
    .padding(20)
    .backgroundColor(this.darkMode ? "#181818" : "#f5f5f5")
  }

  async aboutToDisappear() {
    // 页面退出不释放全局实例,应用退出统一释放
  }
}

4.2 实战二:登录页面(账号、Token 本地缓存)

ets

import PrefUtil, { PREF_FILE } from '../utils/preference_util'
import HttpUtil from '../utils/http_util'
import RouterUtil from '../utils/router_util'
import { STORAGE_KEY, PAGE_ROUTE } from '../model/Constant'
import AppStorage from '@ohos.arkui.state.AppStorage'
import promptAction from '@ohos.promptAction'

@Entry
@Component
struct LoginPage {
  @State account: string = ""
  @State password: string = ""

  async aboutToAppear() {
    const ctx = getContext(this)
    PrefUtil.setContext(ctx)
    // 读取上次登录账号回填输入框
    this.account = await PrefUtil.getString(PREF_FILE.USER, "last_account", "")
  }

  async handleLogin() {
    if (!this.account || !this.password) {
      promptAction.showToast({ message: "账号密码不能为空" })
      return
    }
    // 调用登录接口
    const res = await HttpUtil.post<{ token: string }>("/login", {
      username: this.account,
      password: this.password
    })
    if (!res) return
    // 持久化存储token、账号
    await PrefUtil.putString(PREF_FILE.USER, "last_account", this.account)
    await PrefUtil.putString(PREF_FILE.USER, STORAGE_KEY.TOKEN, res.data.token)
    // 更新全局登录状态
    AppStorage.Set(STORAGE_KEY.IS_LOGIN, true)
    AppStorage.Set(STORAGE_KEY.TOKEN, res.data.token)
    promptAction.showToast({ message: "登录成功" })
    RouterUtil.replace(PAGE_ROUTE.INDEX)
  }

  // 退出登录清空用户存储
  async logout() {
    await PrefUtil.clearFile(PREF_FILE.USER)
    AppStorage.Set(STORAGE_KEY.IS_LOGIN, false)
    AppStorage.Set(STORAGE_KEY.TOKEN, "")
    RouterUtil.replace(PAGE_ROUTE.LOGIN)
  }

  build() {
    Column({ space: 22 }) {
      Text("用户登录-账号本地缓存").fontSize(24).margin({ top: 60 })
      TextInput({ text: this.account, placeholder: "请输入账号" })
        .width("90%")
        .height(48)
        .onChange((val: string) => this.account = val)
      TextInput({ text: this.password, placeholder: "请输入密码" })
        .width("90%")
        .height(48)
        .type(InputType.Password)
        .onChange((val: string) => this.password = val)
      Button("登录")
        .width("90%")
        .height(48)
        .backgroundColor("#007DFF")
        .onClick(() => this.handleLogin())
      Button("清空登录缓存(退出登录)")
        .width("90%")
        .height(48)
        .backgroundColor("#f56c6c")
        .onClick(() => this.logout())
    }
    .width("100%")
    .height("100%")
    .padding(20)
  }
}

五、API23 Preferences 编码与性能规范

5.1 文件拆分规范

  1. 按业务拆分存储文件:用户信息user_config、系统设置app_setting
  2. 禁止所有键值存入同一个文件,读写效率随数据量增大下降;
  3. 临时缓存、长期配置分开存储,清空操作互不干扰。

5.2 生命周期释放规范

  1. 工具内部缓存 pref 实例,避免频繁创建销毁损耗性能;
  2. 应用完全退出、EntryAbility onDestroy 调用releaseAll()释放全部文件句柄;
  3. 单个页面临时专用配置可页面销毁单独 release,全局配置缓存复用。

5.3 持久化落盘规范

  1. 每次 put 操作后必须执行flush(),API23 不会自动同步本地;
  2. 高频循环写入(循环 for 存数据)可循环结束后统一 flush,减少 IO 次数;
  3. 重启后数据丢失优先检查是否缺少 flush 调用。

5.4 读写性能规范

  1. UI 主线程统一使用异步 async/await 接口,禁止同步阻塞渲染;
  2. 页面 onShow 读取一次缓存到 @State,不要频繁实时读取 pref;
  3. 大量数组数据不建议存入 Preferences,改用 RDB 或 fs 文件。

5.5 与 AppStorage、PersistentStorage 配合规范

  1. Preferences:底层持久化数据源;
  2. PersistentStorage:自动绑定 AppStorage 与 Preferences,简化读写;
  3. AppStorage:运行时全局内存状态,UI 实时响应修改。

六、API23 升级高频问题与解决方案

问题 1:重启 APP,深色模式 / 登录账号全部丢失 解决:每次 put 后调用pref.flush()强制写入本地文件。

问题 2:多页面同时读写同一配置,数据互相覆盖 解决:工具层单文件复用同一个 pref 实例,内置读写锁串行操作。

问题 3:多次进出页面,存储文件被占用无法清空 解决:应用退出统一调用 releaseAll 释放所有 pref 句柄。

问题 4:存入数组读取为空 解决:使用 putStringArray /getStringArray 专用数组接口,不要拼接 JSON 字符串。

问题 5:升级 API23 旧代码无 flush,存储失效 解决:所有写入方法尾部补充 flush 同步本地文件。

问题 6:读取时上下文为空,存取失败 解决:页面 aboutToAppear 调用 PrefUtil.setContext (getContext (this)) 注入上下文。

七、总结

Preferences 是鸿蒙轻量化配置存储标准组件,API23 完善实例缓存、文件释放、强制落盘、并发读写锁机制,解决旧版配置重启丢失、文件占用、多页面读写错乱等问题。 工具类按业务拆分存储文件,封装布尔、字符串、数字、数组完整读写、删除、清空能力,搭配登录缓存、主题设置两大高频业务页面,可与 AppStorage、PersistentStorage 配合实现「内存全局状态 + 本地永久保存」标准架构,是所有鸿蒙项目基础底层存储工具,适配课程设计、毕设全部小型配置持久化场景。

Logo

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

更多推荐