ArkTS数据存储全解析
ArkTS数据存储机制、使用方法及最佳实践
在HarmonyOS应用开发中,ArkTS作为核心的声明式开发范式,提供了多层次、多类型的数据存储与状态管理方案,以满足从简单配置到复杂结构化数据、从临时状态到持久化存储的多样化需求。其核心设计思想是根据数据的不同生命周期、访问范围和结构特性,选用最合适的存储方案,以确保应用性能、数据安全与开发效率。
为快速理解各方案的定位与选择依据,可参考下表对比:
| 方案类别 | 核心机制 | 数据生命周期 | 主要适用场景 | 关键特性 |
|---|---|---|---|---|
| 本地持久化存储 | Preferences(键值对) | 应用卸载前 | 用户偏好、简单配置 | 轻量、异步、键值对 |
| 关系型数据库(SQLite) | 应用卸载前 | 复杂结构化数据、需查询排序 | 完整SQL支持、事务、加密 | |
| 文件系统API | 应用卸载前 | 图片、文档、缓存文件等非结构化数据 | 灵活、流式操作 | |
| 应用状态管理 | AppStorage(应用级) | 应用运行时 | 跨页面/组件的全局状态(如用户登录信息) | 应用级单例、响应式同步 |
| LocalStorage(页面级) | 页面实例运行时 | 同一页面内组件间共享状态(如表单数据) | 页面级实例、响应式同步 | |
| PersistentStorage(持久化状态桥) | 持久化 + 运行时 | 需持久化且全局访问的状态(如主题、令牌) | 连接AppStorage与持久化存储 |
一、本地持久化存储方案详解与代码实践
本地持久化存储用于将数据永久保存在设备上,确保应用重启后数据不丢失,是存储用户生成内容或配置的核心手段。
1. Preferences(首选项)
这是一种轻量级的键值对存储,适用于保存用户偏好设置、简单的应用配置或标志位。其操作基于异步Promise,支持创建多个数据库实例以实现数据按业务模块隔离。
// 导入preferences模块
import preferences from '@ohos.data.preferences';
async function demoPreferences() {
// 1. 获取UIAbility的上下文,通常在EntryAbility中获取
let context = ...; // 例如:globalThis.abilityContext
// 创建或打开名为`user_settings`的Preferences实例
let pref: preferences.Preferences = await preferences.getPreferences(context, 'user_settings');
// 2. 存储数据(支持string、number、boolean等基本类型)
await pref.put('username', '李华');
await pref.put('notifications_enabled', true);
await pref.put('last_login_time', Date.now());
await pref.flush(); // 将内存中的数据异步写入持久化文件
// 3. 读取数据,需提供默认值
let username: string = await pref.get('username', 'Guest');
let notificationsOn: boolean = await pref.get('notifications_enabled', false);
console.log(`用户名: ${username}, 通知开启: ${notificationsOn}`);
// 4. 数据管理
await pref.delete('last_login_time'); // 删除单个键
// await pref.clear(); // 清空当前数据库所有数据(慎用)
}
2. 关系型数据库(SQLite)
对于需要复杂查询、事务支持、数据关联和索引的结构化数据,应使用内嵌的SQLite关系型数据库。ArkTS通过@ohos.data.relationalStore模块提供了完整的ORM和SQL操作能力。
// 导入relationalStore模块
import relationalStore from '@ohos.data.relationalStore';
import common from '@ohos.app.ability.common';
async function demoRdb() {
// 1. 定义SQL语句(建议使用常量管理)
const SQL_CREATE_TABLE = `
CREATE TABLE IF NOT EXISTS task (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
description TEXT,
priority INTEGER DEFAULT 0,
created_date INTEGER,
is_done INTEGER DEFAULT 0 CHECK(is_done IN (0, 1))
)
`;
// 2. 配置并获取RdbStore连接
let context: common.UIAbilityContext = ...; // 获取UIAbilityContext
const STORE_CONFIG: relationalStore.StoreConfig = {
name: 'task.db', // 数据库文件名称
securityLevel: relationalStore.SecurityLevel.S1 // 设置数据库安全级别,S1为基础加密
};
let rdbStore: relationalStore.RdbStore;
try {
rdbStore = await relationalStore.getRdbStore(context, STORE_CONFIG);
console.info('RDB Store created successfully.');
} catch (err) {
console.error(`Failed to get RdbStore. Code: ${err.code}, message: ${err.message}`);
return;
}
// 3. 执行建表语句
await rdbStore.executeSql(SQL_CREATE_TABLE);
// 4. 插入数据(使用ValuesBucket封装数据)
const taskToInsert: relationalStore.ValuesBucket = {
'title': '学习ArkTS数据存储',
'description': '掌握Preferences和RDB的使用',
'priority': 1,
'created_date': Date.now()
};
let insertId = await rdbStore.insert('task', taskToInsert);
console.info(`Inserted task with ID: ${insertId}`);
// 5. 查询数据(使用RdbPredicates构建查询条件)
let predicates = new relationalStore.RdbPredicates('task');
predicates.equalTo('is_done', 0) // 查询未完成的任务
.orderByDesc('priority') // 按优先级降序
.limit(10); // 限制返回10条
let columns: Array<string> = ['id', 'title', 'priority'];
let resultSet: relationalStore.ResultSet = await rdbStore.query(predicates, columns);
// 6. 遍历结果集
while (resultSet.goToNextRow()) {
let id = resultSet.getLong(resultSet.getColumnIndex('id'));
let title = resultSet.getString(resultSet.getColumnIndex('title'));
console.info(`Task [${id}]: ${title}`);
}
resultSet.close(); // 必须关闭结果集释放资源
// 7. 更新数据
let updateValues: relationalStore.ValuesBucket = { 'is_done': 1 };
let updatePredicates = new relationalStore.RdbPredicates('task');
updatePredicates.equalTo('id', insertId);
let rowsUpdated = await rdbStore.update(updateValues, updatePredicates);
console.info(`Updated ${rowsUpdated} row(s).`);
// 8. 使用事务确保数据一致性(例如:标记完成并记录完成时间)
await rdbStore.beginTransaction(); // 开始事务
try {
await rdbStore.update(updateValues, updatePredicates);
// 可以在此执行其他相关更新操作...
await rdbStore.commit(); // 提交事务
} catch (e) {
await rdbStore.rollback(); // 发生错误时回滚
console.error('Transaction failed:', e);
}
}
3. 文件系统API
通过@ohos.file.fs等模块,可以直接操作应用沙箱内的目录和文件,适合存储图片、音频、视频、文档或任何自定义格式的缓存文件。这是最灵活但也最底层的存储方式。
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
function demoFileSystem() {
// 获取应用的文件目录路径(沙箱内)
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir; // 类似 `/data/app/.../files`
let cacheDir = context.cacheDir; // 缓存目录,系统可能在存储不足时清理
// 场景1:写入文本配置文件
let configPath = `${filesDir}/app_config.json`;
let configContent = JSON.stringify({ theme: 'dark', language: 'zh-CN' });
// 使用同步API示例(也有异步版本)
let file = fs.openSync(configPath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
fs.writeSync(file.fd, configContent);
fs.closeSync(file.fd);
console.info(`Config saved to: ${configPath}`);
// 场景2:读取文件
file = fs.openSync(configPath, fs.OpenMode.READ_ONLY);
let stat = fs.statSync(file.fd);
let buffer = new ArrayBuffer(stat.size);
let readLen = fs.readSync(file.fd, buffer);
let readContent = String.fromCharCode.apply(null, new Uint8Array(buffer.slice(0, readLen)));
fs.closeSync(file.fd);
let config = JSON.parse(readContent);
console.info(`Read config theme: ${config.theme}`);
// 场景3:管理文件与目录
let imagesDir = `${filesDir}/images`;
if (!fs.accessSync(imagesDir)) {
fs.mkdirSync(imagesDir); // 创建目录
}
// 列出目录内容
let dir = fs.opendirSync(imagesDir);
let entry = fs.readSync(dir);
while (entry !== undefined) {
console.info(`Found entry: ${entry.name}`);
entry = fs.readSync(dir);
}
fs.closeSync(dir);
}
二、应用状态管理方案详解与代码实践
状态管理用于在应用运行时高效地共享和同步可变数据,其核心是响应式机制。AppStorage和LocalStorage通过与UI组件装饰器(如@StorageLink)绑定,实现数据变化自动触发UI更新的效果。
1. AppStorage:应用级全局状态单例AppStorage是应用范围内唯一的可变状态管理中心,类似于“全局状态仓库”。任何组件都可以通过装饰器与之建立连接,读取或修改其中的状态,修改会实时同步到所有绑定了该状态的组件。
// 在任意ets文件中操作AppStorage
import { AppStorage } from '@kit.ArkUI';
// --- 在应用逻辑层(如ViewModel或Service)中管理状态 ---
function initAppGlobalState() {
// SetOrCreate: 如果key不存在则创建并设置初始值,存在则更新
AppStorage.SetOrCreate<number>('globalCounter', 0);
AppStorage.SetOrCreate<string>('currentUser', '');
AppStorage.SetOrCreate<boolean>('isDarkMode', false);
}
function updateUserProfile(name: string) {
// Set: 直接更新已有key的值,会触发所有相关UI更新
AppStorage.Set<string>('currentUser', name);
}
// --- 在UI组件中使用 ---
@Entry
@Component
struct HomePage {
// 使用@StorageLink建立双向绑定:组件内修改会写回AppStorage,并触发其他绑定组件更新
@StorageLink('globalCounter') counter: number = 0;
// 使用@StorageProp建立单向绑定:组件内修改是局部的,不会影响AppStorage中的源数据
@StorageProp('currentUser') userName: string = '';
build() {
Column({ space: 10 }) {
Text(`全局计数器: ${this.counter}`).fontSize(30)
Button('计数器+1')
.onClick(() => {
this.counter += 1; // 修改会同步到AppStorage,触发其他页面的Text更新
})
Text(`欢迎您, ${this.userName}`).fontSize(20)
Button('模拟登录')
.onClick(() => {
// 通过AppStorage的API更新,同样会触发UI更新
AppStorage.Set<string>('currentUser', '张三');
// 由于userName是@StorageProp单向绑定,此处this.userName不会立即改变,需等下次渲染
})
// 另一个组件也绑定同一个全局状态
CounterDisplayComponent()
}
}
}
// 另一个独立的组件
@Component
struct CounterDisplayComponent {
@StorageLink('globalCounter') displayCounter: number = 0; // 绑定同一个key
build() {
Text(`另一个组件看到的计数: ${this.displayCounter}`).fontSize(18)
.fontColor(Color.Red)
}
}
2. LocalStorage:页面级状态共享LocalStorage是页面级别的状态存储,每个页面实例可以拥有自己的LocalStorage对象。它用于在同一个页面(或同一个UIAbility)内的组件树中共享状态,其生命周期与页面实例绑定,页面销毁则状态释放。
// 通常在页面入口处创建并注入LocalStorage实例
// 假设这是某个页面的ets文件
let pageLocalStorage = new LocalStorage();
// 初始化页面级状态
pageLocalStorage.setOrCreate('formData', { name: '', age: 0 });
pageLocalStorage.setOrCreate('pageTitle', '编辑个人信息');
@Entry(pageLocalStorage) // 将storage实例注入页面根组件
@Component
struct PersonalInfoPage {
// 使用@LocalStorageLink建立双向绑定
@LocalStorageLink('formData') formData: Object = {};
@LocalStorageLink('pageTitle') title: string = '';
build() {
Column() {
Text(this.title).fontSize(24)
// 表单输入组件
TextInput({ placeholder: '请输入姓名' })
.value(this.formData.name)
.onChange((value: string) => {
this.formData = { ...this.formData, name: value }; // 需要整体赋值以触发UI更新
})
// 子组件,接收同一个LocalStorage实例
ChildComponent({ storage: pageLocalStorage })
}
}
}
// 子组件
@Component
struct ChildComponent {
// 通过构造参数接收父组件传递的LocalStorage实例
@Consume storage: LocalStorage;
// 在子组件内部使用装饰器绑定
@LocalStorageLink('formData') linkedFormData: Object = {};
build() {
Column() {
Text(`子组件接收到的姓名: ${this.linkedFormData.name}`)
Button('清空姓名')
.onClick(() => {
// 修改会同步到共享的LocalStorage,从而更新父组件中的显示
this.linkedFormData = { ...this.linkedFormData, name: '' };
})
}
}
}
3. PersistentStorage:持久化状态管理桥PersistentStorage是一个工具类,用于在AppStorage中的特定属性和持久化存储(底层是Preferences)之间建立双向同步链路。它解决了需要将全局状态持久化的常见需求。
import { PersistentStorage, AppStorage } from '@kit.ArkUI';
// 应用启动时(如EntryAbility的onCreate中)进行初始化关联
export function initPersistentState() {
// 将AppStorage中的`userToken`属性持久化到本地,初始值为''
PersistentStorage.PersistProp<string>('userToken', '');
// 可以关联多个属性
PersistentStorage.PersistProp<boolean>('isFirstLaunch', true);
PersistentStorage.PersistProp<string>('appTheme', 'light');
}
// 之后,在应用的任何地方操作AppStorage,变化都会自动保存
function performLogin(token: string) {
// 此设置操作会:1. 更新AppStorage内存中的值;2. 自动将新值写入持久化文件
AppStorage.Set<string>('userToken', token);
}
function logout() {
AppStorage.Set<string>('userToken', ''); // 清空token也会同步持久化
}
// 在UI组件中,可以像使用普通AppStorage属性一样使用它
@Entry
@Component
struct MainPage {
@StorageLink('userToken') token: string = '';
@StorageLink('appTheme') theme: string = 'light';
aboutToAppear() {
// 应用启动后,PersistentStorage会自动将本地保存的值读回AppStorage
// 因此这里可以直接使用可能已存在的持久化值
if (this.token) {
console.info('检测到已保存的登录令牌,自动跳转...');
}
}
build() {
Column() {
Text(`当前主题: ${this.theme}`)
Button('切换主题')
.onClick(() => {
let newTheme = this.theme === 'light' ? 'dark' : 'light';
AppStorage.Set<string>('appTheme', newTheme); // 切换并自动保存偏好
})
}
}
}
三、最佳实践与选型建议
-
清晰的数据分层策略:
- 临时/页面状态:仅在单个页面内使用的交互状态(如输入框内容、弹窗显示状态)应使用
LocalStorage。 - 全局应用状态:需要跨多个页面访问和修改的状态(如用户登录信息、全局主题、购物车)应使用
AppStorage。 - 持久化全局配置:需要持久化且全局访问的轻量级数据(如用户设置、登录令牌)应使用
PersistentStorage+AppStorage组合。 - 复杂业务数据:用户创建的内容、订单、消息等结构化数据,应使用 关系型数据库 进行增删改查。
- 简单配置与标志:是否首次启动、简单的开关设置等,使用 Preferences 更为轻便。
- 非结构化媒体或文件:用户头像、下载的文档、离线缓存等,使用 文件系统API 存储。
- 临时/页面状态:仅在单个页面内使用的交互状态(如输入框内容、弹窗显示状态)应使用
-
性能与内存优化:
- 避免大对象存入响应式存储:不要将大型数组或复杂嵌套对象直接存入
AppStorage或LocalStorage,因为每次修改都会触发深度比较和UI更新,可能导致性能问题。应存储引用或ID,详细数据通过其他方式管理。 - 合理使用装饰器:对于只读的全局状态,使用
@StorageProp替代@StorageLink,避免不必要的双向同步开销。仅在组件确实需要修改并同步回全局状态时才使用@StorageLink。 - 及时清理资源:数据库
ResultSet、文件描述符等使用后必须及时关闭。
- 避免大对象存入响应式存储:不要将大型数组或复杂嵌套对象直接存入
-
数据安全与隐私保护:
- 敏感信息加密:存储密码、身份令牌等敏感信息时,务必利用存储方案提供的加密功能。例如,RDB配置
securityLevel,Preferences数据也会被系统自动加密保护。 - 最小权限原则:文件系统操作应严格限定在应用沙箱内,避免请求不必要的文件访问权限。
- 网络数据安全:与远程服务器同步数据时,必须使用HTTPS等安全协议,并对用户隐私数据实施严格的访问控制和脱敏处理。
- 敏感信息加密:存储密码、身份令牌等敏感信息时,务必利用存储方案提供的加密功能。例如,RDB配置
-
典型场景组合示例(用户系统):
// 1. 持久化令牌 (PersistentStorage + AppStorage) PersistentStorage.PersistProp<string>('authToken', ''); // 2. 用户偏好设置 (Preferences) // 在SettingService中管理 async function saveUserPreference(key: string, value: any) { let pref = await preferences.getPreferences(context, 'user_prefs'); await pref.put(key, value); await pref.flush(); } // 3. 用户个人资料 (关系型数据库) // 在UserRepository中管理 async function saveUserProfile(user: User) { let valueBucket: relationalStore.ValuesBucket = { 'userId': user.id, 'name': user.name, 'avatarPath': user.avatarLocalPath // 头像保存为文件 }; await rdbStore.insert('user_profile', valueBucket); } // 4. 头像文件 (文件系统) async function saveAvatarImage(userId: string, imageData: ArrayBuffer) { let avatarPath = `${context.filesDir}/avatars/${userId}.jpg`; // ... 写入文件操作 } // 5. 在UI组件中组合使用 @Entry @Component struct MyApp { @StorageLink('authToken') token: string; build() { Column() { if (this.token) { // 已登录,显示主界面,主界面从数据库和AppStorage读取数据 MainPage() } else { // 未登录,显示登录页 LoginPage() } } } }
通过深入理解并合理运用ArkTS提供的这套从内存到持久化、从局部到全局的完整数据存储与状态管理方案,开发者可以构建出数据流清晰、响应迅速、体验流畅且稳定可靠的HarmonyOS应用。
参考来源
- ArkTS第四课:网络请求和数据存储
- HarmonyOS应用框架的ARKTS解析
- (二一)ArkTS 数据存储与管理
- 掌握HarmonyOS框架的ArkTs如何管理和共享状态数据
- 深入理解ArkTs中的AppStorage和LocalStorage
- 掌握HarmonyOS框架的ArkTs如何管理和共享状态数据
更多推荐




所有评论(0)