HarmonyOS开发中的图像缓存:LRU缓存策略、磁盘缓存、内存缓存、缓存淘汰、缓存预热
HarmonyOS开发中的图像缓存:LRU缓存策略、磁盘缓存、内存缓存、缓存淘汰、缓存预热
核心要点:图像缓存是移动端性能优化的「命门」。本文从LRU淘汰策略出发,深入讲解HarmonyOS中内存缓存与磁盘缓存的双层架构设计,剖析缓存淘汰机制与预热策略,帮你彻底告别「图片加载慢、内存爆、流量贵」三大顽疾。
| 项目 | 说明 |
|---|---|
| 核心API | @ohos.file.cacheManager、@ohos.app.ability.Context、LRUCache |
一、背景与动机
你有没有这样的体验——打开一个图片密集型的App,首页瀑布流刷刷刷地加载,每张图都从网络拉取,流量哗哗地跑,加载速度还慢得让人抓狂。更离谱的是,你往回滑动的时候,刚才看过的图片又得重新下载一遍。
这不是个别现象,而是很多开发者的通病:只管加载,不管缓存。
图像缓存的本质就是「用空间换时间」。把已经加载过的图片存起来,下次用的时候直接从本地读取,省流量、省时间、省电。但缓存又不是无脑存——手机存储有限、内存更有限,你得有一套科学的策略来决定「存什么、存多久、什么时候清」。
这就是今天要聊的核心:LRU缓存策略 + 双层缓存架构 + 缓存淘汰 + 缓存预热,一套组合拳打下来,你的图片加载体验能提升好几个档次。
二、核心原理
2.1 双层缓存架构
图像缓存通常采用「内存缓存 + 磁盘缓存」的双层架构。内存缓存快但容量小,磁盘缓存慢但容量大。两者配合,形成「先查内存、再查磁盘、最后网络」的三级查找链路。
flowchart TD
classDef primary fill:#4FC3F7,stroke:#0288D1,color:#000
classDef warning fill:#FFB74D,stroke:#F57C00,color:#000
classDef error fill:#EF5350,stroke:#C62828,color:#fff
classDef info fill:#81C784,stroke:#388E3C,color:#000
classDef purple fill:#CE93D8,stroke:#7B1FA2,color:#000
A[请求图片] --> B{内存缓存命中?}
B -- 命中 --> C[直接返回PixelMap]:::primary
B -- 未命中 --> D{磁盘缓存命中?}
D -- 命中 --> E[读取文件解码为PixelMap]:::info
E --> F[写入内存缓存]:::primary
F --> C
D -- 未命中 --> G[网络下载图片]:::warning
G --> H[写入磁盘缓存]:::info
H --> I[解码为PixelMap]:::primary
I --> J[写入内存缓存]:::primary
J --> C
2.2 LRU缓存淘汰策略
LRU(Least Recently Used,最近最少使用)是缓存淘汰的经典算法。核心思想很简单:当缓存满了,把最久没被访问的数据踢出去。
HarmonyOS的LRUCache就是基于这个原理实现的。它内部维护了一个双向链表 + 哈希表的结构:
- 哈希表:O(1)时间查找key是否存在
- 双向链表:O(1)时间完成节点的移动和删除
每次访问一个key,就把对应节点移到链表头部;插入新数据也放在头部;缓存满了就从链表尾部删除。
flowchart LR
classDef primary fill:#4FC3F7,stroke:#0288D1,color:#000
classDef warning fill:#FFB74D,stroke:#F57C00,color:#000
classDef error fill:#EF5350,stroke:#C62828,color:#fff
classDef info fill:#81C784,stroke:#388E3C,color:#000
classDef purple fill:#CE93D8,stroke:#7B1FA2,color:#000
subgraph LRUCache工作流程
A[访问Key] --> B{Key存在?}
B -- 是 --> C[将节点移至链表头部]:::primary
B -- 否 --> D{缓存已满?}
D -- 是 --> E[删除链表尾部节点]:::error
D -- 否 --> F[在链表头部插入新节点]:::info
E --> F
end
2.3 缓存预热
缓存预热是指在用户真正需要数据之前,提前把数据加载到缓存中。比如:
- App启动时预加载首页图片
- 列表滑动时预加载即将出现的图片
- 根据用户行为预测可能访问的图片
这就像饭店在饭点之前就把热门菜备好,客人一来直接上菜,不用等。
三、代码实战
3.1 基于LRUCache的内存缓存管理器
这是整个缓存体系的核心——一个完整的内存缓存管理器,支持LRU淘汰、容量控制、命中率统计。
import { LRUCache } from '@kit.ArkTS';
/**
* 图像内存缓存管理器
* 基于LRU策略管理PixelMap缓存,控制内存占用
*/
export class ImageMemoryCache {
// LRU缓存实例,key为图片URL的MD5摘要
private lruCache: LRUCache<string, image.PixelMap>;
// 缓存命中统计
private hitCount: number = 0;
private missCount: number = 0;
// 最大缓存容量(条目数)
private readonly MAX_CAPACITY: number = 100;
constructor(capacity: number = 100) {
this.MAX_CAPACITY = capacity;
this.lruCache = new LRUCache<string, image.PixelMap>(capacity);
}
/**
* 从内存缓存获取图片
* @param key 图片唯一标识(通常为URL的哈希值)
* @returns PixelMap或undefined
*/
get(key: string): image.PixelMap | undefined {
const pixelMap = this.lruCache.get(key);
if (pixelMap !== undefined) {
this.hitCount++;
console.info(`[ImageCache] 内存缓存命中: ${key}`);
} else {
this.missCount++;
console.info(`[ImageCache] 内存缓存未命中: ${key}`);
}
return pixelMap;
}
/**
* 将图片写入内存缓存
* @param key 图片唯一标识
* @param pixelMap 图片像素数据
*/
put(key: string, pixelMap: image.PixelMap): void {
if (key === '' || pixelMap === undefined) {
console.warn('[ImageCache] 无效的缓存写入参数');
return;
}
this.lruCache.put(key, pixelMap);
console.info(`[ImageCache] 写入内存缓存: ${key}, 当前容量: ${this.lruCache.length}/${this.MAX_CAPACITY}`);
}
/**
* 移除指定缓存
*/
remove(key: string): void {
this.lruCache.remove(key);
}
/**
* 清空所有缓存
*/
clear(): void {
this.lruCache.clear();
this.hitCount = 0;
this.missCount = 0;
console.info('[ImageCache] 内存缓存已清空');
}
/**
* 获取缓存命中率
*/
getHitRate(): string {
const total = this.hitCount + this.missCount;
if (total === 0) return '0.00%';
return ((this.hitCount / total) * 100).toFixed(2) + '%';
}
/**
* 获取缓存统计信息
*/
getStats(): object {
return {
capacity: this.MAX_CAPACITY,
currentSize: this.lruCache.length,
hitCount: this.hitCount,
missCount: this.missCount,
hitRate: this.getHitRate()
};
}
}
3.2 磁盘缓存管理器
磁盘缓存负责将图片文件持久化存储到应用沙箱目录,支持文件读写、容量限制和自动清理。
import { fileIo as fs } from '@kit.CoreFileKit';
import { hash } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
/**
* 图像磁盘缓存管理器
* 将图片文件存储到应用沙箱目录,支持容量限制和LRU清理
*/
export class ImageDiskCache {
private context: common.Context;
// 磁盘缓存根目录
private cacheDir: string = '';
// 索引文件路径(记录访问时间,用于LRU淘汰)
private indexFile: string = '';
// 最大磁盘缓存大小(默认50MB)
private readonly MAX_DISK_SIZE: number = 50 * 1024 * 1024;
constructor(context: common.Context, maxDiskSize?: number) {
this.context = context;
if (maxDiskSize !== undefined) {
this.MAX_DISK_SIZE = maxDiskSize;
}
this.initCacheDir();
}
/**
* 初始化缓存目录
*/
private initCacheDir(): void {
// 使用应用的缓存目录
this.cacheDir = this.context.cacheDir + '/image_cache';
this.indexFile = this.cacheDir + '/cache_index.json';
// 创建缓存目录
if (!fs.accessSync(this.cacheDir)) {
fs.mkdirSync(this.cacheDir, true);
console.info('[DiskCache] 创建缓存目录: ' + this.cacheDir);
}
// 创建索引文件
if (!fs.accessSync(this.indexFile)) {
fs.writeTextSync(this.indexFile, JSON.stringify({}));
console.info('[DiskCache] 创建索引文件');
}
}
/**
* 将URL转换为安全的文件名
* 使用哈希算法避免特殊字符问题
*/
private urlToFileName(url: string): string {
// 简单哈希:将URL转为十六进制摘要
let hash = 0;
for (let i = 0; i < url.length; i++) {
const char = url.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // 转为32位整数
}
return 'img_' + Math.abs(hash).toString(16) + '.cache';
}
/**
* 读取索引文件
*/
private readIndex(): Record<string, number> {
try {
const content = fs.readTextSync(this.indexFile);
return JSON.parse(content) as Record<string, number>;
} catch (e) {
return {};
}
}
/**
* 写入索引文件
*/
private writeIndex(index: Record<string, number>): void {
fs.writeTextSync(this.indexFile, JSON.stringify(index));
}
/**
* 从磁盘缓存获取图片数据
* @param url 图片URL
* @returns ArrayBuffer或null
*/
get(url: string): ArrayBuffer | null {
const fileName = this.urlToFileName(url);
const filePath = this.cacheDir + '/' + fileName;
if (!fs.accessSync(filePath)) {
console.info('[DiskCache] 磁盘缓存未命中: ' + url);
return null;
}
try {
// 读取文件内容
const file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
const stat = fs.statSync(filePath);
const buffer = new ArrayBuffer(stat.size);
fs.readSync(file.fd, buffer);
fs.closeSync(file);
// 更新访问时间(LRU)
const index = this.readIndex();
index[fileName] = Date.now();
this.writeIndex(index);
console.info('[DiskCache] 磁盘缓存命中: ' + url);
return buffer;
} catch (e) {
console.error('[DiskCache] 读取缓存文件失败: ' + e);
return null;
}
}
/**
* 将图片数据写入磁盘缓存
* @param url 图片URL
* @param data 图片二进制数据
*/
put(url: string, data: ArrayBuffer): void {
const fileName = this.urlToFileName(url);
const filePath = this.cacheDir + '/' + fileName;
try {
// 写入文件
const file = fs.openSync(filePath, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY);
fs.writeSync(file.fd, data);
fs.closeSync(file);
// 更新索引
const index = this.readIndex();
index[fileName] = Date.now();
this.writeIndex(index);
console.info('[DiskCache] 写入磁盘缓存: ' + url);
// 检查磁盘容量,超限则清理
this.evictIfNeeded();
} catch (e) {
console.error('[DiskCache] 写入缓存文件失败: ' + e);
}
}
/**
* 检查并清理磁盘缓存
* 当总缓存大小超过阈值时,按LRU策略淘汰最久未访问的文件
*/
private evictIfNeeded(): void {
const index = this.readIndex();
let totalSize = 0;
// 计算当前缓存总大小
const fileNames = Object.keys(index);
for (const name of fileNames) {
const filePath = this.cacheDir + '/' + name;
try {
if (fs.accessSync(filePath)) {
const stat = fs.statSync(filePath);
totalSize += stat.size;
}
} catch (e) {
// 文件可能已被删除,从索引中移除
delete index[name];
}
}
// 未超限,无需清理
if (totalSize <= this.MAX_DISK_SIZE) {
return;
}
console.info(`[DiskCache] 缓存超限(${(totalSize / 1024 / 1024).toFixed(2)}MB),开始LRU淘汰`);
// 按访问时间排序(升序,最旧的在前)
const sortedEntries = Object.entries(index).sort((a, b) => a[1] - b[1]);
// 逐个删除最旧的文件,直到总大小低于阈值的80%
const targetSize = this.MAX_DISK_SIZE * 0.8;
for (const [name, _] of sortedEntries) {
if (totalSize <= targetSize) break;
const filePath = this.cacheDir + '/' + name;
try {
if (fs.accessSync(filePath)) {
const stat = fs.statSync(filePath);
totalSize -= stat.size;
fs.unlinkSync(filePath);
delete index[name];
console.info('[DiskCache] 淘汰缓存文件: ' + name);
}
} catch (e) {
delete index[name];
}
}
this.writeIndex(index);
}
/**
* 清空所有磁盘缓存
*/
clear(): void {
const index = this.readIndex();
for (const name of Object.keys(index)) {
const filePath = this.cacheDir + '/' + name;
try {
if (fs.accessSync(filePath)) {
fs.unlinkSync(filePath);
}
} catch (e) {
// 忽略删除失败
}
}
this.writeIndex({});
console.info('[DiskCache] 磁盘缓存已清空');
}
}
3.3 双层缓存 + 网络加载的完整图片加载器
把内存缓存和磁盘缓存组合起来,加上网络请求,就是一个完整的图片加载管线。同时支持缓存预热。
import { http } from '@kit.NetworkKit';
import { image } from '@kit.ImageKit';
import { common } from '@kit.AbilityKit';
/**
* 图像双层缓存加载器
* 整合内存缓存、磁盘缓存和网络请求,实现三级查找链路
*/
export class ImageCacheLoader {
private memoryCache: ImageMemoryCache;
private diskCache: ImageDiskCache;
// 正在进行的请求,避免重复下载
private pendingRequests: Map<string, Promise<image.PixelMap>> = new Map();
constructor(context: common.Context) {
this.memoryCache = new ImageMemoryCache(100);
this.diskCache = new ImageDiskCache(context, 50 * 1024 * 1024);
}
/**
* 加载图片(三级缓存链路)
* @param url 图片URL
* @param options 图片解码参数
*/
async loadImage(url: string, options?: image.DecodingOptions): Promise<image.PixelMap> {
// 第一级:内存缓存
const memCached = this.memoryCache.get(url);
if (memCached !== undefined) {
return memCached;
}
// 第二级:磁盘缓存
const diskData = this.diskCache.get(url);
if (diskData !== null) {
const pixelMap = await this.decodeToPixelMap(diskData, options);
this.memoryCache.put(url, pixelMap);
return pixelMap;
}
// 第三级:网络请求(防重复)
if (this.pendingRequests.has(url)) {
return this.pendingRequests.get(url)!;
}
const requestPromise = this.fetchFromNetwork(url, options);
this.pendingRequests.set(url, requestPromise);
try {
const pixelMap = await requestPromise;
return pixelMap;
} finally {
this.pendingRequests.delete(url);
}
}
/**
* 从网络下载图片
*/
private async fetchFromNetwork(url: string, options?: image.DecodingOptions): Promise<image.PixelMap> {
console.info('[ImageLoader] 网络请求: ' + url);
const httpRequest = http.createHttp();
try {
const response = await httpRequest.request(url, {
method: http.RequestMethod.GET,
expectDataType: http.HttpDataType.ARRAY_BUFFER,
connectTimeout: 10000,
readTimeout: 10000
});
if (response.responseCode !== 200) {
throw new Error(`HTTP错误: ${response.responseCode}`);
}
const imageData = response.result as ArrayBuffer;
// 写入磁盘缓存
this.diskCache.put(url, imageData);
// 解码并写入内存缓存
const pixelMap = await this.decodeToPixelMap(imageData, options);
this.memoryCache.put(url, pixelMap);
return pixelMap;
} finally {
httpRequest.destroy();
}
}
/**
* 将ArrayBuffer解码为PixelMap
*/
private async decodeToPixelMap(data: ArrayBuffer, options?: image.DecodingOptions): Promise<image.PixelMap> {
const imageSource = image.createImageSource(data);
const decodingOptions: image.DecodingOptions = options ?? {
editable: false,
desiredSize: { width: 0, height: 0 },
desiredPixelFormat: image.PixelMapFormat.RGBA_8888
};
const pixelMap = await imageSource.createPixelMap(decodingOptions);
imageSource.release();
return pixelMap;
}
/**
* 缓存预热:提前加载图片到缓存
* 适用于首页图片预加载场景
* @param urls 需要预热的图片URL列表
*/
async prewarm(urls: string[]): Promise<void> {
console.info(`[ImageLoader] 开始缓存预热,共${urls.length}张图片`);
const tasks: Promise<void>[] = [];
for (const url of urls) {
// 并发预热,但限制并发数
tasks.push(this.loadImage(url).then(() => {
console.info('[ImageLoader] 预热完成: ' + url);
}).catch((err: Error) => {
console.warn('[ImageLoader] 预热失败: ' + url + ', 原因: ' + err.message);
}));
}
await Promise.allSettled(tasks);
console.info('[ImageLoader] 缓存预热全部完成');
}
/**
* 获取缓存统计信息
*/
getCacheStats(): object {
return {
memory: this.memoryCache.getStats()
};
}
/**
* 清空所有缓存
*/
clearAll(): void {
this.memoryCache.clear();
this.diskCache.clear();
}
}
3.4 在UI组件中使用缓存加载器
import { image } from '@kit.ImageKit';
import { common } from '@kit.AbilityKit';
@Entry
@Component
struct ImageCacheDemoPage {
// 缓存加载器实例
private cacheLoader: ImageCacheLoader | null = null;
// 图片列表
@State imageList: string[] = [
'https://example.com/photo1.jpg',
'https://example.com/photo2.jpg',
'https://example.com/photo3.jpg',
'https://example.com/photo4.jpg',
'https://example.com/photo5.jpg'
];
// 已加载的PixelMap
@State pixelMaps: Map<string, image.PixelMap> = new Map();
// 加载状态
@State loadingStates: Map<string, boolean> = new Map();
// 缓存统计
@State cacheStats: string = '';
aboutToAppear(): void {
// 初始化缓存加载器
this.cacheLoader = new ImageCacheLoader(getContext(this) as common.Context);
// 缓存预热:提前加载前3张图片
this.prewarmCache();
}
/**
* 缓存预热
*/
async prewarmCache(): Promise<void> {
if (this.cacheLoader === null) return;
const prewarmUrls = this.imageList.slice(0, 3);
await this.cacheLoader.prewarm(prewarmUrls);
// 预热完成后自动加载
for (const url of prewarmUrls) {
this.loadAndDisplay(url);
}
}
/**
* 加载并显示图片
*/
async loadAndDisplay(url: string): Promise<void> {
if (this.cacheLoader === null) return;
this.loadingStates.set(url, true);
try {
const pixelMap = await this.cacheLoader.loadImage(url);
this.pixelMaps.set(url, pixelMap);
this.loadingStates.set(url, false);
// 更新缓存统计
const stats = this.cacheLoader.getCacheStats();
this.cacheStats = JSON.stringify(stats, null, 2);
} catch (err) {
this.loadingStates.set(url, false);
console.error('图片加载失败: ' + url);
}
}
build() {
Column() {
// 标题栏
Text('图像缓存演示')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 16 })
// 缓存统计面板
Row() {
Text('缓存统计')
.fontSize(14)
.fontColor('#999999')
Text(this.cacheStats)
.fontSize(12)
.fontColor('#666666')
.maxLines(3)
}
.width('100%')
.padding(12)
.backgroundColor('#1A1A2E')
.borderRadius(8)
.margin({ bottom: 16 })
// 图片网格
Grid() {
ForEach(this.imageList, (url: string) => {
GridItem() {
Column() {
if (this.pixelMaps.has(url)) {
Image(this.pixelMaps.get(url))
.width('100%')
.height(160)
.objectFit(ImageFit.Cover)
.borderRadius(8)
} else if (this.loadingStates.get(url) === true) {
// 加载中占位
Column() {
LoadingProgress()
.width(32)
.height(32)
.color('#4FC3F7')
Text('加载中...')
.fontSize(12)
.fontColor('#999999')
.margin({ top: 8 })
}
.width('100%')
.height(160)
.justifyContent(FlexAlign.Center)
.backgroundColor('#1A1A2E')
.borderRadius(8)
} else {
// 未加载占位
Column() {
Text('点击加载')
.fontSize(14)
.fontColor('#4FC3F7')
}
.width('100%')
.height(160)
.justifyContent(FlexAlign.Center)
.backgroundColor('#1A1A2E')
.borderRadius(8)
.onClick(() => this.loadAndDisplay(url))
}
}
}
.padding(4)
})
}
.columnsTemplate('1fr 1fr')
.rowsGap(8)
.columnsGap(8)
.width('100%')
.height(400)
// 操作按钮
Row() {
Button('全部加载')
.backgroundColor('#4FC3F7')
.fontColor('#000000')
.onClick(() => {
for (const url of this.imageList) {
this.loadAndDisplay(url);
}
})
Button('清空缓存')
.backgroundColor('#EF5350')
.fontColor('#FFFFFF')
.onClick(() => {
this.cacheLoader?.clearAll();
this.pixelMaps.clear();
this.loadingStates.clear();
this.cacheStats = '缓存已清空';
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
.margin({ top: 16 })
}
.width('100%')
.height('100%')
.padding(16)
.backgroundColor('#0D0D1A')
}
}
四、踩坑与注意事项
4.1 PixelMap不能跨线程传递
这是一个非常常见的坑。PixelMap对象不能直接通过@Sendable或TaskPool跨线程传递。如果你在子线程解码了PixelMap,必须在线程内完成所有操作后再回到主线程使用。
解决方案:在子线程中只做网络请求和磁盘读写,将ArrayBuffer传回主线程后再解码为PixelMap。
4.2 内存缓存容量不是越大越好
LRUCache的容量设置需要权衡。容量太大,会导致内存占用过高,触发系统内存回收反而更慢;容量太小,缓存命中率低,失去了缓存的意义。
建议:
- 一般App:50-100条
- 图片密集型App(如相册):200-300条
- 根据实际图片大小动态调整,总内存占用建议不超过可用内存的1/8
4.3 磁盘缓存的线程安全问题
多个请求同时写入磁盘缓存时,可能出现文件读写冲突。索引文件的并发修改尤其危险。
解决方案:
- 使用
@Sendable+TaskPool时,确保磁盘缓存操作在同一个串行队列中执行 - 或者使用
distributedKVStore替代手动管理索引文件
4.4 缓存Key的设计
直接用URL作为缓存Key可能不够安全。同一个URL可能返回不同内容(服务端更新了图片),或者URL中包含时效性参数。
建议:
- 使用URL + 内容哈希作为Key
- 或者响应头中的ETag/Last-Modified来判断缓存是否过期
- 对于CDN图片,URL通常就是稳定的,可以直接用
4.5 缓存预热不要阻塞启动
预热是好事,但不要在App启动时同步等待所有预热完成,否则启动速度会变慢。
建议:预热放在后台异步执行,不阻塞首屏渲染。首页先显示占位图,预热完成后自动替换。
五、HarmonyOS 6适配
5.1 LRUCache API变化
| 变化项 | HarmonyOS 5 | HarmonyOS 6 |
|---|---|---|
| LRUCache构造参数 | new LRUCache(capacity) |
不变 |
| contains方法 | lruCache.contains(key) |
不变 |
| getCapacity方法 | lruCache.getCapacity() |
不变 |
| trimToSize方法 | 无 | 新增trimToSize(newSize),主动缩减缓存 |
5.2 缓存目录变化
HarmonyOS 6中,应用沙箱的缓存目录路径规则不变,但系统对缓存目录的空间配额管理更加严格。当设备存储空间不足时,系统可能自动清理cacheDir下的文件。
迁移建议:
- 重要缓存数据不要只存在
cacheDir,应同时备份到filesDir或preferences - 使用
@ohos.file.statvfsAPI定期检查磁盘剩余空间 - 监听
@ohos.app.ability.Want中的存储空间广播
5.3 新增cacheManager API
HarmonyOS 6增强了@ohos.file.cacheManager模块,支持应用级缓存配额设置:
// HarmonyOS 6 新增API示例
import { cacheManager } from '@kit.CoreFileKit';
// 设置应用缓存配额
cacheManager.setCacheQuota({
totalQuota: 100 * 1024 * 1024, // 100MB
imageQuota: 50 * 1024 * 1024 // 图片缓存50MB
});
六、总结
| 知识点 | 核心内容 |
|---|---|
| 双层缓存架构 | 内存缓存(快、小)+ 磁盘缓存(慢、大),三级查找链路:内存→磁盘→网络 |
| LRU淘汰策略 | 最近最少使用,双向链表+哈希表实现O(1)操作,满时淘汰链表尾部 |
| 内存缓存 | 使用LRUCache<string, PixelMap>,容量建议50-300条,注意PixelMap不能跨线程 |
| 磁盘缓存 | 文件存储+索引文件记录访问时间,超限时按LRU淘汰,注意线程安全 |
| 缓存预热 | 启动时异步预加载,不阻塞首屏,限制并发数避免资源争抢 |
| 缓存Key设计 | URL+内容哈希,注意CDN参数和时效性参数 |
| HarmonyOS 6 | 新增trimToSize方法、cacheManager配额管理,系统可能自动清理cacheDir |
一句话总结:图像缓存不是「存起来就行」,而是一套从淘汰策略到预热调度的系统工程。LRU是骨架,双层缓存是血肉,预热是灵魂——三者缺一不可。
- 点赞
- 收藏
- 关注作者
评论(0)