一、前言
做完网络请求,几乎所有开发者下一步都会接触本地数据持久化。
网络拿下来的数据不能每次打开APP都重新请求,用户Token、个人设置、主题模式、本地缓存、离线数据,全部需要本地保存。
很多新手分不清鸿蒙三种存储:
什么时候用 Preferences?
什么时候用 关系型数据库?
什么时候用 沙箱文件存储?
而且大量踩坑:重启APP数据丢失、对象存不进去、存储权限报错、明文保存敏感数据、缓存不会清理。
今天我把鸿蒙三大持久化方案一次性讲透,给你商用级封装工具类,复制直接放进项目,适配API10+,全部原生API、无第三方依赖。
二、三种存储方案适用场景
存储方式 | 特点 | 适用场景 |
|---|
Preferences | 键值对、轻量、读写快 | Token、配置、主题、简单标记 |
关系型数据库(RDB) | 结构化、增删改查、条件查询 | 大量列表、聊天记录、本地表单 |
文件沙箱存储 | 任意文件、二进制、超大容量 | 图片缓存、下载文件、日志文件 |
三、最终实现效果
本篇一次性封装三套工具类:
全部商用级别、异常捕获、防止闪退、适配鸿蒙规范。
四、核心实现思路
Preferences:单例初始化、JSON序列化、敏感数据Base64编码、强制flush落地;
RDB数据库:封装打开关闭、通用增删改查、SQL语句简化、事务保证数据一致性;
文件存储:获取沙箱路径、读写权限隔离、文件覆盖、删除、缓存清理;
五、完整可运行源码
5.1 轻量键值存储工具(Preferences)StorageUtil.ets
import { preferences } from '@kit.ArkData';import { common } from '@kit.BasicServicesKit';let dataPreferences: preferences.Preferences | null = null;export class StorageUtil { // 初始化 static async initStorage() { if (dataPreferences) return; const context = getContext() as common.UIAbilityContext; dataPreferences = await preferences.getPreferences(context, "app_config"); } // 保存数据 static async setData(key: string, value: any) { await this.initStorage(); try { let saveValue = value; // 对象序列化 if (typeof value === 'object') { saveValue = JSON.stringify(value); } // 敏感数据简易编码 if (key.includes('token') || key.includes('pwd')) { saveValue = btoa(saveValue); } await dataPreferences!.putSync(key, saveValue); await dataPreferences!.flush(); } catch (e) { console.error("存储失败:", e); } } // 读取数据 static async getData<T>(key: string, defaultValue: T): Promise<T> { await this.initStorage(); try { let value = await dataPreferences!.get(key, defaultValue); // 解密 if (key.includes('token') || key.includes('pwd')) { value = atob(value as string); } // JSON解析 if (typeof value === 'string' && (value.startsWith('{') || value.startsWith('['))) { return JSON.parse(value) as T; } return value as T; } catch (e) { return defaultValue; } } // 删除单条 static async deleteKey(key: string) { await this.initStorage(); await dataPreferences!.delete(key); await dataPreferences!.flush(); } // 清空全部 static async clearAll() { await this.initStorage(); await dataPreferences!.clear(); await dataPreferences!.flush(); }}
5.2 关系型数据库工具 RdbUtil.ets
import { relationalStore } from '@kit.ArkData';import { common } from '@kit.BasicServicesKit';const STORE_NAME = "local_database.db";const STORE_VERSION = 1;let rdbStore: relationalStore.RdbStore | null = null;// 用户表结构const CREATE_TABLE_SQL = `CREATE TABLE IF NOT EXISTS user( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, age INTEGER)`;export class RdbUtil { // 初始化数据库 static async initRdb() { if (rdbStore) return; const context = getContext() as common.UIAbilityContext; const config: relationalStore.StoreConfig = { name: STORE_NAME, securityLevel: relationalStore.SecurityLevel.S1 }; rdbStore = await relationalStore.getRdbStore(context, config); // 创建表 await rdbStore.executeSql(CREATE_TABLE_SQL); } // 新增数据 static async insertUser(user: {username:string,age:number}) { await this.initRdb(); const valueBucket: relationalStore.ValuesBucket = { "username": user.username, "age": user.age }; return await rdbStore!.insert("user", valueBucket); } // 查询全部 static async queryAllUser() { await this.initRdb(); const resultSet = await rdbStore!.query("user", ["id","username","age"]); let list: any[] = []; while (resultSet.goToNextRow()) { list.push({ id: resultSet.getLong(0), username: resultSet.getString(1), age: resultSet.getLong(2) }); } resultSet.close(); return list; } // 删除 static async deleteUser(id: number) { await this.initRdb(); const predicates = new relationalStore.RdbPredicates("user"); predicates.equalTo("id", id); return await rdbStore!.delete(predicates); } // 更新 static async updateUser(id:number,newName:string) { await this.initRdb(); const bucket: relationalStore.ValuesBucket = {"username":newName}; const predicates = new relationalStore.RdbPredicates("user"); predicates.equalTo("id",id); return await rdbStore!.update(bucket,predicates); }}
5.3 沙箱文件工具 FileUtil.ets
import { fileIo, fs } from '@kit.CoreFileKit';import { common } from '@kit.BasicServicesKit';export class FileUtil { // 获取沙箱根目录 staticgetSandboxPath(): string { const context = getContext() as common.UIAbilityContext; return context.filesDir; } // 写入文本文件 staticasyncwriteFile(fileName: string, content: string) { const path = this.getSandboxPath() + "/" + fileName; const file = await fileIo.open(path, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY); await fileIo.write(file.fd, content); await fileIo.close(file.fd); } // 读取文本文件 staticasyncreadFile(fileName: string): Promise<string> { const path = this.getSandboxPath() + "/" + fileName; const file = await fileIo.open(path, fileIo.OpenMode.READ_ONLY); const res = await fileIo.readText(file.fd); await fileIo.close(file.fd); return res; } // 删除文件 staticasyncdeleteFile(fileName:string){ const path = this.getSandboxPath() + "/" + fileName; await fs.unlink(path); }}
5.4 页面演示 Index.ets
import { StorageUtil } from './utils/StorageUtil';import { RdbUtil } from './utils/RdbUtil';import { FileUtil } from './utils/FileUtil';import { promptAction } from '@kit.ArkUI';@Entry@Componentstruct Index { @State tips: string = "暂无数据"; build() { Column({ space: 12 }) { Text("Day09 三大数据持久化演示") .fontSize(22) .fontWeight(FontWeight.Bold) .margin({ top: 30 }) Text(this.tips) .fontSize(15) .padding(15) .width("90%") .backgroundColor("#fff") .borderRadius(10) Button("1.保存Token(加密存储)") .width("85%") .onClick(async () => { await StorageUtil.setData("token","ABC123456789TOKEN"); this.tips = "Token保存成功,已加密"; }) Button("2.读取Token") .width("85%") .backgroundColor("#10B981") .onClick(async () => { const token = await StorageUtil.getData("token","无数据"); this.tips = "读取Token:" + token; }) Button("3.数据库新增用户") .width("85%") .backgroundColor("#2563EB") .onClick(async () => { await RdbUtil.insertUser({username:"鸿蒙开发者",age:18}); this.tips = "数据库插入成功"; }) Button("4.文件写入日志") .width("85%") .backgroundColor("#F59E0B") .onClick(async () => { await FileUtil.writeFile("log.txt","我是本地日志内容"); this.tips = "文件写入成功"; }) } .width('100%') .height('100%') .padding(15) .backgroundColor("#f7f8fa") }}
六、API详解
6.1 Preferences
轻量键值数据库,适合少量配置数据,读写速度极快。必须执行flush()才会永久落地,否则杀进程数据丢失。
6.2 RDB关系型数据库
鸿蒙内置SQLite,支持标准SQL,适合大量结构化数据,支持事务、条件查询、分页。
6.3 FileIO文件操作
沙箱私有目录,APP卸载自动清除,无需手动申请权限,适合存放图片、日志、二进制文件。
七、避坑指南
Preferences不能直接存对象:必须手动JSON序列化;
不flush等于没保存:新手数据丢失90%都是没写flush;
数据库不要频繁打开关闭:全局单例保持唯一实例;
沙箱路径不要写死:必须使用代码动态获取;
敏感数据不要明文:Token、密码至少Base64编码,商用建议AES加密。
八、总结
本篇一次性讲透鸿蒙Preferences、RDB数据库、文件存储三大持久化方案。
记住最简单选型口诀:配置用Preferences、列表用RDB、文件用FileIO。
三套工具类全部商用封装,异常捕获、加密处理、内存优化,直接复制进项目即可投产。