Administrator
发布于 2026-08-30 / 112 阅读
0
0

鸿蒙数据持久化全解:Preferences、RDB 与分布式数据的场景选择

系列文章《鸿蒙开发从入门到专家》第 5 篇(中级篇)

前言

页面搭好了,导航通了,下一步是数据——应用重启后数据不能丢,用户配置要保存,业务数据要持久化。鸿蒙提供了多种数据存储方案,但官方文档的问题在于:每种方案都讲得很详细,却没有告诉你"什么时候该用哪种"。

我见过太多开发者一上来就用 RelationalStore 存用户配置,用 Preferences 存聊天记录,最后性能和可维护性双双崩盘。本文把鸿蒙的数据持久化方案按场景分类,帮你建立清晰的选型直觉。

一、Preferences:轻量键值存储的首选

1.1 基本用法

Preferences 是鸿蒙提供的轻量级键值存储方案,适合保存用户配置、开关状态、简单的缓存数据。

import { preferences } from '@kit.ArkData'

class AppPreferences {
  private static instance: AppPreferences
  private pref: preferences.Preferences | null = null

  static async getInstance(): Promise<AppPreferences> {
    if (!AppPreferences.instance) {
      AppPreferences.instance = new AppPreferences()
      let context = getContext()
      AppPreferences.instance.pref = await preferences.getPreferences(context, {
        name: 'app_config'
      })
    }
    return AppPreferences.instance
  }

  async setString(key: string, value: string): Promise<void> {
    await this.pref?.put(key, value)
    await this.pref?.flush()  // 立即写入磁盘
  }

  async getString(key: string, defaultValue: string = ''): Promise<string> {
    return (await this.pref?.get(key, defaultValue)) as string
  }

  async setNumber(key: string, value: number): Promise<void> {
    await this.pref?.put(key, value)
    await this.pref?.flush()
  }

  async getNumber(key: string, defaultValue: number = 0): Promise<number> {
    return (await this.pref?.get(key, defaultValue)) as number
  }

  async setBoolean(key: string, value: boolean): Promise<void> {
    await this.pref?.put(key, value)
    await this.pref?.flush()
  }

  async getBoolean(key: string, defaultValue: boolean = false): Promise<boolean> {
    return (await this.pref?.get(key, defaultValue)) as boolean
  }
}

// 使用示例
async function demo() {
  let prefs = await AppPreferences.getInstance()
  await prefs.setString('user_nickname', '张三')
  await prefs.setBoolean('dark_mode', true)
  await prefs.setNumber('font_size', 16)

  let nickname = await prefs.getString('user_nickname')
  console.info(`昵称: ${nickname}`)
}

1.2 API 26 新增能力

HarmonyOS NEXT 5.0 对 Preferences 做了增强:

  • 支持 BigInt 和 Object 类型:之前只能存 string、number、boolean,现在可以存大整数和简单对象

  • 支持指定 key 的数据变更订阅:可以监听某个 key 的变化

// 订阅特定 key 的变化
this.pref?.on('change', (key: string) => {
  if (key === 'dark_mode') {
    console.info('主题模式发生变化')
  }
})

1.3 Preferences 的适用边界

适合存储

不适合存储

用户配置(主题、字体、开关)

大量结构化数据

登录 token、用户信息缓存

需要复杂查询的数据

简单的应用状态标记

图片、文件等二进制大对象

单条数据 < 几百字节

数据量 > 1MB

性能基准:单次读写约 1-5ms,数据量大时 flush() 会阻塞,建议批量写入后再统一 flush。

二、RelationalStore:SQLite 的鸿蒙封装

当数据有结构化需求、需要多表关联查询、或者数据量超过 Preferences 的承载能力时,就需要上关系型数据库了。

2.1 基础 CRUD 操作

import { relationalStore } from '@kit.ArkData'

class TodoDatabase {
  private rdbStore: relationalStore.RdbStore | null = null
  private static instance: TodoDatabase

  static async getInstance(): Promise<TodoDatabase> {
    if (!TodoDatabase.instance) {
      TodoDatabase.instance = new TodoDatabase()
      let context = getContext()
      
      const config: relationalStore.StoreConfig = {
        name: 'todo.db',
        securityLevel: relationalStore.SecurityLevel.S1
      }
      
      TodoDatabase.instance.rdbStore = await relationalStore.getRdbStore(context, config)
      
      // 创建表
      await TodoDatabase.instance.rdbStore.executeSql(`
        CREATE TABLE IF NOT EXISTS todos (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          title TEXT NOT NULL,
          content TEXT,
          priority INTEGER DEFAULT 0,
          completed INTEGER DEFAULT 0,
          create_time INTEGER,
          update_time INTEGER
        )
      `)
    }
    return TodoDatabase.instance
  }

  // 插入
  async insertTodo(todo: TodoItem): Promise<number> {
    let values = {
      title: todo.title,
      content: todo.content,
      priority: todo.priority,
      completed: todo.completed ? 1 : 0,
      create_time: Date.now(),
      update_time: Date.now()
    }
    return await this.rdbStore?.insert('todos', values) || -1
  }

  // 查询全部
  async queryAllTodos(): Promise<TodoItem[]> {
    let predicates = new relationalStore.RdbPredicates('todos')
    predicates.orderByDesc('create_time')
    
    let resultSet = await this.rdbStore?.query(predicates, [
      'id', 'title', 'content', 'priority', 'completed', 'create_time'
    ])
    
    let todos: TodoItem[] = []
    while (resultSet?.goToNextRow()) {
      todos.push({
        id: resultSet.getLong(resultSet.getColumnIndex('id')),
        title: resultSet.getString(resultSet.getColumnIndex('title')),
        content: resultSet.getString(resultSet.getColumnIndex('content')),
        priority: resultSet.getLong(resultSet.getColumnIndex('priority')),
        completed: resultSet.getLong(resultSet.getColumnIndex('completed')) === 1,
        createTime: resultSet.getLong(resultSet.getColumnIndex('create_time'))
      })
    }
    resultSet?.close()
    return todos
  }

  // 条件查询:未完成的待办
  async queryUncompleted(): Promise<TodoItem[]> {
    let predicates = new relationalStore.RdbPredicates('todos')
    predicates.equalTo('completed', 0)
      .and()
      .greaterThan('priority', 0)
      .orderByDesc('priority')
    
    let resultSet = await this.rdbStore?.query(predicates)
    // ... 解析结果
    return []
  }

  // 更新
  async updateTodo(todo: TodoItem): Promise<void> {
    let values = {
      title: todo.title,
      content: todo.content,
      priority: todo.priority,
      completed: todo.completed ? 1 : 0,
      update_time: Date.now()
    }
    let predicates = new relationalStore.RdbPredicates('todos')
    predicates.equalTo('id', todo.id)
    await this.rdbStore?.update(values, predicates)
  }

  // 删除
  async deleteTodo(id: number): Promise<void> {
    let predicates = new relationalStore.RdbPredicates('todos')
    predicates.equalTo('id', id)
    await this.rdbStore?.delete(predicates)
  }
}

interface TodoItem {
  id?: number
  title: string
  content: string
  priority: number
  completed: boolean
  createTime?: number
}

2.2 API 26 增强特性

HarmonyOS NEXT 5.0 对 RDB 的增强非常实用:

  • 支持 BigInt 类型:处理大整数 ID 不再受限

  • attach/detach 多数据库:可以在一个连接中跨库查询

  • 数据库变更通知携带数据 ID:订阅者能精确知道哪条数据变了

  • 支持 TS 同步 API:不再需要处处写 await(注意:同步 API 会阻塞线程,UI 操作仍建议用异步)

  • 新增只读模式配置:isReadOnly: true,适合只读场景

2.3 RDB 的适用边界

适合存储

不适合存储

结构化业务数据(订单、消息、待办)

图片、音视频等大文件

需要多条件查询的数据

极简的 key-value 场景(用 Preferences)

数据量 1MB ~ 100MB

数据量超过 500MB(考虑文件存储)

需要事务保证一致性

跨设备实时同步(用分布式数据)

三、ORM 封装:让数据库操作更优雅

手写 SQL 和 RdbPredicates 很繁琐,企业级项目通常需要一层 ORM 封装。鸿蒙生态中有一些社区 ORM 方案,但最稳妥的做法是自己封装一个轻量级的:

// 简化版 ORM 基类
abstract class BaseDao<T> {
  protected abstract tableName: string
  protected abstract primaryKey: string
  protected rdbStore: relationalStore.RdbStore

  constructor(rdbStore: relationalStore.RdbStore) {
    this.rdbStore = rdbStore
  }

  async insert(entity: T): Promise<number> {
    return await this.rdbStore.insert(this.tableName, entity as relationalStore.ValuesBucket)
  }

  async deleteById(id: number | string): Promise<void> {
    let predicates = new relationalStore.RdbPredicates(this.tableName)
    predicates.equalTo(this.primaryKey, id)
    await this.rdbStore.delete(predicates)
  }

  async queryById(id: number | string): Promise<T | null> {
    let predicates = new relationalStore.RdbPredicates(this.tableName)
    predicates.equalTo(this.primaryKey, id)
    let resultSet = await this.rdbStore.query(predicates)
    // ... 解析结果
    return null
  }
}

// 具体 DAO 实现
class TodoDao extends BaseDao<TodoItem> {
  protected tableName = 'todos'
  protected primaryKey = 'id'
}

四、分布式数据:鸿蒙的杀手级能力

这是鸿蒙区别于 Android 和 iOS 的核心特性。基于分布式软总线,鸿蒙设备之间可以自动发现、自动组网,应用数据可以在手机、平板、PC 之间无缝流转。

4.1 分布式键值存储

import { distributedKVStore } from '@kit.ArkData'

class DistributedPreferences {
  private kvManager: distributedKVStore.KVManager | null = null
  private kvStore: distributedKVStore.SingleKVStore | null = null

  async init(): Promise<void> {
    let context = getContext()
    
    this.kvManager = distributedKVStore.createKVManager({
      context: context,
      bundleName: context.applicationInfo.name
    })

    let options: distributedKVStore.Options = {
      createIfMissing: true,
      encrypt: false,
      backup: false,
      autoSync: true,           // 自动同步到其他设备
      kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
      securityLevel: distributedKVStore.SecurityLevel.S1
    }

    this.kvStore = await this.kvManager.getKVStore('distributed_config', options)
  }

  async put(key: string, value: string | number | boolean): Promise<void> {
    await this.kvStore?.put(key, value)
  }

  async get(key: string): Promise<distributedKVStore.ValueType | undefined> {
    return await this.kvStore?.get(key)
  }

  // 监听数据同步事件
  subscribeSync(callback: (entries: distributedKVStore.Entry[]) => void): void {
    this.kvStore?.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_ALL, callback)
  }
}

4.2 分布式关系型数据

对于更复杂的场景,RelationalStore 也支持分布式能力:

const config: relationalStore.StoreConfig = {
  name: 'distributed_todo.db',
  securityLevel: relationalStore.SecurityLevel.S1,
  // 关键配置:启用分布式
  customDir: 'distributed',
  isReadOnly: false
}

// 数据变更会自动同步到同一华为账号下的其他设备

4.3 分布式数据的限制

限制项

说明

账号绑定

数据只在同一华为账号的设备间同步

网络要求

设备需要在同一局域网或开启蓝牙

数据大小

单条建议 < 1MB,总量 < 100MB

冲突解决

默认"后写入获胜",复杂场景需自定义冲突策略

隐私合规

敏感数据需加密,且要符合各国家地区的数据法规

五、选型决策树

需要持久化存储?
├── 数据量极小(< 几条)且结构简单?
│   └── 用 Preferences
├── 结构化数据,需要查询?
│   ├── 单设备使用?
│   │   └── 用 RelationalStore
│   └── 需要跨设备同步?
│       └── 用分布式 KVStore 或分布式 RDB
├── 图片/音视频等大文件?
│   └── 用文件系统(core.fileIO)或 MediaLibrary
└── 需要端云同步?
    └── 用 CloudDB 或自己搭建后端

六、实战:一个完整的设置页数据管理

// SettingsViewModel.ets
class SettingsViewModel {
  private prefs: AppPreferences

  constructor() {
    AppPreferences.getInstance().then(p => this.prefs = p)
  }

  // 主题设置
  async setDarkMode(enabled: boolean): Promise<void> {
    await this.prefs.setBoolean('dark_mode', enabled)
  }

  async isDarkMode(): Promise<boolean> {
    return await this.prefs.getBoolean('dark_mode', false)
  }

  // 字体大小
  async setFontSize(size: number): Promise<void> {
    await this.prefs.setNumber('font_size', size)
  }

  async getFontSize(): Promise<number> {
    return await this.prefs.getNumber('font_size', 16)
  }

  // 清除所有数据
  async clearAllData(): Promise<void> {
    await this.prefs.setString('user_token', '')
    await this.prefs.setString('user_info', '')
    // 同时清除数据库缓存
    let todoDb = await TodoDatabase.getInstance()
    // ... 执行清理操作
  }
}

// SettingsPage.ets
@Entry
@Component
struct SettingsPage {
  @State isDarkMode: boolean = false
  @State fontSize: number = 16
  private viewModel: SettingsViewModel = new SettingsViewModel()

  async aboutToAppear() {
    this.isDarkMode = await this.viewModel.isDarkMode()
    this.fontSize = await this.viewModel.getFontSize()
  }

  build() {
    Column() {
      // 深色模式开关
      Row() {
        Text('深色模式')
          .fontSize(16)
          .layoutWeight(1)
        Toggle({ type: ToggleType.Switch, isOn: this.isDarkMode })
          .onChange(async (value: boolean) => {
            this.isDarkMode = value
            await this.viewModel.setDarkMode(value)
          })
      }
      .width('100%')
      .height(56)
      .padding({ left: 16, right: 16 })

      Divider()

      // 字体大小选择
      Row() {
        Text('字体大小')
          .fontSize(16)
          .layoutWeight(1)
        Text(`${this.fontSize}px`)
          .fontSize(14)
          .fontColor('#999999')
      }
      .width('100%')
      .height(56)
      .padding({ left: 16, right: 16 })
      .onClick(() => {
        // 弹出字体大小选择弹窗
      })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
}

小结与下篇预告

本文按场景梳理了鸿蒙的三种核心持久化方案:

  1. Preferences:轻量 key-value,存配置、token、简单状态

  2. RelationalStore:SQLite 封装,存结构化业务数据,支持复杂查询

  3. 分布式数据:鸿蒙独有,同一账号设备间自动同步

选型记住一句话:配置用 Preferences,业务数据用 RDB,跨设备用分布式,大文件用文件系统。

下一篇(第 6 篇)我们进入网络层:HTTP 请求的封装、数据流转与错误处理最佳实践。包括请求拦截、Token 刷新、缓存策略和离线优先架构。

参考阅读


评论