OpenHarmony 轻量键值存储 Preferences 完整封装(API Version23)
摘要
Preferences 是鸿蒙轻量本地持久化存储,适合存储简单键值配置(登录 Token、主题开关、用户基础配置、页面缓存参数),区别于 RDB 关系数据库:仅支持字符串、数字、布尔、数组基础类型,无需建表,读写速度更快。API23 重构 Preferences 实例生命周期、多实例隔离、同步 / 异步读写、数据自动落盘逻辑,修复旧版多页面并发读写错乱、修改后重启丢失、实例未关闭占用文件、批量写入卡顿等问题。本文封装全局 Preferences 工具类,封装布尔、字符串、数字、数组存取、删除 key、清空全部数据全套方法,搭配登录缓存、深色模式两套实战页面,给出实例销毁、并发读写、持久化同步规范,是项目基础存储必备底层工具。
关键词
OpenHarmony;ArkTS;API23;Preferences;轻量存储;键值持久化;本地配置缓存
一、引言
1.1 Preferences 适用场景区分
- Preferences(优先):简单配置、开关、token、账号缓存、主题状态,数据量小、读写频繁;
- RDB:结构化多条业务数据(笔记、通讯录、商品列表),支持分页、条件查询;
- fs 文件:图片、日志、大文本二进制文件;
- AppStorage:运行时全局内存状态,重启丢失,需搭配 Preferences 持久化。
API23 Preferences 核心升级变更:
- 强制每个存储文件独立实例,禁止全局单例长期占用;
- 新增自动 flush 落盘机制,修改后实时写入本地,防止重启数据丢失;
- 优化并发读写锁,多页面同时存取不会出现数据覆盖错乱;
- 区分同步 get/set 与异步 getAsync/setAsync 两套 API,主线程推荐异步避免 UI 卡顿;
- 废弃不关闭 getPreferences 实例写法,页面销毁必须 release 释放文件句柄;
- 支持基础数组类型直接存取,无需手动 JSON 序列化转换。
1.2 模块与基础初始化
依赖模块:@ohos.data.preferences 无需动态权限,应用沙盒私有存储自动拥有读写权限 基础概念:
- 存储文件名:区分不同业务配置,如
user_config、app_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 文件拆分规范
- 按业务拆分存储文件:用户信息
user_config、系统设置app_setting; - 禁止所有键值存入同一个文件,读写效率随数据量增大下降;
- 临时缓存、长期配置分开存储,清空操作互不干扰。
5.2 生命周期释放规范
- 工具内部缓存 pref 实例,避免频繁创建销毁损耗性能;
- 应用完全退出、EntryAbility onDestroy 调用
releaseAll()释放全部文件句柄; - 单个页面临时专用配置可页面销毁单独 release,全局配置缓存复用。
5.3 持久化落盘规范
- 每次 put 操作后必须执行
flush(),API23 不会自动同步本地; - 高频循环写入(循环 for 存数据)可循环结束后统一 flush,减少 IO 次数;
- 重启后数据丢失优先检查是否缺少 flush 调用。
5.4 读写性能规范
- UI 主线程统一使用异步 async/await 接口,禁止同步阻塞渲染;
- 页面 onShow 读取一次缓存到 @State,不要频繁实时读取 pref;
- 大量数组数据不建议存入 Preferences,改用 RDB 或 fs 文件。
5.5 与 AppStorage、PersistentStorage 配合规范
- Preferences:底层持久化数据源;
- PersistentStorage:自动绑定 AppStorage 与 Preferences,简化读写;
- 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 配合实现「内存全局状态 + 本地永久保存」标准架构,是所有鸿蒙项目基础底层存储工具,适配课程设计、毕设全部小型配置持久化场景。
更多推荐

所有评论(0)