HarmonyOS 6.1 数据持久化与首选项实战:Preferences 和 RDB 的那些坑
作者踩坑实录,写于连续加班排查"数据莫名丢失"bug的第三天凌晨。
前言
说实话,我一开始根本没把数据持久化当回事。HarmonyOS NEXT 嘛,写个 putSync 存个配置,完事儿。结果上线后用户反馈"设置项重启就没了",我排查了一整天才发现——我没调 flushSync。
那一刻我真的很想砸键盘。
所以今天这篇文章,不是官方文档的复读机,而是我用血泪换来的实战经验。Preferences、RDB、分布式KV,我都踩过坑,一个一个给你讲清楚。

一、Preferences:轻量键值存储,简单但暗藏杀机
1.1 什么是 Preferences
Preferences 就是 HarmonyOS NEXT 提供的轻量级键值对存储方案。你可以把它理解成 Android 的 SharedPreferences,或者 iOS 的 UserDefaults,或者前端的 localStorage——反正就是那种"存个小配置、小标记"的东西。
它的定位很明确:轻量、同步、小数据。如果你存的东西超过 2KB,趁早打消念头,后面我会说为什么。
1.2 核心 API 一览
先看一段完整的示例代码,来自我的 Demo 页面 DataPersistDemo:
import { preferences } from '@kit.ArkData';
const PREFERENCES_NAME = 'my_app_prefs';
const KEY_THEME = 'theme_mode';
const KEY_FONT_SIZE = 'font_size';
// 获取 Preferences 实例
let prefs: preferences.Preferences | null = null;
function initPreferences(context: Context): void {
prefs = preferences.getPreferencesSync(context, { name: PREFERENCES_NAME });
}
function saveThemeMode(mode: string): void {
if (prefs) {
prefs.putSync(KEY_THEME, mode);
// 千万别忘了 flush!!!
prefs.flushSync();
}
}
function getThemeMode(): string {
if (prefs) {
return prefs.getSync(KEY_THEME, 'light') as string;
}
return 'light';
}
function deleteThemeMode(): void {
if (prefs) {
prefs.deleteSync(KEY_THEME);
prefs.flushSync();
}
}
逐个说下这些 API:
| API | 作用 | 同步/异步 |
|---|---|---|
getPreferencesSync |
获取 Preferences 实例 | 同步 |
putSync |
写入键值对 | 同步 |
getSync |
读取键值对 | 同步 |
deleteSync |
删除键值对 | 同步 |
flushSync |
持久化到磁盘 | 同步 |
注意到没有?全是 Sync 结尾。Preferences 的 API 设计就是同步优先,用起来很舒服,不用包一堆 async/await。
1.3 最大的坑:put 不 flush,等于白存
这是我踩过的最痛的坑,必须单独拿一节来说。
putSync 只是把数据写到了内存缓存里,没有写磁盘! 你必须显式调用 flushSync() 才会真正持久化。
我第一次用的时候写了这样的代码:
// 错误示范!!!
prefs.putSync('theme_mode', 'dark');
// 这里没调 flushSync,进程一杀数据就没了
当时在开发阶段一切正常,因为应用进程没被杀,内存里的数据还在。但一旦应用被系统回收,或者用户手动清理后台,数据就没了。我排查了很久才意识到这个问题。
正确的写法:
// 正确做法:put 之后立刻 flush
prefs.putSync('theme_mode', 'dark');
prefs.flushSync();
或者你也可以批量写完再 flush:
prefs.putSync('theme_mode', 'dark');
prefs.putSync('font_size', 16);
prefs.putSync('language', 'zh');
prefs.flushSync(); // 一次性刷盘
总之原则就一条:写了就 flush,别犹豫。 你可以理解为 put 是写内存,flush 是存磁盘,这是两步操作,不是一步。
1.4 Preferences 不适合大数据
官方文档说 Preferences 适合"轻量数据",但到底多轻?我实测的经验是:单个键值别超过 2KB,整个 Preferences 文件别超过几百 KB。
为什么?因为 Preferences 是全量读写的设计。你每次 flush,都是把整个文件重写一遍。数据量一上去,性能就拉胯。我有一次把一个 JSON 数组往 Preferences 里塞了 50KB 的数据,flush 的时候明显感觉到卡顿。
所以如果你要存的数据量超过 2KB,老老实实用 RDB 或者分布式 KV 吧。
1.5 getContext 已废弃的问题
这是个版本迁移的坑。早期 HarmonyOS 代码里到处都是 getContext(this),但在新版本中这个 API 已经废弃了。
// 旧写法(已废弃)
let context = getContext(this);
// 新写法
let context = this.getUIContext().getHostContext()!;
如果你还在用 getContext,编译时可能不会报错(取决于 SDK 版本),但运行时行为不可预期,而且迟早会被彻底移除。建议立刻迁移。
在 DataPersistDemo 中,我的初始化代码是这样的:
@Entry
@Component
struct DataPersistDemo {
private prefs: preferences.Preferences | null = null;
aboutToAppear(): void {
let context = this.getUIContext().getHostContext()!;
this.prefs = preferences.getPreferencesSync(context, { name: 'demo_prefs' });
}
}
1.6 Preferences 完整实战代码片段
下面是 DataPersistDemo 中 Preferences 部分的完整交互代码:
import { preferences } from '@kit.ArkData';
@Entry
@Component
struct DataPersistDemo {
@State themeMode: string = 'light';
@State fontSize: number = 14;
@State outputLog: string = '';
private prefs: preferences.Preferences | null = null;
aboutToAppear(): void {
let context = this.getUIContext().getHostContext()!;
this.prefs = preferences.getPreferencesSync(context, { name: 'demo_prefs' });
this.loadPrefs();
}
private loadPrefs(): void {
if (this.prefs) {
this.themeMode = this.prefs.getSync('theme_mode', 'light') as string;
this.fontSize = this.prefs.getSync('font_size', 14) as number;
this.log('读取配置完成:theme=' + this.themeMode + ', fontSize=' + this.fontSize);
}
}
private savePrefs(): void {
if (this.prefs) {
this.prefs.putSync('theme_mode', this.themeMode);
this.prefs.putSync('font_size', this.fontSize);
this.prefs.flushSync();
this.log('保存配置完成(已flush)');
}
}
private deletePref(key: string): void {
if (this.prefs) {
this.prefs.deleteSync(key);
this.prefs.flushSync();
this.log('删除配置项:' + key);
}
}
private log(msg: string): void {
this.outputLog = msg + '\n' + this.outputLog;
}
build() {
Column() {
// Preferences 操作区域
Text('Preferences 键值存储')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Row() {
Button('切换主题')
.onClick(() => {
this.themeMode = this.themeMode === 'light' ? 'dark' : 'light';
this.savePrefs();
})
Button('增大字号')
.onClick(() => {
this.fontSize += 2;
this.savePrefs();
})
Button('删除主题配置')
.onClick(() => {
this.deletePref('theme_mode');
this.themeMode = 'light';
})
}
Text('当前主题: ' + this.themeMode + ' | 字号: ' + this.fontSize)
.margin({ top: 10 })
Scroll() {
Text(this.outputLog)
.fontSize(12)
.fontColor('#666666')
}
.height(200)
.margin({ top: 10 })
}
.padding(20)
}
}
二、RDB:关系型数据库,重炮手
2.1 什么是 RDB
RDB(Relational Database)是 HarmonyOS NEXT 提供的关系型数据库,底层基于 SQLite。如果你做过 Android 开发,它就相当于 Room;如果你做过 iOS 开发,它相当于 Core Data 的 SQLite 模式。
适合场景:结构化数据、数据量较大、需要复杂查询。比如用户的聊天记录、订单列表、本地缓存的文章数据,这些都该用 RDB。
2.2 核心 API 一览
| API | 作用 |
|---|---|
getRdbStore |
获取/创建数据库实例 |
executeSql |
执行原始 SQL(建表等) |
insert |
插入数据 |
query |
查询数据 |
delete |
删除数据 |
update |
更新数据 |
RdbPredicates |
查询条件构造器 |
ValuesBucket |
键值对数据容器 |
注意一个关键区别:executeSql 是同步的,但 insert/query/delete/update 是异步的! 这个不一致设计坑了我好几次,后面细说。
2.3 创建数据库和表
import { relationalStore } from '@kit.ArkData';
const STORE_CONFIG: relationalStore.StoreConfig = {
name: 'demo_db.db',
securityLevel: relationalStore.SecurityLevel.S1,
};
const CREATE_TABLE_SQL = `
CREATE TABLE IF NOT EXISTS user_notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT NOT NULL,
created_time INTEGER NOT NULL,
is_deleted INTEGER DEFAULT 0
)
`;
let rdbStore: relationalStore.RdbStore | null = null;
async function initRdb(context: Context): Promise<void> {
rdbStore = await relationalStore.getRdbStore(context, STORE_CONFIG);
rdbStore.executeSql(CREATE_TABLE_SQL);
}
这里 getRdbStore 是异步的(返回 Promise),而 executeSql 是同步的。这种混搭风格说实话有点别扭,但用多了就习惯了。
securityLevel 是 HarmonyOS 的安全级别设计,一般本地数据用 S1 就行,如果是敏感数据可以用更高级别。
2.4 插入数据:ValuesBucket + insert
async function insertNote(title: string, content: string): Promise<number> {
if (!rdbStore) {
return -1;
}
const valueBucket: relationalStore.ValuesBucket = {
'title': title,
'content': content,
'created_time': Date.now(),
'is_deleted': 0,
};
const rowId = await rdbStore.insert('user_notes', valueBucket);
return rowId;
}
ValuesBucket 就是一个键值对容器,键是列名,值是对应的数据。跟 Android 的 ContentValues 一个意思。
注意:insert 返回的是行 ID(number),不是布尔值。 返回 -1 表示失败。
2.5 查询数据:RdbPredicates + query
async function queryAllNotes(): Promise<relationalStore.ResultSet> {
if (!rdbStore) {
throw new Error('RDB not initialized');
}
const predicates = new relationalStore.RdbPredicates('user_notes');
predicates.equalTo('is_deleted', 0);
predicates.orderByDesc('created_time');
const resultSet = await rdbStore.query(predicates);
return resultSet;
}
async function queryNotesByTitle(keyword: string): Promise<relationalStore.ResultSet> {
if (!rdbStore) {
throw new Error('RDB not initialized');
}
const predicates = new relationalStore.RdbPredicates('user_notes');
predicates.equalTo('is_deleted', 0);
predicates.like('title', '%' + keyword + '%');
const resultSet = await rdbStore.query(predicates);
return resultSet;
}
RdbPredicates 是查询条件构造器,用法类似 Android 的 ContentQuery 或 Room 的 Query Builder。支持 equalTo、like、between、in、groupBy、orderByDesc 等各种条件。
2.6 读取 ResultSet
ResultSet 的读取是个容易出错的地方,我第一次用的时候就在列索引上栽了跟头:
function parseNotes(resultSet: relationalStore.ResultSet): Array<NoteItem> {
const notes: Array<NoteItem> = [];
if (resultSet.rowCount <= 0) {
return notes;
}
resultSet.goToFirstRow();
do {
const id = resultSet.getLong(resultSet.getColumnIndex('id'));
const title = resultSet.getString(resultSet.getColumnIndex('title'));
const content = resultSet.getString(resultSet.getColumnIndex('content'));
const createdTime = resultSet.getLong(resultSet.getColumnIndex('created_time'));
notes.push({
id: id,
title: title,
content: content,
createdTime: createdTime,
});
} while (resultSet.goToNextRow());
resultSet.close();
return notes;
}
几个要点:
- 必须先判断
rowCount,否则空结果集上调用goToFirstRow可能出问题。 - 用
getColumnIndex获取列索引,不要硬编码数字索引,容易错。 - 用完必须
close(),否则会泄漏资源。我用 try-finally 包裹更保险。 goToFirstRow+do...while(goToNextRow)是标准遍历模式。
2.7 删除数据
async function deleteNoteById(id: number): Promise<number> {
if (!rdbStore) {
return -1;
}
const predicates = new relationalStore.RdbPredicates('user_notes');
predicates.equalTo('id', id);
const deletedRows = await rdbStore.delete(predicates);
return deletedRows;
}
返回值是受影响的行数,0 表示没删到东西。
2.8 那个令人困惑的同步/异步混搭
这是我必须吐槽的一点。看这个对比:
| 方法 | 同步/异步 | 返回值 |
|---|---|---|
executeSql |
同步 | void |
insert |
异步 | Promise<number> |
query |
异步 | Promise<ResultSet> |
delete |
异步 | Promise<number> |
update |
异步 | Promise<number> |
executeSql 是同步的,但增删查改全是异步的。这意味着你不能这么写:
// 错误!insert 是异步的,不能用同步思路
rdbStore.executeSql('CREATE TABLE ...');
const rowId = rdbStore.insert('user_notes', valueBucket); // rowId 是 Promise,不是 number!
必须用 await:
// 正确
rdbStore.executeSql('CREATE TABLE ...'); // 同步,没问题
const rowId = await rdbStore.insert('user_notes', valueBucket); // 异步,必须 await
这种不一致的设计说实话挺恶心的,容易让人写错。我的建议是:RDB 相关的所有操作统一放在 async 函数里,该 await 的 await,别省。
三、DataPersistDemo 完整代码
把 Preferences 和 RDB 整合在一起的 Demo 页面:
import { preferences, relationalStore } from '@kit.ArkData';
interface NoteItem {
id: number;
title: string;
content: string;
createdTime: number;
}
@Entry
@Component
struct DataPersistDemo {
@State themeMode: string = 'light';
@State fontSize: number = 14;
@State outputLog: string = '';
@State noteTitle: string = '';
@State noteContent: string = '';
@State noteList: Array<NoteItem> = [];
private prefs: preferences.Preferences | null = null;
private rdbStore: relationalStore.RdbStore | null = null;
private readonly STORE_CONFIG: relationalStore.StoreConfig = {
name: 'demo_db.db',
securityLevel: relationalStore.SecurityLevel.S1,
};
private readonly CREATE_TABLE_SQL: string =
'CREATE TABLE IF NOT EXISTS user_notes (' +
'id INTEGER PRIMARY KEY AUTOINCREMENT, ' +
'title TEXT NOT NULL, ' +
'content TEXT NOT NULL, ' +
'created_time INTEGER NOT NULL, ' +
'is_deleted INTEGER DEFAULT 0)';
async aboutToAppear(): Promise<void> {
let context = this.getUIContext().getHostContext()!;
this.prefs = preferences.getPreferencesSync(context, { name: 'demo_prefs' });
this.themeMode = this.prefs.getSync('theme_mode', 'light') as string;
this.fontSize = this.prefs.getSync('font_size', 14) as number;
this.rdbStore = await relationalStore.getRdbStore(context, this.STORE_CONFIG);
this.rdbStore.executeSql(this.CREATE_TABLE_SQL);
await this.refreshNoteList();
this.log('初始化完成:Preferences + RDB 均已就绪');
}
private savePrefs(): void {
if (this.prefs) {
this.prefs.putSync('theme_mode', this.themeMode);
this.prefs.putSync('font_size', this.fontSize);
this.prefs.flushSync();
this.log('Preferences 已保存并flush');
}
}
private async addNote(): Promise<void> {
if (!this.rdbStore || !this.noteTitle || !this.noteContent) {
this.log('标题和内容不能为空');
return;
}
const valueBucket: relationalStore.ValuesBucket = {
'title': this.noteTitle,
'content': this.noteContent,
'created_time': Date.now(),
'is_deleted': 0,
};
const rowId = await this.rdbStore.insert('user_notes', valueBucket);
this.log('插入笔记成功,rowId=' + rowId);
this.noteTitle = '';
this.noteContent = '';
await this.refreshNoteList();
}
private async deleteNote(id: number): Promise<void> {
if (!this.rdbStore) {
return;
}
const predicates = new relationalStore.RdbPredicates('user_notes');
predicates.equalTo('id', id);
const deletedRows = await this.rdbStore.delete(predicates);
this.log('删除笔记,id=' + id + ',受影响行数=' + deletedRows);
await this.refreshNoteList();
}
private async refreshNoteList(): Promise<void> {
if (!this.rdbStore) {
return;
}
const predicates = new relationalStore.RdbPredicates('user_notes');
predicates.equalTo('is_deleted', 0);
predicates.orderByDesc('created_time');
const resultSet = await this.rdbStore.query(predicates);
const notes: Array<NoteItem> = [];
if (resultSet.rowCount > 0) {
resultSet.goToFirstRow();
do {
notes.push({
id: resultSet.getLong(resultSet.getColumnIndex('id')),
title: resultSet.getString(resultSet.getColumnIndex('title')),
content: resultSet.getString(resultSet.getColumnIndex('content')),
createdTime: resultSet.getLong(resultSet.getColumnIndex('created_time')),
});
} while (resultSet.goToNextRow());
}
resultSet.close();
this.noteList = notes;
}
private log(msg: string): void {
const time = new Date().toLocaleTimeString();
this.outputLog = '[' + time + '] ' + msg + '\n' + this.outputLog;
}
build() {
Scroll() {
Column({ space: 20 }) {
Text('数据持久化实战 Demo')
.fontSize(24)
.fontWeight(FontWeight.Bold)
// ---- Preferences 区域 ----
Column({ space: 10 }) {
Text('Preferences 键值存储')
.fontSize(18)
.fontWeight(FontWeight.Medium)
Text('当前主题: ' + this.themeMode + ' | 字号: ' + this.fontSize)
Row({ space: 10 }) {
Button('切换主题')
.onClick(() => {
this.themeMode = this.themeMode === 'light' ? 'dark' : 'light';
this.savePrefs();
})
Button('字号+2')
.onClick(() => {
this.fontSize += 2;
this.savePrefs();
})
Button('重置')
.onClick(() => {
if (this.prefs) {
this.prefs.deleteSync('theme_mode');
this.prefs.deleteSync('font_size');
this.prefs.flushSync();
this.themeMode = 'light';
this.fontSize = 14;
this.log('Preferences 已重置');
}
})
}
}
.padding(15)
.backgroundColor('#f5f5f5')
.borderRadius(12)
// ---- RDB 区域 ----
Column({ space: 10 }) {
Text('RDB 关系型数据库')
.fontSize(18)
.fontWeight(FontWeight.Medium)
TextInput({ placeholder: '笔记标题' })
.onChange((value: string) => { this.noteTitle = value; })
TextInput({ placeholder: '笔记内容' })
.onChange((value: string) => { this.noteContent = value; })
Button('添加笔记')
.width('100%')
.onClick(() => { this.addNote(); })
if (this.noteList.length > 0) {
ForEach(this.noteList, (note: NoteItem) => {
Row() {
Column() {
Text(note.title).fontWeight(FontWeight.Medium)
Text(note.content).fontSize(12).fontColor('#666666').maxLines(1)
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
Text(new Date(note.createdTime).toLocaleString())
.fontSize(10)
.fontColor('#999999')
Button('删除')
.fontSize(12)
.height(30)
.onClick(() => { this.deleteNote(note.id); })
}
.padding(10)
.backgroundColor(Color.White)
.borderRadius(8)
}, (note: NoteItem) => note.id.toString())
} else {
Text('暂无笔记数据')
.fontColor('#999999')
}
}
.padding(15)
.backgroundColor('#f5f5f5')
.borderRadius(12)
// ---- 日志区域 ----
Column() {
Text('操作日志')
.fontSize(16)
.fontWeight(FontWeight.Medium)
Scroll() {
Text(this.outputLog)
.fontSize(11)
.fontColor('#666666')
.copyOption(CopyOptions.LocalDevice)
}
.height(180)
}
.padding(15)
.backgroundColor('#f5f5f5')
.borderRadius(12)
}
.padding(20)
}
}
}

四、三大持久化方案横向对比
很多人问"我该用哪个",我给你一张表,一目了然:
| 对比维度 | Preferences | RDB | 分布式 KV |
|---|---|---|---|
| 数据模型 | 键值对 | 关系型表 | 键值对(可跨设备同步) |
| 适用数据量 | < 2KB | 无硬上限(SQLite 限制) | 单条 < 2KB,总量可更大 |
| API 风格 | 全同步 | 混合(executeSql 同步,CRUD 异步) | 异步为主 |
| 查询能力 | 无(只能按 key 取) | SQL 全量查询 | 支持条件查询 |
| 跨设备同步 | 不支持 | 不支持 | 支持(需组网) |
| 事务支持 | 无 | 有 | 有 |
| 数据持久化时机 | 需手动 flush | 自动(每条操作即持久) | 自动 |
| 典型场景 | 用户设置、开关状态、简单标记 | 聊天记录、订单列表、结构化业务数据 | 多设备配置同步、跨端状态共享 |
| 学习成本 | 低 | 中(需懂 SQL) | 中高(需理解分布式) |
| 性能(小数据读写) | 极快 | 快 | 快 |
| 性能(大数据量) | 慢(全量读写) | 快(索引查询) | 中等 |
我的选择原则很简单:
- 就存个设置项?Preferences。 别忘了 flush。
- 有结构化数据要查?RDB。 老老实实建表写 SQL。
- 要多设备同步?分布式 KV。 但你要确保你的场景真的需要同步,别为了用而用。
五、那些我踩过的坑,一个都别漏
坑 1:put 不 flush = 数据只在内存
这个前面说过了,再强调一遍:putSync 只写内存,flushSync 才写磁盘。 忘了 flush,数据就是临时工,进程一杀就走人。
坑 2:RDB 的 executeSql 同步但 insert/query/delete 异步
// 这种写法看似合理,实则 rowId 是 Promise 不是 number
rdbStore.executeSql('CREATE TABLE IF NOT EXISTS ...');
let rowId = rdbStore.insert('user_notes', valueBucket); // 错!
console.log('插入行:' + rowId); // 打印 [object Promise]
// 正确做法
let rowId = await rdbStore.insert('user_notes', valueBucket);
console.log('插入行:' + rowId); // 打印实际的行号
坑 3:getContext 已废弃
// 旧写法,已废弃
let context = getContext(this);
// 新写法
let context = this.getUIContext().getHostContext()!;
getContext 在某些版本还能跑,但这是定时炸弹,迟早爆炸。尽早迁移到 this.getUIContext()。
坑 4:Preferences 存大数据会卡
我之前犯过傻,把一个 50KB 的 JSON 字符串往 Preferences 里塞。flush 的时候肉眼可见的卡顿。原因前面说了:Preferences 是全量读写,每次 flush 重写整个文件。 数据大了必然慢。
还有更严重的隐患:如果 flush 过程中应用崩溃,可能导致文件损坏,数据全丢。所以别挑战 Preferences 的容量极限。
坑 5:INTERNET 权限与数据同步
如果你的应用涉及分布式数据同步(分布式 KV),别忘了在 module.json5 里加网络权限:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
}
不加这个权限,分布式同步静默失败,不会给你报错,日志里也不一定能看出来。我就因为这个浪费了半天。
坑 6:ResultSet 不 close 会泄漏
// 错误:用完不关
const resultSet = await rdbStore.query(predicates);
// 读完数据就丢...
// 下次查询可能就 OOM 了
// 正确:用完关闭
const resultSet = await rdbStore.query(predicates);
try {
// 读取数据...
} finally {
resultSet.close();
}
坑 7:Preferences 的 key 写错不会报错
getSync 的时候如果 key 不存在,它不会报错,而是返回你给的那个默认值。这本来是个设计优点,但如果你 key 拼写错误(比如 theme_mod 写成 thme_mode),你不会得到任何提示,只会拿到默认值,然后一脸懵逼"我明明存了啊"。
我的做法是:把所有 key 定义成常量,不要到处写魔法字符串。
const PREF_KEY_THEME = 'theme_mode';
const PREF_KEY_FONT_SIZE = 'font_size';
const PREF_KEY_LANGUAGE = 'language';
// 使用
prefs.putSync(PREF_KEY_THEME, 'dark');
六、什么时候用哪个?我的决策树
最后给一个实用的决策流程:
需要持久化数据?
├── 数据是简单的键值对?< 2KB?
│ ├── 是 → Preferences(别忘了 flush)
│ └── 否 → 数据需要跨设备同步?
│ ├── 是 → 分布式 KV
│ └── 否 → RDB
├── 数据是结构化的?需要条件查询/排序/分页?
│ └── 是 → RDB
└── 多设备场景?配置/状态需同步?
└── 是 → 分布式 KV
简单说:小配置用 Preferences,结构化数据用 RDB,跨设备用分布式 KV。 别把 Preferences 当数据库用,也别为了存个布尔值去建表。
七、总结
这篇文章写了这么多,其实核心就几句话:
- Preferences 一定要 flush,不然白存。
- RDB 的 API 同步异步混搭,别搞混了。
- 别用 getContext,用
this.getUIContext()。 - Preferences 别存大数据,2KB 是红线。
- 分布式同步要 INTERNET 权限,不然静默失败。
- ResultSet 一定要 close,不然内存泄漏。
数据持久化不是什么高深的东西,但每个坑都够你调试半天。希望这篇文章能帮你少走弯路,别像我一样凌晨两点还在查"为什么数据丢了"。
共勉。
更多推荐



所有评论(0)