HarmonyOS 数据持久化怎么选:用户首选项 Preferences 与关系型数据库 RDBStore 实战

前言

几乎每个应用都要落地数据:登录态、设置项、缓存、业务表。HarmonyOS 提供了多层持久化方案,最常用的是用户首选项(Preferences)关系型数据库(RDBStore)。很多新手分不清"该用哪一个",于是把上千条结构化业务数据塞进 Preferences,导致读取卡顿、无法查询;或者拿 RDB 存一个"是否首次启动"的布尔值,杀鸡用牛刀。本文用对比 + 可运行示例,把两者的边界讲清楚。

问题描述

典型误用场景:

  • 场景 A:用 Preferences 存"我的订单列表",每条订单有订单号、金额、状态、时间。想"查昨天的已完成订单"时,只能全量读出再在内存里 filter,且不支持按字段排序。
  • 场景 B:用 RDB 存 isFirstLaunchthemeMode 两个开关,建表、写 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')

总结

  1. 配置类用 PreferencesgetPreferences + put + flush,轻量 KV 首选,注意 flush() 才真正落盘。
  2. 业务表用 RDBStoregetRdbStore + executeSql 建表 + RdbPredicates 做条件查询,支持排序、聚合、事务。
  3. ResultSet 用完必 close():否则会泄漏数据库连接,这是最常见的 RDB 内存问题。
  4. 不要混用:别把列表塞进 Preferences,也别用 RDB 存开关。按决策树选,代码量和性能都最优。
  5. 初始化时机:RDB 在 UIAbility.onWindowStageCreateinit 一次即可全局复用,避免每次建连。
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐