摘要

Preferences 是 OpenHarmony 轻量键值持久化存储组件,用于保存小型配置数据,应用重启、设备关机后数据不会丢失,常与 AppStorage 全局内存状态搭配使用,实现「内存实时读写 + 磁盘永久保存」双缓存架构,适配登录缓存、主题偏好、设置开关、搜索历史、用户基础配置等轻量数据场景。API Version23 重构 Preferences 文件读写、多实例锁、异步回调、类型序列化逻辑,修复低版本存储写入丢失、并发读写错乱、布尔 / 数字读取默认值异常、多页面同时操作文件冲突等兼容缺陷。低版本工程升级至 API23 后,普遍出现:保存的数据重启应用消失、并发读写覆盖数据、读取数字返回 0、布尔值读取恒为 false、存储文件损坏等故障。本文基于 DevEco Studio 适配 OpenHarmony API23 及以上版本,讲解 Preferences 同步 / 异步读写、多类型存取、封装全局存储工具类、与 AppStorage 联动持久化、分页搜索历史存储,输出并发读写锁、存储性能优化、异常容错规范,汇总版本升级兼容故障解决方案,为鸿蒙轻量本地持久化开发提供标准化实操模板。

关键词

OpenHarmony;ArkUI;API Version23;Preferences;本地持久化;键值存储;持久化全局配置;AppStorage 联动;轻量缓存

一、引言

1.1 Preferences 开发背景

AppStorage 仅存在应用内存,进程杀死数据全部销毁,无法实现永久配置保存;数据库适合海量复杂数据,存储小型开关、用户名、主题标识会产生性能冗余。Preferences 作为系统原生轻量持久化方案,以 XML 文件形式持久化至应用沙盒目录,仅支持基础键值类型,读写速度快、API 简洁,是应用配置类数据首选存储方案。

OpenHarmony API Version23 针对 Preferences 底层文件 IO 引擎做全面升级,核心变更点:

  1. 新增文件读写互斥锁,同一存储文件多页面并发读写不再互相覆盖;
  2. 统一基础类型序列化规则,string/number/boolean 读取不会出现默认值错乱;
  3. 优化异步 getPreferences 加载逻辑,修复低版本文件加载延迟导致读取空值;
  4. 规范 flush 持久化强制落盘机制,区分内存缓存与磁盘持久化;
  5. 增加文件损坏自动兜底默认值逻辑,避免应用闪退;
  6. 限制超大字符串、数组直接存入 Preferences,给出性能告警。

大量 API9~11 旧项目升级后,切换深色模式重启失效、登录状态重启丢失、搜索历史保存不全,根源是旧版无锁并发写入、未强制 flush 落盘,因此掌握 API23 标准 Preferences 封装规范是配置类开发必备技能。

1.2 开发环境与测试场景

开发工具:DevEco Studio 5.0 及以上 适配系统:OpenHarmony API Version23、HarmonyOS NEXT 开发语言:ArkTS 导入模块:@ohos.data.preferences 测试场景:深色主题持久保存、登录状态本地缓存、用户昵称永久存储、搜索历史数组持久化、AppStorage 全局配置落地磁盘、多页面并发读写配置文件

二、API23+ Preferences 核心 API 与版本变更说明

2.1 核心对象获取方式(API23 标准异步写法)

ets

import preferences from '@ohos.data.preferences'

// 获取Preferences实例,fileName为沙盒内存储文件名
async function getPrefInstance() {
  let dataPreferences = await preferences.getPreferences(getContext(this), "app_config")
  return dataPreferences
}

2.2 数据写入与落盘

  1. putSync (key, value):同步写入内存缓存
  2. put (key, value):异步写入内存缓存
  3. flush ():强制将内存缓存数据写入磁盘文件(API23 必须调用,否则进程销毁丢失)

2.3 数据读取 API

getSync<T>(key, defaultValue) 同步读取;get<T>(key, defaultValue) 异步读取,无数据返回默认值。 支持类型:string、number、boolean、Array<string>

2.4 删除与清空

deleteSync (key) 删除单条键;clearSync () 清空全部存储;hasSync (key) 判断键是否存在。

2.5 API23 废弃与强制约束

  1. 废弃无 flush 直接退出进程写法,不执行 flush 会丢失本次写入;
  2. 禁止多实例同时操作同一个 fileName 文件,必须全局单例管理;
  3. 废弃复杂对象直接存储,对象需 JSON 序列化字符串后存取;
  4. 单条字符串存储长度上限 8000 字符,超长数据拆分或改用数据库;
  5. 同步 API 禁止在主线程循环大量调用,易造成页面卡顿。

三、API23 标准基础示例代码

3.1 基础布尔值持久化(深色模式开关)

ets

import preferences from '@ohos.data.preferences'

// 保存深色模式开关
async function saveDarkMode(isDark: boolean) {
  let pref = await preferences.getPreferences(getContext(), "app_config")
  pref.putSync("dark_mode", isDark)
  await pref.flush() // API23必须强制落盘
}

// 读取深色模式,无数据默认false
async function getDarkMode(): Promise<boolean> {
  let pref = await preferences.getPreferences(getContext(), "app_config")
  let val = pref.getSync("dark_mode", false)
  return val
}

3.2 字符串持久化(登录用户名)

ets

async function saveUserName(name: string) {
  let pref = await preferences.getPreferences(getContext(), "app_config")
  pref.putSync("user_name", name)
  await pref.flush()
}

async function getUserName(): Promise<string> {
  let pref = await preferences.getPreferences(getContext(), "app_config")
  return pref.getSync("user_name", "游客")
}

3.3 数组存储(搜索历史,JSON 序列化)

ets

async function saveSearchHistory(list: string[]) {
  let pref = await preferences.getPreferences(getContext(), "app_config")
  let jsonStr = JSON.stringify(list)
  pref.putSync("search_history", jsonStr)
  await pref.flush()
}

async function getSearchHistory(): Promise<string[]> {
  let pref = await preferences.getPreferences(getContext(), "app_config")
  let jsonStr = pref.getSync("search_history", "[]")
  return JSON.parse(jsonStr)
}

四、四大业务完整实战案例(全兼容 API23)

4.1 实战一:全局 Preferences 工具类单例封装(项目通用)

新建 utils/preference_utils.ets,全局统一存取,避免多实例冲突

ets

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

class PreferenceUtil {
  private static instance: PreferenceUtil
  private pref: preferences.Preferences | null = null
  private readonly FILE_NAME = "app_global_config"

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

  // 初始化存储实例,页面启动调用一次
  async init(context: common.UIAbilityContext) {
    this.pref = await preferences.getPreferences(context, this.FILE_NAME)
  }

  // 保存布尔值
  async putBool(key: string, value: boolean) {
    if (!this.pref) return
    this.pref.putSync(key, value)
    await this.pref.flush()
  }

  async getBool(key: string, def: boolean = false): Promise<boolean> {
    if (!this.pref) return def
    return this.pref.getSync(key, def)
  }

  // 保存字符串
  async putString(key: string, value: string) {
    if (!this.pref) return
    this.pref.putSync(key, value)
    await this.pref.flush()
  }

  async getString(key: string, def: string = ""): Promise<string> {
    if (!this.pref) return def
    return this.pref.getSync(key, def)
  }

  // 保存数字
  async putNumber(key: string, value: number) {
    if (!this.pref) return
    this.pref.putSync(key, value)
    await this.pref.flush()
  }

  async getNumber(key: string, def: number = 0): Promise<number> {
    if (!this.pref) return def
    return this.pref.getSync(key, def)
  }

  // 数组序列化存储
  async putArray<T>(key: string, arr: T[]) {
    if (!this.pref) return
    let str = JSON.stringify(arr)
    this.pref.putSync(key, str)
    await this.pref.flush()
  }

  async getArray<T>(key: string): Promise<T[]> {
    if (!this.pref) return []
    let str = this.pref.getSync(key, "[]")
    return JSON.parse(str)
  }

  // 删除单条key
  async deleteKey(key: string) {
    if (!this.pref) return
    this.pref.deleteSync(key)
    await this.pref.flush()
  }

  // 清空全部存储
  async clearAll() {
    if (!this.pref) return
    this.pref.clearSync()
    await this.pref.flush()
  }
}

export default PreferenceUtil.getInstance()

4.2 实战二:Preferences + AppStorage 联动持久化主题

实现:页面修改开关同步 AppStorage 内存,同时写入 Preferences 磁盘,应用重启自动读取 Preferences 回填全局状态

ets

import prefUtil from '../utils/preference_utils'

@Entry
@Component
struct ThemePage {
  @StorageLink('darkMode') darkSwitch: boolean = false

  async aboutToAppear() {
    // 页面加载从本地持久化读取,初始化全局状态
    await prefUtil.init(getContext(this))
    let localDark = await prefUtil.getBool("dark_mode")
    AppStorage.Set("darkMode", localDark)
    this.darkSwitch = localDark
  }

  // 切换开关同步内存与本地存储
  async changeTheme(val: boolean) {
    this.darkSwitch = val
    AppStorage.Set("darkMode", val)
    await prefUtil.putBool("dark_mode", val)
  }

  build() {
    Column({ space: 25 }) {
      Text("持久化深色主题设置")
        .fontSize(22)
        .fontWeight(FontWeight.Bold)
      Row() {
        Text("夜间模式").layoutWeight(1).fontSize(17)
        Toggle({ isOn: this.darkSwitch })
          .onChange(async v => await this.changeTheme(v))
      }
      .width("100%")
      .height(56)
      .padding(15)
      .backgroundColor(Color.White)
      .borderRadius(10)
      Text("关闭应用重新打开,配置不会丢失")
        .fontSize(14)
        .fontColor("#999")
    }
    .width("100%")
    .height("100%")
    .padding(20)
    .backgroundColor(this.darkSwitch ? "#1A1A1A" : "#F5F5F5")
  }
}

4.3 实战三:登录信息本地持久缓存(重启免登)

ets

import prefUtil from '../utils/preference_utils'
import router from '@ohos.router'

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

  async aboutToAppear() {
    await prefUtil.init(getContext(this))
    // 读取上次登录账号回填输入框
    let saveAccount = await prefUtil.getString("login_account")
    this.account = saveAccount
  }

  async handleLogin() {
    if (this.account.length < 1) {
      promptAction.showToast({ message: "账号不能为空" })
      return
    }
    // 登录成功存入本地持久化与全局AppStorage
    await prefUtil.putString("login_account", this.account)
    await prefUtil.putBool("is_login", true)
    AppStorage.Set("isLogin", true)
    AppStorage.Set("userName", this.account)
    router.replaceUrl({ url: "pages/mine/mine" })
  }

  build() {
    Column({ space: 22 }) {
      Text("账号登录(持久化缓存)")
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
      TextInput({ text: this.account, placeholder: "输入账号" })
        .width("100%")
        .height(48)
        .onChange(v => this.account = v)
      TextInput({ text: this.pwd, placeholder: "输入密码" })
        .type(InputType.Password)
        .width("100%")
        .height(48)
      Button("登录并保存账号")
        .width("100%")
        .height(48)
        .backgroundColor("#007DFF")
        .onClick(async () => await this.handleLogin())
    }
    .width("100%")
    .height("100%")
    .padding(25)
    .justifyContent(FlexAlign.Center)
  }
}

4.4 实战四:搜索历史数组持久存取

ets

import prefUtil from '../utils/preference_utils'

@Entry
@Component
struct SearchPage {
  @State keyword: string = ""
  @State historyList: string[] = []

  async aboutToAppear() {
    await prefUtil.init(getContext(this))
    // 读取本地历史记录
    this.historyList = await prefUtil.getArray<string>("search_history")
  }

  // 新增搜索词并持久保存
  async addHistory() {
    if (!this.keyword) return
    // 去重
    let idx = this.historyList.indexOf(this.keyword)
    if (idx > -1) this.historyList.splice(idx, 1)
    this.historyList.unshift(this.keyword)
    // 限制最多8条历史
    if (this.historyList.length > 8) this.historyList.pop()
    // 存入本地持久化
    await prefUtil.putArray("search_history", this.historyList)
    this.keyword = ""
  }

  build() {
    Column({ space: 18 }) {
      Row({ space: 10 }) {
        TextInput({ text: this.keyword, placeholder: "输入搜索内容" })
          .layoutWeight(1)
          .height(44)
        Button("搜索")
          .height(44)
          .onClick(async () => await this.addHistory())
      }
      Text("搜索历史").fontSize(18).fontWeight(FontWeight.Medium)
      List({ space: 8 }) {
        ForEach(this.historyList, (item: string) => {
          ListItem() {
            Text(item).width("100%").padding(12).backgroundColor(Color.White).borderRadius(8)
          }
        })
      }
      .layoutWeight(1)
    }
    .width("100%")
    .height("100%")
    .padding(15)
  }
}

五、API23+ 持久化存储适配与性能优化规范

5.1 多页面并发读写规范

  1. 全局使用单例工具类管理 Preferences 实例,禁止每个页面新建实例;
  2. 每次 put 操作后必须执行 await flush () 强制落盘,API23 无自动持久化;
  3. 短时间连续多次写入增加防抖,避免高频 flush 重复 IO 操作;
  4. 启动页统一初始化 pref 实例,子页面直接调用工具类无需重复 init。

5.2 数据存储分层规范

  1. 临时页面变量:@State,页面销毁丢失;
  2. 全局内存共享配置:AppStorage,进程杀死丢失;
  3. 永久保存配置:Preferences,双缓存架构(AppStorage+Preferences);
  4. 海量结构化数据、图片二进制:使用 RDB 关系型数据库,不使用 Preferences。

5.3 性能优化准则

  1. 不存储超长文本、大型 JSON 对象,拆分精简字段;
  2. 主线程避免循环同步 get/put 大量数据,改用异步 API;
  3. 退出登录、重置功能调用 clearAll 清空冗余配置,减少文件体积;
  4. 数组统一 JSON 序列化存储,API23 不支持直接传入数组参数。

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

问题 1:重启应用后保存的配置全部消失 解决:每次写入后必须执行await pref.flush()强制写入磁盘,旧版本隐性落盘逻辑在 API23 已移除。

问题 2:多页面同时读写同一配置,数据互相覆盖错乱 解决:全局单例工具类统一管控实例,利用 API23 内置文件互斥锁,禁止多实例操作同一文件。

问题 3:读取布尔值恒为 false、数字恒为 0 解决:读取时传入正确默认值,旧项目升级统一替换同步 getSync,异步 get 需 await 接收返回。

问题 4:数组存入后读取解析报错 解决:复杂数组 / 对象必须 JSON.stringify 序列化字符串存储,读取后 JSON.parse 还原。

问题 5:页面频繁切换存储读写,页面滑动卡顿 解决:增加防抖延迟,合并短时间多次写入操作,减少 flush 磁盘 IO 次数。

问题 6:升级后旧存储文件读取失败、返回默认值 解决:初始化时增加异常捕获,文件损坏自动使用默认值覆盖重建配置文件。

七、总结

Preferences 是 OpenHarmony 轻量本地持久化标准组件,API Version23 重构文件 IO 并发锁、数据序列化、强制落盘机制,解决旧版数据丢失、并发错乱核心缺陷。项目标准架构为「Preferences 磁盘持久化 + AppStorage 全局内存缓存」,启动时从本地读取回填全局状态,修改时同步更新内存与磁盘,兼顾实时响应与永久保存。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐