当前位置:首页>鸿蒙APP>鸿蒙本地缓存持久化全解(Preferences、ArkDB、图片缓存、过期清理策略)

鸿蒙本地缓存持久化全解(Preferences、ArkDB、图片缓存、过期清理策略)

  • 2026-08-16 18:18:12
鸿蒙本地缓存持久化全解(Preferences、ArkDB、图片缓存、过期清理策略)

一、前言

本地持久化缓存是App开发必备能力,用于存储用户登录信息、配置参数、列表离线数据、图片资源等。很多项目缓存方案混乱,存在大量线上问题:

  • 简单数据乱用数据库,重写大量冗余代码;

  • 大列表数据存在Preferences,读写卡顿、丢失数据;

  • 图片无本地缓存,每次打开页面重复下载,消耗流量;

  • 缓存无过期淘汰策略,长期使用占用大量存储空间;

  • 页面退出不清理临时缓存,造成数据错乱、隐私泄露;

  • 多页面并发读写缓存,出现脏数据、读取失败。

本文完整拆解鸿蒙两套官方持久化方案 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 使用禁忌

  • 禁止存储长数组、大列表数据,文件过大读取阻塞UI;

  • 频繁循环读写会造成性能卡顿,建议批量写入;

  • 卸载应用自动清空,无法跨安装持久保存;

  • 不适合存储图片、二进制文件。

四、海量结构化数据 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}`);    }  }}

六、统一缓存过期清理全局策略

三种缓存统一规范过期逻辑,避免存储无限膨胀:

  1. Preferences:仅存储短期配置,版本更新一键清空;

  2. ArkDB:查询数据时自动过滤过期记录,定期后台清理;

  3. 图片文件:读取时校验修改时间,过期自动删除;

  4. 页面onDestroy、应用退出时执行一次全局缓存清理。

七、缓存开发高频踩坑点

  • 并发读写Preferences未加异步锁,数据覆盖丢失;

  • ArkDB使用后不关闭rdbStore,数据库连接泄漏;

  • 图片缓存不做过期淘汰,长期使用占用数十MB存储空间;

  • 登录退出未清空用户相关缓存,造成隐私信息残留;

  • 沙盒路径硬编码,不同设备缓存目录读取失败;

  • 大对象直接存入Preferences,页面切换卡顿、ANR。

八、全文总结

鸿蒙本地持久化三套方案分工明确:轻量配置用Preferences、海量列表数据用ArkDB、图片资源使用文件缓存。

商用项目必须统一封装缓存工具类,强制增加过期淘汰机制,避免存储空间无限增长、数据错乱、流量浪费等问题。合理分层缓存,既能提升页面加载速度、减少网络请求,又能保障App长期运行性能与用户存储体验。

最新文章

随机文章