HarmonyOS 数据持久化怎么选:用户首选项 Preferences 与关系型数据库 RDBStore 实战
·
HarmonyOS 数据持久化怎么选:用户首选项 Preferences 与关系型数据库 RDBStore 实战
前言
几乎每个应用都要落地数据:登录态、设置项、缓存、业务表。HarmonyOS 提供了多层持久化方案,最常用的是用户首选项(Preferences)和关系型数据库(RDBStore)。很多新手分不清"该用哪一个",于是把上千条结构化业务数据塞进 Preferences,导致读取卡顿、无法查询;或者拿 RDB 存一个"是否首次启动"的布尔值,杀鸡用牛刀。本文用对比 + 可运行示例,把两者的边界讲清楚。
问题描述
典型误用场景:
- 场景 A:用 Preferences 存"我的订单列表",每条订单有订单号、金额、状态、时间。想"查昨天的已完成订单"时,只能全量读出再在内存里 filter,且不支持按字段排序。
- 场景 B:用 RDB 存
isFirstLaunch、themeMode两个开关,建表、写 SQL、关连接,代码量是 Preferences 的 5 倍。 - 场景 C:Preferences 主线程同步读取大对象,冷启动白屏。
核心问题只有一个:按"数据是否结构化、是否需要查询/事务"来选型。
细节解析
选型决策树
| 特征 | 用 Preferences | 用 RDBStore |
|---|---|---|
| 数据量小(< 1 万条 KV) | ✅ | ❌ |
| 结构简单、无需复杂查询 | ✅ | ❌ |
| 需要 SQL 查询、排序、聚合 | ❌ | ✅ |
| 多字段、有关联关系 | ❌ | ✅ |
| 需要事务(原子提交) | ❌ | ✅ |
| 频繁写入同一批配置 | ✅ | ⚠️ 也可 |
经验法则:配置/标记/轻量缓存 → Preferences;业务实体表 → RDBStore。
关键 API 差异
- Preferences:
getPreferences(context, name)异步拿实例,put(key, value)+flush()落盘(flush 是持久化,非必须每次调用)。读取是同步的,但首次getPreferences是异步。 - RDBStore:
getRdbStore(context, config)异步建库,executeSql建表,insert/query/update/delete+RdbPredicates做查询。query返回ResultSet,需goToNextRow()遍历。
示例代码
一、Preferences:轻量 KV 读写封装
// src/main/ets/store/PrefsStore.ets
import { preferences } from '@kit.ArkData'
import { common } from '@kit.AbilityKit'
export class PrefsStore {
private static cache = new Map<string, preferences.Preferences>()
private static async get(context: common.UIAbilityContext, name: string) {
if (!this.cache.has(name)) {
const p = await preferences.getPreferences(context, name)
this.cache.set(name, p)
}
return this.cache.get(name)!
}
static async setString(ctx: common.UIAbilityContext, name: string, key: string, value: string) {
const p = await this.get(ctx, name)
await p.put(key, value)
await p.flush() // 持久化到磁盘
}
static async getString(ctx: common.UIAbilityContext, name: string, key: string, def = ''): Promise<string> {
const p = await this.get(ctx, name)
return p.get(key, def) as string
}
static async setBool(ctx: common.UIAbilityContext, name: string, key: string, value: boolean) {
const p = await this.get(ctx, name)
await p.put(key, value)
await p.flush()
}
static async getBool(ctx: common.UIAbilityContext, name: string, key: string, def = false): Promise<boolean> {
const p = await this.get(ctx, name)
return p.get(key, def) as boolean
}
static async remove(ctx: common.UIAbilityContext, name: string, key: string) {
const p = await this.get(ctx, name)
await p.delete(key)
await p.flush()
}
}
// 使用
// await PrefsStore.setBool(ctx, 'settings', 'isFirstLaunch', false)
// const theme = await PrefsStore.getString(ctx, 'settings', 'theme', 'light')
二、RDBStore:建表 + 插入 + 条件查询
// src/main/ets/store/OrderDB.ets
import { relationalStore } from '@kit.ArkData'
import { common } from '@kit.AbilityKit'
export interface Order {
id?: number
orderNo: string
amount: number
status: string // 'paid' | 'done' | 'cancel'
createdAt: string // 'YYYY-MM-DD HH:mm:ss'
}
const STORE_NAME = 'Order.db'
const TABLE = 'orders'
export class OrderDB {
private static store: relationalStore.RdbStore | null = null
static async init(ctx: common.UIAbilityContext) {
const config: relationalStore.StoreConfig = {
name: STORE_NAME,
securityLevel: relationalStore.SecurityLevel.S1
}
this.store = await relationalStore.getRdbStore(ctx, config)
// 建表(已存在不会重复建)
await this.store.executeSql(
`CREATE TABLE IF NOT EXISTS ${TABLE} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
orderNo TEXT NOT NULL,
amount REAL,
status TEXT,
createdAt TEXT
)`
)
}
static async insert(o: Order) {
if (!this.store) return
const value = relationalStore.ValuesBucket() // 等价于 ContentValues
value.orderNo = o.orderNo
value.amount = o.amount
value.status = o.status
value.createdAt = o.createdAt
await this.store.insert(TABLE, value)
}
static async queryByStatus(status: string): Promise<Order[]> {
if (!this.store) return []
const predicates = new relationalStore.RdbPredicates(TABLE)
predicates.equalTo('status', status)
predicates.orderByDesc('createdAt')
const result = await this.store.query(predicates, ['id', 'orderNo', 'amount', 'status', 'createdAt'])
const list: Order[] = []
while (result.goToNextRow()) {
list.push({
id: result.getLong(result.getColumnIndex('id')),
orderNo: result.getString(result.getColumnIndex('orderNo')),
amount: result.getDouble(result.getColumnIndex('amount')),
status: result.getString(result.getColumnIndex('status')),
createdAt: result.getString(result.getColumnIndex('createdAt'))
})
}
result.close() // 必须关闭,否则内存泄漏
return list
}
static async countByStatus(status: string): Promise<number> {
if (!this.store) return 0
const predicates = new relationalStore.RdbPredicates(TABLE)
predicates.equalTo('status', status)
return await this.store.count(predicates)
}
}
// 使用(在 Ability 的 onWindowStageCreate 里 OrderDB.init 一次)
// await OrderDB.insert({ orderNo: 'NO20260910', amount: 99.5, status: 'paid', createdAt: '2026-09-10 10:00:00' })
// const paid = await OrderDB.queryByStatus('paid')
总结
- 配置类用 Preferences:
getPreferences+put+flush,轻量 KV 首选,注意flush()才真正落盘。 - 业务表用 RDBStore:
getRdbStore+executeSql建表 +RdbPredicates做条件查询,支持排序、聚合、事务。 ResultSet用完必close():否则会泄漏数据库连接,这是最常见的 RDB 内存问题。- 不要混用:别把列表塞进 Preferences,也别用 RDB 存开关。按决策树选,代码量和性能都最优。
- 初始化时机:RDB 在
UIAbility.onWindowStageCreate里init一次即可全局复用,避免每次建连。
更多推荐
所有评论(0)