当前位置: 首页 > news >正文

OpenHarmony 轻量键值存储 Preferences 完整封装(API Version23)

摘要

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 配合实现「内存全局状态 + 本地永久保存」标准架构,是所有鸿蒙项目基础底层存储工具,适配课程设计、毕设全部小型配置持久化场景。

http://www.jsqmd.com/news/1190294/

相关文章:

  • 双2080ti部署大模型与Claude集成:低成本AI本地化实战
  • 阅读Java开源框架源码的心得分享!
  • 2026年7月最新温州芝柏官方售后联系电话与客户服务中心网点地址 - 亨得利官方服务中心
  • 昆山市独角兽企业、独角兽培育企业认定指南
  • 宇舶中国官方专柜客户服务电话权威信息通告(2026年7月最新) - 亨得利钟表维修中心
  • 雷达官方售后服务中心维修地址与电话实地考察报告+多信源验证(2026年7月最新) - 亨得利钟表维修中心
  • 泰格豪雅官方更换表蒙价格查询|地址及24小时客服电话权威信息公告(2026年7月最新) - 亨得利官方服务中心
  • 26.1.智能窗帘-V5-WiFi-语言控制-基于STM32单片机物联网设计【硬件+APP+云平台】
  • 工业设备上 AWS,一台 MQT-805-GL 就够了
  • 2026国内靠谱的SEO/GEO优化公司选评维度梳理参考 - 奔跑123
  • 内存管理机制:ArkTS的垃圾回收策略与内存泄漏检测(163)
  • 模板驱动型文档自动化:零代码实现结构化填充与多端交付
  • 【2013-12-12】C语言代码检测:cppcheck的使用
  • SpringSecurity进阶小册:Java码农必备!
  • ChatGPT技术选型终极checklist(2024.06更新版):覆盖模型层/推理层/缓存层/审计层/降级层——仅限前500名技术负责人领取完整Excel版
  • 3步学会岛屿设计:从零开始创建你的梦想岛屿
  • 2026年拼接地垫源头厂家行业实力排行名录 - 奔跑123
  • 2026 年当下,赤水热门的古代场景复原制造商哪家好,穿越回唐朝?先别急着兴奋,这3个细节会让你瞬间社死 - 企业推荐官【认证】
  • 商丘市区及虞城县本地正规家装公司行业盘点 - 奔跑123
  • 深度学习求职通关秘籍:Deep-Learning-Interview-Book开源项目深度解析与实战使用指南
  • 积家中国官方专柜客户电话权威信息通告(2026年7月最新) - 积家官方售后服务中心
  • 《Java 100 天进阶之路》第60篇:并发编程面试压轴题(2026版)
  • COM线程模型解析:STA与MTA的核心原理与优化实践
  • AI模型能力门控(Capability Gating)技术原理与工程实践
  • YOLO11 改进 - 特征融合 | STFFM空间时间特征融合模块,强化时空互补、抑制噪声,助力小目标检测高效涨点
  • 启动速度优化:AppStartup框架与冷启动秒开实践(164)
  • 万国官方服务项目及价格查询|全新电话和完整维修地址权威信息通告(2026年7月最新) - 万国中国官方服务中心
  • 2026 年现阶段,苍溪口碑好的厨卫漏水查漏服务团队选型指南,装修漏水惊魂!这招教你轻松查明厨卫隐形水源 - 鉴选官
  • 从课程到就业,AI培训怎么选才不踩坑? - GrowUME
  • 2026西安初高中冲刺补习:家长藏在日常里的升学心事 - 奔跑123