鸿蒙应用数据持久化实战:preferences、关系型数据库与分布式数据怎么选
鸿蒙应用里要存数据时,可选方案不止一种:轻量级 preferences、关系型数据库 RelationalStore、分布式数据,以及直接读写沙箱文件。很多教程只讲 API 用法,不讲什么时候该用哪个。本文按实际开发中的典型场景,给一套选型思路和落地写法,适合正在做第一个鸿蒙应用、准备把数据真正存下来的开发者。
一、先分清四种方案
把常见需求映射到方案上:
- 记住用户偏好:主题色、字体大小、上次停留页面,用 preferences;
- 结构化业务数据:待办清单、聊天记录、账单明细,用关系型数据库;
- 多端数据一致:手机和平板看到同一份数据,用分布式数据或端云协同;
- 大文件与媒体:图片缓存、导出的 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 封装开始,把建表、升级、增删改查集中管理,后面无论功能怎么长,数据层都是稳的。
- 点赞
- 收藏
- 关注作者
评论(0)