)
摘要Preferences 是鸿蒙轻量本地持久化存储适合存储简单键值配置登录 Token、主题开关、用户基础配置、页面缓存参数区别于 RDB 关系数据库仅支持字符串、数字、布尔、数组基础类型无需建表读写速度更快。API23 重构 Preferences 实例生命周期、多实例隔离、同步 / 异步读写、数据自动落盘逻辑修复旧版多页面并发读写错乱、修改后重启丢失、实例未关闭占用文件、批量写入卡顿等问题。本文封装全局 Preferences 工具类封装布尔、字符串、数字、数组存取、删除 key、清空全部数据全套方法搭配登录缓存、深色模式两套实战页面给出实例销毁、并发读写、持久化同步规范是项目基础存储必备底层工具。关键词OpenHarmonyArkTSAPI23Preferences轻量存储键值持久化本地配置缓存一、引言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_settingPreferences 实例操作文件的句柄用完必须 release 释放二、API23 Preferences 原生基础示例etsimport 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.etsetsimport 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: Mapstring, 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): Promisepreferences.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): Promiseboolean { 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): Promiseboolean { const pref await this.getPref(file) if (!pref) return def return await pref.getBool(key, def) } // 写入字符串 async putString(file: string, key: string, value: string): Promiseboolean { 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): Promisestring { const pref await this.getPref(file) if (!pref) return def return await pref.getString(key, def) } // 写入数字 async putNumber(file: string, key: string, value: number): Promiseboolean { 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): Promisenumber { const pref await this.getPref(file) if (!pref) return def return await pref.getNumber(key, def) } // 写入字符串数组 async putStringArr(file: string, key: string, arr: string[]): Promiseboolean { 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[]): Promisestring[] { const pref await this.getPref(file) if (!pref) return def return await pref.getStringArray(key, def) } // 删除单个key async deleteKey(file: string, key: string): Promiseboolean { const pref await this.getPref(file) if (!pref) return false await pref.delete(key) await pref.flush() return true } // 清空当前文件全部键值 async clearFile(file: string): Promiseboolean { 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 实战一设置页深色模式持久化etsimport 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 本地缓存etsimport 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 配合实现「内存全局状态 本地永久保存」标准架构是所有鸿蒙项目基础底层存储工具适配课程设计、毕设全部小型配置持久化场景。