鸿蒙应用数据持久化实战:preferences、关系型数据库与分布式数据怎么选

举报
yd_227460417 发表于 2026/11/08 14:12:41 2026/11/08
【摘要】 鸿蒙应用里要存数据时,可选方案不止一种:轻量级 preferences、关系型数据库 RelationalStore、分布式数据,以及直接读写沙箱文件。很多教程只讲 API 用法,不讲什么时候该用哪个。本文按实际开发中的典型场景,给一套选型思路和落地写法,适合正在做第一个鸿蒙应用、准备把数据真正存下来的开发者。 一、先分清四种方案把常见需求映射到方案上:记住用户偏好:主题色、字体大小、上次停...

鸿蒙应用里要存数据时,可选方案不止一种:轻量级 preferences、关系型数据库 RelationalStore、分布式数据,以及直接读写沙箱文件。很多教程只讲 API 用法,不讲什么时候该用哪个。本文按实际开发中的典型场景,给一套选型思路和落地写法,适合正在做第一个鸿蒙应用、准备把数据真正存下来的开发者。

一、先分清四种方案

把常见需求映射到方案上:

  1. 记住用户偏好:主题色、字体大小、上次停留页面,用 preferences;
  2. 结构化业务数据:待办清单、聊天记录、账单明细,用关系型数据库;
  3. 多端数据一致:手机和平板看到同一份数据,用分布式数据或端云协同;
  4. 大文件与媒体:图片缓存、导出的 PDF,直接沙箱文件加路径管理。

最常见误用是拿 preferences 存业务数据,或者拿数据库存几个开关。前者有容量和类型限制,后者是杀鸡用牛刀,都给后期埋雷。

二、preferences:小而美的键值存储

适合存放几百条以内的简单配置。写法直接,初始化后即可读写:

import preferences from '@ohos.data.preferences';

async function saveTheme(context: Context, mode: string): Promise<void> {
  const store = await preferences.getPreferences(context, 'settings');
  await store.put('themeMode', mode);
  await store.flush();
}

async function loadTheme(context: Context): Promise<string> {
  const store = await preferences.getPreferences(context, 'settings');
  return await store.get('themeMode', 'light') as string;
}

三个使用建议:

  • flush 不要省略,put 只写内存,flush 才落盘;
  • 不要在高频路径上反复 getPreferences,应用启动时初始化一次,全局复用;
  • 值类型只支持简单的 number、string、boolean,复杂数组要先序列化。

另外,preferences 有容量上限(默认较小),如果发现“存着存着丢了”,大概率是把业务数据塞进来了,该换数据库了。

三、关系型数据库:业务数据的主力

待办、账单、聊天记录这类数据,用 RelationalStore 最合适。先建表:

import relationalStore from '@ohos.data.relationalStore';

async function initDB(context: Context): Promise<relationalStore.RdbStore> {
  const config: relationalStore.StoreConfig = {
    name: 'app.db',
    securityLevel: relationalStore.SecurityLevel.S1
  };
  const store = await relationalStore.getRdbStore(context, config);
  const sql = `
    CREATE TABLE IF NOT EXISTS bills (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      title TEXT NOT NULL,
      amount REAL NOT NULL,
      category TEXT,
      paid_at INTEGER NOT NULL,
      created_at INTEGER NOT NULL
    );
  `;
  await store.executeSql(sql);
  return store;
}

插入数据用 ResultSet 方式比拼 SQL 字符串更安全:

async function addBill(store: relationalStore.RdbStore, bill: Bill): Promise<void> {
  const value: relationalStore.ValuesBucket = {
    'title': bill.title,
    'amount': bill.amount,
    'category': bill.category,
    'paid_at': bill.paidAt,
    'created_at': Date.now()
  };
  await store.insert('bills', value);
}

查询同样建议用查询构造器,避免手写条件拼接:

async function queryByMonth(
  store: relationalStore.RdbStore,
  start: number,
  end: number
): Promise<Bill[]> {
  const predicates = new relationalStore.RdbPredicates('bills')
    .between('paid_at', start, end)
    .orderByDesc('paid_at');
  const result = await store.query(predicates, ['id', 'title', 'amount', 'category', 'paid_at']);
  const bills: Bill[] = [];
  while (result.goToNextRow()) {
    bills.push({
      id: result.getValue(result.getColumnIndex('id')),
      title: result.getString(result.getColumnIndex('title')),
      amount: result.getDouble(result.getColumnIndex('amount')),
      category: result.getString(result.getColumnIndex('category')),
      paidAt: result.getLong(result.getColumnIndex('paid_at'))
    });
  }
  result.close();
  return bills;
}

三条实践经验:

  • ResultSet 用完必须 close,否则连接会泄漏;
  • 批量写入用事务包裹,速度快很多,还能保证原子性;
  • 时间戳字段统一毫秒整型,避免字符串比较的坑。

四、沙箱文件:大文件的正解

图片缓存、导出报告这类数据不要进数据库,直接操作沙箱目录:

import fs from '@ohos.file.fs';

async function saveCacheFile(
  context: Context,
  name: string,
  data: ArrayBuffer
): Promise<string> {
  const dir = context.cacheDir;
  const path = `${dir}/${name}`;
  const file = fs.openSync(path, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
  fs.writeSync(file.fd, data);
  fs.closeSync(file);
  return path;
}

cacheDir 与 filesDir 的区别要记住:cacheDir 系统可能在磁盘紧张时清理,适合可再生的缓存;filesDir 由应用自己管理,适合导出文件这类用户资产。放错目录,要么缓存被误清,要么重要文件占着缓存位。

五、分布式数据:多端一致的最后一块

如果应用要在手机、平板之间无缝切换,可以了解分布式数据对象。它把一个对象在不同设备间自动同步,适合待办清单这类轻量数据。

但要注意两点:

  • 分布式能力依赖组网与账号体系,真机验证比模拟器靠谱;
  • 一旦数据结构复杂、冲突规则不明确,同步带来的调试成本会陡增。

实践中更稳妥的路线往往是:先用云侧接口做数据源,端侧只做缓存与展示,也就是“端云协同”。多端一致性交给服务端处理,逻辑简单得多。

六、一张选型速查表

数据特征 推荐方案 典型例子
少量配置项 preferences 主题、开关、上次页面
结构化、可查询 关系型数据库 账单、待办、消息记录
大文件、二进制 沙箱文件 图片缓存、导出文件
多端实时一致 分布式数据或端云协同 多设备待办同步

七、几个容易踩的坑

  • preferences 当数据库用:存了几百条记录后性能和容量双双出问题;
  • 每次读写都新建 store 实例:既慢又浪费,应全局复用;
  • 忘记 flush 或忘记 close:数据丢失和句柄泄漏都来自这里;
  • 主键用业务字段:一旦业务规则变化,主键就废了,尽量用自增 id;
  • 没有版本管理:表结构升级时直接崩溃,建表脚本要带版本号和迁移逻辑。

八、小结

持久化选型的原则就一句话:按数据的生命周期和访问方式选工具,而不是按 API 熟悉程度。配置进 preferences,业务进数据库,大文件进沙箱,多端一致交给云。四条边界划清楚,应用的数据层基本就不会返工。

如果你的应用还在早期,建议从关系型数据库加一层简单的 DAO 封装开始,把建表、升级、增删改查集中管理,后面无论功能怎么长,数据层都是稳的。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。