一、前言
本地持久化缓存是App开发必备能力,用于存储用户登录信息、配置参数、列表离线数据、图片资源等。很多项目缓存方案混乱,存在大量线上问题:
本文完整拆解鸿蒙两套官方持久化方案 Preferences 轻量存储、ArkDB关系型数据库,封装图片缓存工具、统一过期清理机制,根据数据规模给出选型标准,全部代码可直接落地商用项目。
二、鸿蒙持久化方案选型对比
存储方案 | 底层能力 | 存储数据规模 | 适合场景 | 优缺点 |
|---|
Preferences | 键值对XML持久化 | 小数据(KB级) | 登录状态、主题、开关、简单配置 | 上手简单;不适合大量数组、对象,大数据读写卡顿 |
ArkDB | SQLite数据库封装 | 海量数据(MB级) | 离线列表、历史记录、复杂结构化数据 | 支持增删改查分页、条件筛选;初始化略繁琐 |
文件缓存 | 沙盒文件读写 | 图片、二进制资源 | 网络图片、离线文件、大资源缓存 | 读写性能高,需手动实现过期清理 |
选型口诀:小配置用Preferences,大量列表用ArkDB,图片资源用文件缓存。
三、轻量键值存储 Preferences 封装实战
3.1 工具类封装(统一存取、自动序列化对象)
// common/PreferencesUtil.etsimport preferences from '@ohos.data.preferences';import common from '@ohos.app.ability.common';const STORE_NAME = "app_global_store";export class PreferencesUtil { private static ins: PreferencesUtil; private dataPreferences: preferences.Preferences | null = null; private context: common.UIAbilityContext; static getInstance(ctx: common.UIAbilityContext): PreferencesUtil { if (!this.ins) { this.ins = new PreferencesUtil(); this.ins.context = ctx; } return this.ins; } // 初始化存储实例 async init() { this.dataPreferences = await preferences.getPreferences(this.context, STORE_NAME); } // 存储字符串、数字、布尔、对象 async set(key: string, value: any) { if (!this.dataPreferences) await this.init(); let saveVal: string; if (typeof value === "object") { saveVal = JSON.stringify(value); } else { saveVal = String(value); } await this.dataPreferences!.putSync(key, saveVal); await this.dataPreferences!.flush(); } // 读取数据,支持对象自动解析 async get<T>(key: string, defVal: T): Promise<T> { if (!this.dataPreferences) await this.init(); const val = this.dataPreferences!.getSync(key, String(defVal)); try { return JSON.parse(val) as T; } catch (e) { return val as unknown as T; } } // 删除单条缓存 async remove(key: string) { if (!this.dataPreferences) await this.init(); await this.dataPreferences!.deleteSync(key); await this.dataPreferences!.flush(); } // 清空全部缓存 async clearAll() { if (!this.dataPreferences) await this.init(); await this.dataPreferences!.clearSync(); await this.dataPreferences!.flush(); }}
3.2 页面调用示例
import { PreferencesUtil } from '../common/PreferencesUtil';import common from '@ohos.app.ability.common';@Entry@Componentstruct PreDemo { ctx = getContext(this) as common.UIAbilityContext store = PreferencesUtil.getInstance(this.ctx) @State userInfo: any = null aboutToAppear() { this.loadUserCache() } // 写入用户信息缓存 async saveUser() { const user = { id: 1001, name: "鸿蒙开发者", isLogin: true } await this.store.set("user_info", user) } // 读取缓存用户 async loadUserCache() { const info = await this.store.get("user_info", null) this.userInfo = info } build() { Column({ space: 15 }) { Text(`缓存用户:${JSON.stringify(this.userInfo)}`) Button("保存登录信息").onClick(() => this.saveUser()) Button("清除登录缓存").onClick(() => this.store.remove("user_info")) } .width('100%') .height('100%') .justifyContent(FlexAlign.Center) }}
3.3 Preferences 使用禁忌
四、海量结构化数据 ArkDB 数据库实战
ArkDB基于SQLite封装,支持建表、分页查询、条件筛选,适合历史记录、离线商品列表等大量结构化数据。
4.1 数据库基础工具封装
// common/ArkDBUtil.etsimport relationalStore from '@ohos.data.relationalStore';import common from '@ohos.app.ability.common';const DB_NAME = "app_cache_db";const DB_VERSION = 1;// 建表语句:本地历史记录表const CREATE_TABLE_SQL = `CREATE TABLE IF NOT EXISTS search_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, keyword TEXT, create_time INTEGER, expire_time INTEGER)`;export class ArkDBUtil { private static ins: ArkDBUtil; private rdbStore: relationalStore.RdbStore | null = null; private ctx: common.UIAbilityContext; static getInstance(ctx: common.UIAbilityContext): ArkDBUtil { if (!this.ins) { this.ins = new ArkDBUtil(); this.ins.ctx = ctx; } return this.ins; } // 初始化数据库并创建表 async initDB() { const config: relationalStore.StoreConfig = { name: DB_NAME, securityLevel: relationalStore.SecurityLevel.S1 } this.rdbStore = await relationalStore.getRdbStore(this.ctx, config); await this.rdbStore.executeSql(CREATE_TABLE_SQL); } // 插入历史记录 async addHistory(keyword: string, expire: number) { if (!this.rdbStore) await this.initDB(); const values: relationalStore.ValuesBucket = { keyword: keyword, create_time: Date.now(), expire_time: Date.now() + expire } await this.rdbStore!.insert("search_history", values); } // 查询未过期数据,分页 async getValidHistory(page: number, size: number): Promise<any[]> { if (!this.rdbStore) await this.initDB(); const predicates = new relationalStore.RdbPredicates("search_history") predicates.where("expire_time > ?").bindAsLong(Date.now()) predicates.orderByDesc("create_time") predicates.limit(size, (page - 1) * size) const res = await this.rdbStore!.query(predicates) let list = [] while (res.goToNextRow()) { list.push({ id: res.getLong(0), keyword: res.getString(1), create_time: res.getLong(2), expire_time: res.getLong(3) }) } res.close() return list } // 自动清理过期缓存 async clearExpireData() { if (!this.rdbStore) await this.initDB(); const predicates = new relationalStore.RdbPredicates("search_history") predicates.where("expire_time < ?").bindAsLong(Date.now()) await this.rdbStore!.delete(predicates) } // 关闭数据库释放资源 close() { if (this.rdbStore) { this.rdbStore.close() this.rdbStore = null } }}
4.2 页面使用历史缓存库
import { ArkDBUtil } from '../common/ArkDBUtil';import common from '@ohos.app.ability.common';@Entry@Componentstruct DbCachePage { ctx = getContext(this) as common.UIAbilityContext db = ArkDBUtil.getInstance(this.ctx) @State historyList: any[] = [] aboutToAppear() { this.loadHistory() } // 添加搜索记录,缓存有效期7天 async addSearch(key: string) { // 7天过期毫秒 const sevenDay = 7 * 24 * 60 * 60 * 1000 await this.db.addHistory(key, sevenDay) await this.loadHistory() } // 加载有效历史 async loadHistory() { // 先清理过期数据 await this.db.clearExpireData() const list = await this.db.getValidHistory(1, 20) this.historyList = list } build() { Column({ space: 12 }) { Button("添加搜索记录").onClick(() => this.addSearch("鸿蒙ArkTS开发")) ForEach(this.historyList, item => { Text(item.keyword).fontSize(16) }) } .width('100%') .padding(20) }}
五、图片本地文件缓存(流量优化核心)
网络图片每次加载重复请求,消耗流量、加载缓慢;通过沙盒文件持久化图片,设置过期时间自动清理。
5.1 文件缓存工具核心能力
根据图片URL生成唯一MD5文件名;
图片下载保存至应用缓存目录;
读取本地缓存,不存在再发起网络请求;
定时清理超过有效期的图片文件。
// common/ImageCacheUtil.etsimport fs from '@ohos.file.fs';import common from '@ohos.app.ability.common';import http from '@ohos.net.http';import crypto from '@ohos.crypto';export class ImageCacheUtil { private static ins: ImageCacheUtil; private cacheDir: string = ""; private ctx: common.UIAbilityContext; // 默认缓存有效期3天 private static EXPIRE_MS = 3 * 24 * 60 * 60 * 1000; static getInstance(ctx: common.UIAbilityContext): ImageCacheUtil { if (!this.ins) { this.ins = new ImageCacheUtil(); this.ins.ctx = ctx; } return this.ins; } // 初始化缓存目录 async initDir() { const cachePath = this.ctx.cacheDir; this.cacheDir = `${cachePath}/img_cache`; try { await fs.access(this.cacheDir); } catch (e) { await fs.mkdir(this.cacheDir); } } // url转md5文件名 private getMd5Name(url: string): string { const md5 = crypto.createHash("md5"); md5.update(url); return md5.digest("hex"); } // 获取本地缓存图片路径,无缓存则下载 async getImagePath(url: string): Promise<string> { await this.initDir(); const fileName = this.getMd5Name(url); const filePath = `${this.cacheDir}/${fileName}`; try { const stat = await fs.stat(filePath); const modifyTime = stat.mtime.getTime(); // 判断是否过期 if (Date.now() - modifyTime < ImageCacheUtil.EXPIRE_MS) { return filePath; } else { // 过期删除旧文件 await fs.unlink(filePath); } } catch (e) { // 文件不存在,走下载逻辑 } // 下载图片保存本地 await this.downloadImg(url, filePath); return filePath; } // 下载图片写入本地文件 private async downloadImg(url: string, savePath: string) { return new Promise<void>((resolve, reject) => { const httpReq = http.createHttp(); httpReq.request(url, { method: http.RequestMethod.GET, readTimeout: 10000 }, async (err, res) => { if (err) { httpReq.destroy(); reject(err); return; } const file = await fs.open(savePath, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY); await fs.write(file.fd, res.result as ArrayBuffer); await fs.close(file); httpReq.destroy(); resolve(); }) }) } // 清空全部图片缓存 async clearAllCache() { await this.initDir(); const files = await fs.listFile(this.cacheDir); for (const file of files) { await fs.unlink(`${this.cacheDir}/${file}`); } }}
六、统一缓存过期清理全局策略
三种缓存统一规范过期逻辑,避免存储无限膨胀:
Preferences:仅存储短期配置,版本更新一键清空;
ArkDB:查询数据时自动过滤过期记录,定期后台清理;
图片文件:读取时校验修改时间,过期自动删除;
页面onDestroy、应用退出时执行一次全局缓存清理。
七、缓存开发高频踩坑点
并发读写Preferences未加异步锁,数据覆盖丢失;
ArkDB使用后不关闭rdbStore,数据库连接泄漏;
图片缓存不做过期淘汰,长期使用占用数十MB存储空间;
登录退出未清空用户相关缓存,造成隐私信息残留;
沙盒路径硬编码,不同设备缓存目录读取失败;
大对象直接存入Preferences,页面切换卡顿、ANR。
八、全文总结
鸿蒙本地持久化三套方案分工明确:轻量配置用Preferences、海量列表数据用ArkDB、图片资源使用文件缓存。
商用项目必须统一封装缓存工具类,强制增加过期淘汰机制,避免存储空间无限增长、数据错乱、流量浪费等问题。合理分层缓存,既能提升页面加载速度、减少网络请求,又能保障App长期运行性能与用户存储体验。