HarmonyOS为开发者提供了层次清晰的数据持久化方案,其中用户首选项(Preferences)与关系型数据库(RelationalStore)是处理本地数据存储的两大核心工具。前者如同轻便的“记忆便签”,适合存储用户的个性化设置;后者则像一个功能强大的“数据管家”,专门管理结构复杂、关系紧密的业务数据。下面为你详细解析它们的核心机制、开发实践与选型要点。
一、 用户首选项:轻量级数据的“记忆便签”
用户首选项为应用提供了轻量级的键值对(Key-Value)数据读写能力。它非常适合存储小体量的配置信息,例如用户的字体大小偏好、主题模式(深色/浅色)、是否首次启动等标志位。
核心机制:
用户首选项的数据会以文本文件的形式保存在设备中。当应用访问时,会将文件中的数据全量加载到内存中,因此其读写速度极快,但也正因如此,它不适合存储大量或复杂的数据(数据量建议保持在KB级别)。
1. 开发实战(以“字体大小调节”为例)
本示例将展示如何通过用户首选项保存并读取用户设置的字体大小偏移量。
步骤一:封装工具类
创建一个PreferencesUtil.ets文件,用于集中管理首选项的操作。
typescript
import { preferences } from '@kit.ArkData'; import { BusinessError } from '@kit.BasicServicesKit'; // 定义存储实例的名称和Key const PREFERENCES_NAME: string = 'MyAppPreferences'; const KEY_FONT_SIZE: string = 'fontSizeOffset'; export class PreferencesUtil { private preference?: preferences.Preferences; // 1. 获取首选项实例 async getPreferencesInstance(context: Context) { if (!this.preference) { let options: preferences.Options = { name: PREFERENCES_NAME }; try { // 通过上下文获取Preferences实例 this.preference = await preferences.getPreferences(context, options); } catch (err) { let code = (err as BusinessError).code; console.error(`获取Preferences实例失败,错误码: ${code}`); } } return this.preference; } // 2. 保存数据(写入并持久化) async saveFontSize(context: Context, fontSize: number) { let pref = await this.getPreferencesInstance(context); if (pref) { try { // 写入数据 await pref.put(KEY_FONT_SIZE, fontSize); // 重要:调用flush()方法,将数据从内存持久化到磁盘文件 await pref.flush(); console.info(`字体大小已保存: ${fontSize}`); } catch (err) { console.error(`保存字体大小失败: ${(err as BusinessError).code}`); } } } // 3. 读取数据(若Key不存在,返回默认值) async getFontSize(context: Context): Promise<number> { let pref = await this.getPreferencesInstance(context); let defaultValue: number = 0; // 默认偏移量为0 if (pref) { try { let result = await pref.get(KEY_FONT_SIZE, defaultValue); return result as number; } catch (err) { console.error(`读取字体大小失败: ${(err as BusinessError).code}`); return defaultValue; } } return defaultValue; } }步骤二:在页面中使用
typescript
import { PreferencesUtil } from '../database/PreferencesUtil'; @Entry @Component struct SettingsPage { @State fontSizeOffset: number = 0; private prefUtil: PreferencesUtil = new PreferencesUtil(); async aboutToAppear() { // 页面加载前,读取存储的字体偏移量 this.fontSizeOffset = await this.prefUtil.getFontSize(getContext(this)); } build() { Column() { Text('Hello HarmonyOS') .fontSize(16 + this.fontSizeOffset) // 系统默认大小+偏移量 .fontWeight(FontWeight.Bold) Button('增加字体') .onClick(async () => { this.fontSizeOffset += 2; // 保存新的偏移量 await this.prefUtil.saveFontSize(getContext(this), this.fontSizeOffset); }) } .width('100%') .height('100%') .justifyContent(FlexAlign.Center) } }关键技术点:
flush():写入数据后务必调用flush(),否则数据仅存在于内存中,应用退出后会丢失。默认值:在
get()方法中设置合理的默认值,可以避免因数据不存在而导致的异常。
二、 关系型数据库:结构化数据的“全能管家”
关系型数据库(RelationalStore)底层基于SQLite,提供了一套完整的SQL接口,支持事务、索引、多表关联查询等高级功能。它适用于管理大量、结构化的业务数据,如记账应用的账单、聊天记录、商品列表等。
1. 开发实战(以“运动计划管理”为例)
此案例包含“运动表”(sport)和“计划表”(plan),演示了从建表到联表查询的完整流程。
步骤一:初始化数据库并建表
typescript
// RDBStoreUtil.ets import { relationalStore } from '@kit.ArkData'; import { BusinessError } from '@kit.BasicServicesKit'; const DB_NAME: string = 'Objective.db'; const CREATE_SPORT_TABLE: string = `CREATE TABLE IF NOT EXISTS sport ( id INTEGER PRIMARY KEY AUTOINCREMENT, sport_name TEXT NOT NULL, sport_type TEXT );`; const CREATE_PLAN_TABLE: string = `CREATE TABLE IF NOT EXISTS plan ( id INTEGER PRIMARY KEY AUTOINCREMENT, sport_id INTEGER, duration INTEGER, status INTEGER, FOREIGN KEY (sport_id) REFERENCES sport(id) );`; export class RDBStoreUtil { private rdbStore?: relationalStore.RdbStore; async initRdbStore(context: Context) { if (this.rdbStore) { return this.rdbStore; } const config: relationalStore.StoreConfig = { name: DB_NAME, securityLevel: relationalStore.SecurityLevel.S1 // 设置安全级别 }; try { this.rdbStore = await relationalStore.getRdbStore(context, config); // 执行建表语句 await this.rdbStore.executeSql(CREATE_SPORT_TABLE); await this.rdbStore.executeSql(CREATE_PLAN_TABLE); console.info('数据库初始化及建表成功'); return this.rdbStore; } catch (err) { console.error(`初始化数据库失败: ${(err as BusinessError).code}`); } } // ... 后续CRUD方法 }步骤二:实现数据的增删改查
typescript
// 1. 插入数据(使用ValuesBucket封装) async insertPlan(sportId: number, duration: number) { if (!this.rdbStore) return -1; const valueBucket: relationalStore.ValuesBucket = { 'sport_id': sportId, 'duration': duration, 'status': 0 // 0表示未完成 }; try { let rowId = await this.rdbStore.insert('plan', valueBucket); console.info(`插入计划成功,rowId: ${rowId}`); return rowId; } catch (err) { console.error(`插入失败: ${(err as BusinessError).code}`); return -1; } } // 2. 联表查询(查询所有计划及其对应的运动名称) async queryAllPlansWithSport(): Promise<Array<object>> { if (!this.rdbStore) return []; const sql = `SELECT plan.id, plan.duration, plan.status, sport.sport_name, sport.sport_type FROM plan INNER JOIN sport ON plan.sport_id = sport.id`; let resultSet: relationalStore.ResultSet | undefined; try { // 执行查询SQL resultSet = await this.rdbStore.querySql(sql); let resultArray: Array<object> = []; // 遍历结果集 while (resultSet.goToNextRow()) { let id = resultSet.getLong(resultSet.getColumnIndex('id')); let duration = resultSet.getLong(resultSet.getColumnIndex('duration')); let sportName = resultSet.getString(resultSet.getColumnIndex('sport_name')); resultArray.push({ id, duration, sportName }); } return resultArray; } catch (err) { console.error(`联表查询失败: ${(err as BusinessError).code}`); return []; } finally { // 关键:必须关闭ResultSet以释放内存 resultSet?.close(); } }关键技术点:
ResultSet管理:查询返回的ResultSet对象在使用完毕后必须调用close()方法释放资源,否则会造成内存泄漏。事务:对于批量插入或更新等需要保持数据一致性的操作,应使用
beginTransaction()、commit()和rollback()来管理事务。
三、 如何选择?一张表看清差异
| 特性维度 | 用户首选项 (Preferences) | 关系型数据库 (RelationalStore) |
|---|---|---|
| 数据模型 | 键值对 (Key-Value) | 二维表 (行与列) |
| 适用数据量 | 小(KB级别) | 大(无严格上限,建议单条<2MB) |
| 查询能力 | 简单,仅能通过Key查询 | 强大,支持复杂SQL、索引、联表查询 |
| 典型场景 | 用户设置、主题、登录Token、应用配置 | 记账记录、通讯录、商品订单、聊天消息 |
| 事务支持 | 不支持 | 完整支持(ACID) |
| 性能特点 | 全量加载到内存,读取速度极快 | 按需查询,支持分页和索引优化 |
总结
用户首选项是轻量级存储的首选,其简单、快速的特点使其成为管理应用“状态”和“偏好”的最佳工具。牢记使用
flush()确保持久化。关系型数据库则是处理复杂业务逻辑和海量数据的中坚力量。它提供的SQL能力、事务机制和数据结构化能力,能满足绝大多数应用的核心数据管理需求。开发时注意合理使用事务和及时释放
ResultSet资源。