HarmonyOS 7 实战 03:本地缓存选 Preferences 还是 RDB?
别一上来就 Preferences 一把梭
做 HarmonyOS 应用,只要涉及数据展示,早晚要碰本地缓存。很多同学第一反应就是 Preferences:简单、key-value、几行代码就能存。于是把用户信息、列表数据、甚至整个 JSON 都往里塞。
结果呢?列表数据稍微大一点,页面就开始卡。点一下卡半秒,滑动掉帧,用户以为你写了个假 App。
问题不在 Preferences 本身,而在你把它用错了地方。Preferences 适合存轻量配置,比如主题、语言、登录 token。一旦数据上了几百条、需要按条件查询、需要分页,就该换关系型数据库 RDB 了。
这篇就讲清楚:Preferences 和 RDB 各自适合什么场景,怎么封装一个统一的缓存层,以及实际开发中那些容易踩的坑。
一、Preferences:轻量配置的优选
Preferences 的 API 很简单,核心就几个方法:
typescript
import preferences from '@ohos.data.preferences';
// 获取 Preferences 实例
let pref = await preferences.getPreferences(context, 'myStore');
// 存
await pref.put('username', '张三');
await pref.flush();
// 取
let username = await pref.get('username', '默认值') as string;
注意几个点:
第一,getPreferences 是异步的。 别在 build() 里调用,最好在 aboutToAppear 或应用启动时初始化一次,把实例存起来复用。
第二,put 之后必须 flush。 不调 flush,数据只存在内存里,应用一杀就没了。很多同学存完没生效,就是漏了这行。
第三,Preferences 有大小限制。 官方建议单个 value 不要超过 8KB,整个 store 不要超过 1MB。你存个长列表 JSON,很容易就超了。
所以 Preferences 的正确用法是:存配置、存标记、存小对象。比如:
typescript
// 用户登录态
await pref.put('token', token);
await pref.put('userId', userId);
await pref.put('theme', 'dark');
// 首次启动标记
await pref.put('hasLaunched', true);
这些数据量小、读写频繁、不需要复杂查询,用 Preferences 最合适。
二、RDB:结构化数据的归宿
当数据变成列表、需要按条件筛选、需要分页、需要排序,就该上 RDB 了。
RDB 基于 SQLite,用法和 Android 的 Room 有点像,但更原始一些。先建表:
typescript
import relationalStore from '@ohos.data.relationalStore';
const STORE_CONFIG: relationalStore.StoreConfig = {
name: 'MyApp.db',
securityLevel: relationalStore.SecurityLevel.S1
};
const SQL_CREATE_TABLE = `
CREATE TABLE IF NOT EXISTS user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER,
email TEXT
)
`;
let rdbStore: relationalStore.RdbStore;
async function initRdb(context: Context) {
rdbStore = await relationalStore.getRdbStore(context, STORE_CONFIG);
await rdbStore.executeSql(SQL_CREATE_TABLE);
}
插入数据:
typescript
const valueBucket: relationalStore.ValuesBucket = {
name: '张三',
age: 28,
email: 'zhangsan@example.com'
};
await rdbStore.insert('user', valueBucket);
查询数据:
typescript
const predicates = new relationalStore.RdbPredicates('user');
predicates.equalTo('age', 28);
predicates.orderByDesc('id');
const resultSet = await rdbStore.query(predicates, ['id', 'name', 'age', 'email']);
while (resultSet.goToNextRow()) {
const id = resultSet.getLong(resultSet.getColumnIndex('id'));
const name = resultSet.getString(resultSet.getColumnIndex('name'));
console.info(`${id} - ${name}`);
}
resultSet.close();
RDB 的优势很明显:支持 SQL、支持索引、支持事务、数据量大也不虚。但缺点是写起来比 Preferences 麻烦,而且需要手动管理结果集关闭,否则会内存泄漏。
三、封装一个统一的缓存层
项目里如果每个页面都直接调 Preferences 或 RDB,代码会散得到处都是。更好的做法是封装一个 CacheManager,对外暴露统一接口,内部根据数据类型选择存储方式。
先定义接口:
typescript
export interface CacheOptions {
type: 'preferences' | 'rdb';
key: string;
value: any;
}
然后实现:
typescript
export class CacheManager {
private static instance: CacheManager;
private pref: preferences.Preferences | null = null;
private rdb: relationalStore.RdbStore | null = null;
static getInstance(): CacheManager {
if (!CacheManager.instance) {
CacheManager.instance = new CacheManager();
}
return CacheManager.instance;
}
async init(context: Context) {
this.pref = await preferences.getPreferences(context, 'app_cache');
this.rdb = await relationalStore.getRdbStore(context, {
name: 'app_cache.db',
securityLevel: relationalStore.SecurityLevel.S1
});
await this.rdb.executeSql(`
CREATE TABLE IF NOT EXISTS cache (
key TEXT PRIMARY KEY,
value TEXT,
updateTime INTEGER
)
`);
}
async set(key: string, value: any, useRdb: boolean = false) {
if (useRdb) {
const bucket: relationalStore.ValuesBucket = {
key,
value: JSON.stringify(value),
updateTime: Date.now()
};
await this.rdb!.insert('cache', bucket);
} else {
await this.pref!.put(key, JSON.stringify(value));
await this.pref!.flush();
}
}
async get<T>(key: string, useRdb: boolean = false): Promise<T | null> {
if (useRdb) {
const predicates = new relationalStore.RdbPredicates('cache');
predicates.equalTo('key', key);
const resultSet = await this.rdb!.query(predicates, ['value']);
if (resultSet.goToFirstRow()) {
const value = resultSet.getString(resultSet.getColumnIndex('value'));
resultSet.close();
return JSON.parse(value) as T;
}
resultSet.close();
return null;
} else {
const value = await this.pref!.get(key, '') as string;
return value ? JSON.parse(value) as T : null;
}
}
}
这样调用方只需要关心 key 和 value,不用管底层用的是 Preferences 还是 RDB。小数据走 Preferences,大数据走 RDB,切换成本很低。
四、踩坑合集
坑一:Preferences 存大 JSON,主线程卡顿。
Preferences 的 put 和 get 虽然是异步的,但底层序列化和反序列化是在主线程做的。你存一个 500KB 的 JSON,序列化就要几十毫秒,页面直接掉帧。所以大列表千万别用 Preferences。
坑二:RDB 查询没关 ResultSet。
上面代码里每次 query 后面都跟了 resultSet.close()。不关的话,游标会一直占着资源,查几次之后应用就崩了。这个坑非常常见,一定要养成习惯。
坑三:RDB 升级没写迁移逻辑。
应用版本更新时,如果数据库表结构变了,需要实现 onUpgrade。不处理的话,老用户升级后直接闪退。
typescript
const STORE_CONFIG: relationalStore.StoreConfig = {
name: 'MyApp.db',
securityLevel: relationalStore.SecurityLevel.S1
};
rdbStore = await relationalStore.getRdbStore(context, STORE_CONFIG);
rdbStore.version = 2; // 设置新版本号,触发 onUpgrade
然后在 getRdbStore 的回调里处理升级。别偷懒,这一步迟早要补。
五、总结
这篇讲了 Preferences 和 RDB 的适用场景、基本用法和封装思路。核心记住三点:
-
小数据用 Preferences,大数据用 RDB,别混着用。
-
Preferences 的 put 后要 flush,RDB 的 ResultSet 用完要 close。
-
封装统一缓存层,别让业务代码直接依赖具体存储 API。
更多推荐



所有评论(0)