鸿蒙应用如何实现内存级别全局缓存数据?
一、结论:
该问题首先需要搞清楚,全局缓存的数据,是内存级别缓存,即数据只存在App运行期间。还是持久化级别,即数据在App关闭再重新打开,依旧可以获取。
1、首先内存级别:
可以使用(1)AppStorage(2)单例管理(3)LRUCache管理。
2、其次持久化级别:
(1)PersistentStorage(2)Preferences 首选项(3)以及关系型数据库。
二、代码实现和详细解释:
1、首先内存级别:
(1)AppStorage
typescript
// 存储数据
AppStorage.SetOrCreate<string>('userToken', 'abc123');
// 获取数据
let token: string = AppStorage.Get<string>('userToken');
// 组件内使用(自动绑定)
@Entry
@Component
struct UserInfo {
@StorageLink('userToken') token: string = 'test';
build() {
Text(`Token: ${this.token}`)
}
}
(2)单例管理
GlobalCacheMgr 示例:
typescript
export class GlobalCacheMgr {
// 移除多余空格,规范类型声明
private static mGlobalCacheMgr: GlobalCacheMgr;
private cacheMap: Map<string, Object> = new Map();
public static Ins(): GlobalCacheMgr {
if (!GlobalCacheMgr.mGlobalCacheMgr) {
GlobalCacheMgr.mGlobalCacheMgr = new GlobalCacheMgr();
}
return GlobalCacheMgr.mGlobalCacheMgr;
}
// 存储数据
set(key: string, value: Object): void {
this.cacheMap.set(key, value);
}
// 获取数据:泛型推导更安全,避免强制类型断言的风险
get<T>(key: string): T | undefined {
const value = this.cacheMap.get(key);
return value as T | undefined;
}
// 增加缓存清理方法,避免内存泄漏
clear(key?: string): void {
if (key) {
this.cacheMap.delete(key);
} else {
this.cacheMap.clear();
}
}
}
调用示例:
typescript
import { GlobalCacheMgr } from '../mgr/GlobalCacheMgr';
/**
* 全局缓存单例管理类 测试页面
*/
@Entry
@Component
struct GlobalCacheMgrTestPage {
private TAG: string = "ContactPage";
private onClickTest = () => {
let key: string = "userToken";
GlobalCacheMgr.Ins().set(key, "test");
let result: string | undefined = GlobalCacheMgr.Ins().get(key);
console.log(this.TAG, " onClickTest result: " + result);
}
build() {
Row() {
Button('点击测试全局缓存')
.onClick(this.onClickTest)
}
.justifyContent(FlexAlign.Center)
.size({
width: "100%",
height: "100%"
})
}
}
(3)LRUCache管理
LRUCache在图片缓存中使用最多,采用链表的方式,设置最大值,每次填入数据,通过key去判断,如果有,且没有达到最大值,则更新。如果达到最大值,则删除最头部的数据。保证高频数据的缓存,减轻内存的压力。
LRUCacheMgr 管理类示例:
typescript
import { util } from '@kit.ArkTS';
/**
* LRU缓存工具类(单例模式)
* 基于HarmonyOS内置util.LRUCache实现,采用LRU(最近最少使用)淘汰策略
* 当缓存数量达到容量上限时,自动移除最久未使用的缓存项
*/
export class LRUCacheMgr {
// 单例实例(全局唯一)
private static instance: LRUCacheMgr;
// LRU缓存核心实例:键为string类型,值为任意Object类型
private lruCache: util.LRUCache<string, Object>;
// 初始最大缓存容量(测试用,默认5个缓存项)
private MAX_NUM: number = 5;
/**
* 私有构造函数
* 禁止外部通过new关键字实例化,保证单例特性
* 初始化LRUCache并设置初始最大容量
*/
private constructor() {
this.lruCache = new util.LRUCache(this.MAX_NUM);
}
/**
* LRUCacheMgr(懒汉式初始化)
* 首次调用时创建实例,后续调用返回已创建的实例
* @returns LRUCacheMgr 全局唯一的单例对象
*/
public static getInstance(): LRUCacheMgr {
if (!LRUCacheMgr.instance) {
LRUCacheMgr.instance = new LRUCacheMgr();
}
return LRUCacheMgr.instance;
}
/**
* 判断LRU缓存是否为空
* @returns boolean - true:缓存为空;false:缓存非空
*/
public isEmpty(): boolean {
return this.lruCache.isEmpty();
}
/**
* 获取LRU缓存的当前最大容量(可存储的缓存项数量上限)
* @returns number 缓存容量值
*/
public getCapacity(): number {
return this.lruCache.getCapacity();
}
/**
* 重置/更新LRU缓存的最大容量
* @param newCapacity 新的缓存容量(需传入大于0的数字)
*/
public updateCapacity(newCapacity: number) {
this.lruCache.updateCapacity(newCapacity);
}
/**
* 向LRU缓存中添加/更新缓存项
* 若key已存在,会覆盖原有值;若容量已满,会淘汰最久未使用的缓存项
* @param key 缓存键(唯一标识,字符串类型)
* @param value 缓存值(任意Object类型,如字符串、对象、数字等)
*/
public putCache(key: string, value: Object) {
this.lruCache.put(key, value);
}
/**
* 删除指定key对应的缓存项
* 若key不存在,该操作无效果
* @param key 要删除的缓存键
*/
public remove(key: string) {
this.lruCache.remove(key);
}
/**
* 获取指定key对应的缓存值
* @param key 缓存键
* @returns Object | undefined - 存在则返回缓存值,不存在则返回undefined
*/
public getCache(key: string): Object | undefined {
return this.lruCache.get(key);
}
/**
* 判断缓存中是否包含指定key的缓存项
* @param key 缓存键
* @returns boolean - true:存在;false:不存在
*/
public contains(key: string): boolean {
return this.lruCache.contains(key);
}
/**
* 清空所有缓存数据,并将缓存容量重置为64
*/
public clearCache() {
this.lruCache.clear(); // 清空缓存内容
this.lruCache.updateCapacity(64); // 重置缓存容量
}
}
调用示例:
typescript
import { LRUCacheMgr } from '../mgr/LRUCacheMgr';
export class TestInfo{
id: number = 0;
name: string = "";
age: number = 0;
}
/**
* LRU缓存工具类 测试页面
*/
@Entry
@Component
struct LRUCacheMgrTestPage {
// 日志TAG,便于定位日志来源
private TAG: string = "LRUCacheMgrTestPage";
/**
* 点击按钮触发LRU缓存全流程测试
* 依次测试:初始化、添加缓存、获取缓存、判断存在性、更新容量、删除缓存、清空缓存等
*/
private onClickTestLRU = () => {
// 1. 获取LRU缓存单例实例
const lruCache = LRUCacheMgr.Ins();
console.log(this.TAG, "===== 开始测试LRU缓存 =====");
// 2. 初始状态检查
const isEmptyInit = lruCache.isEmpty();
const initCapacity = lruCache.getCapacity();
console.log(this.TAG, `初始状态 - 缓存是否为空:${isEmptyInit},初始容量:${initCapacity}`);
// 3. 添加不同类型的缓存项
const tokenKey = "userToken";
const userInfoKey = "userInfo";
const maxCountKey = "maxCount";
lruCache.putCache(tokenKey, "hmos_987654321");
let testInfo: TestInfo = { id: 1001, name: "鸿蒙开发者", age: 25 };
lruCache.putCache(userInfoKey, testInfo);
lruCache.putCache(maxCountKey, 200);
console.log(this.TAG, "添加缓存项 - userToken/userInfo/maxCount");
// 4. 获取缓存项并打印
const token = lruCache.getCache(tokenKey);
const userInfo = lruCache.getCache(userInfoKey);
const maxCount = lruCache.getCache(maxCountKey);
console.log(this.TAG, `获取缓存 - ${tokenKey}: ${token}`);
console.log(this.TAG, `获取缓存 - ${userInfoKey}: ${JSON.stringify(userInfo)}`);
console.log(this.TAG, `获取缓存 - ${maxCountKey}: ${maxCount}`);
// 5. 判断缓存项是否存在
const hasUserInfo = lruCache.contains(userInfoKey);
const hasInvalidKey = lruCache.contains("invalidKey");
console.log(this.TAG, `判断存在性 - ${userInfoKey}存在:${hasUserInfo},invalidKey存在:${hasInvalidKey}`);
// 6. 更新缓存容量
const newCapacity = 10;
lruCache.updateCapacity(newCapacity);
const updatedCapacity = lruCache.getCapacity();
console.log(this.TAG, `更新容量 - 原容量${initCapacity} → 新容量${updatedCapacity}`);
// 7. 删除指定缓存项
lruCache.remove(maxCountKey);
const deletedMaxCount = lruCache.getCache(maxCountKey);
console.log(this.TAG, `删除缓存 - ${maxCountKey}:${deletedMaxCount === undefined ? "删除成功" : "删除失败"}`);
// 8. 清空缓存并重置容量
lruCache.clearCache();
const isEmptyAfterClear = lruCache.isEmpty();
const finalCapacity = lruCache.getCapacity();
console.log(this.TAG, `清空缓存 - 缓存是否为空:${isEmptyAfterClear},重置后容量:${finalCapacity}`);
console.log(this.TAG, "===== LRU缓存测试结束 =====");
}
build() {
Row() {
Button('点击测试LRU缓存')
.fontSize(16)
.padding({ left: 20, right: 20, top: 10, bottom: 10 })
.onClick(this.onClickTestLRU)
}
.justifyContent(FlexAlign.Center)
.size({
width: "100%",
height: "100%"
})
}
}
资料引用:
AppStorage 应用级变量的内存管理: https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-state-management#appstorage
LRUCache 应用内存占用优化: https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-memory-optimization#section1518265464211
Preferences 用户首选项: