在一些以图片为主的app中,常用页面一般都包含了很多网络图片,所以图片的显示速度非常重要,直接跟用户的体验挂钩。可以通过缓存图片的方法将图片保存下来,这样子除了第一次需要等待下载,后续都能够直接显示,就像本地图片一样。
但是随着图片的缓存越来越多,app的体积也会越来越大,所以需要定期或者定量的删除一些没有用或者不怎么用的图片缓存,这里用到的是LRU策略,以下是它的介绍:
以下是我根据这个解释,自己想到的一个flutter实现思路:
- 定义一个临时的有序数组,用来存储当前缓存的文件名称;
- 定义一个映射缓存字段,用来根据文件名去找到真正的缓存路径,如下图所示;
- 创建一个等待队列,把每张图片添加到临时数组的方法,通过队列的方式依次执行,确保不会出现
竞态的问题; - 每次有图片显示时,先查找这张图片都文件名是否已经出现在临时数组里,如果有的话需要把它“挪移”到最前面,没有到话直接在最前面添加就可以;
- 当添加完之后,检查当前的临时数组长度是否大于最大缓存数,如果大于了,需要将超过的部分删除掉,必须同时删除临时数组、映射缓存字段、图片实际缓存;
- 应用退出时,把临时数组保存到缓存中,确保下一次打开应用时,能衔接上去;
以下是实现的代码
LRU服务代码
import 'dart:async';
import 'dart:collection';
import 'dart:io';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:path_provider/path_provider.dart';
import '../utils/storage.dart';
class LruImageService {
static List<dynamic> imageNameList = []; // 文件名数组
static final Queue<Future<void> Function()> _operationQueue = Queue(); // 操作队列(存储待执行的增删操作)
static final int _maxCount = int.parse(dotenv.env['IMAGE_CACHE_MAX'].toString()); // 最大缓存数量(超过此值会清理)
static bool _isProcessing = false; // 是否正在执行队列中的操作(避免并发执行)
static String _directory = ""; // 文件目录
// 初始化
static void init(){
// 获取应用目录路径
_getDirectory();
// 初始化数组
_initList();
}
static void _getDirectory() async {
var directory = await StorageUtil.get(StorageUtil.APP_DIRECTORY);
if(directory != null){
_directory = directory;
}else{
directory = await getApplicationDocumentsDirectory();
StorageUtil.save(StorageUtil.APP_DIRECTORY, directory.path);
_directory = directory.path;
}
}
/// 初始化缓存数组
static Future<void> _initList() async {
imageNameList = await StorageUtil.get(StorageUtil.LRU_IMAGE_LIST, []);
}
/*
* 新增数据(只接收网络图片)
* 1. 提取文件名
* 2. 判断文件名数组(imageNameList)中是否有该图片数据,有的话把该文件名位置挪到第一位,没有的话直接在数组头部添加该文件名
* */
static void insertData(String fileName) {
// 将“新增操作”包装成Future,加入队列
_operationQueue.add(() async {
// 判断文件名数组中是否有该数据
bool hasData = imageNameList.contains(fileName);
if (hasData) {
// 有的话,直接删除掉
imageNameList.remove(fileName);
}
// 最后统一插入到最前面
imageNameList.insert(0, fileName);
// 判断添加完成后,是否超过最大显示,超过的话,就删除最后一个数据
if (imageNameList.length > _maxCount) {
await deleteFileByName(imageNameList[imageNameList.length - 1]);
imageNameList.removeLast();
}
});
// 触发队列处理
_processQueue();
}
// 根据图片名删除图片
static Future<void> deleteFileByName(String fileName) async {
String filePath = await StorageUtil.get(fileName, "");
if (filePath != "") {
try {
filePath = filePath.replaceAll("file_image_name:", "$_directory/");
final file = File(filePath);
// 检查文件是否存在
if (await file.exists()) {
await file.delete();
StorageUtil.remove(fileName);
print("删除了$filePath");
} else {
print('文件不存在:$filePath');
}
} catch (e) {
print('删除文件失败:$e');
}
} else {
print("文件$fileName不存在");
}
}
/// 处理队列(核心逻辑)
static Future<void> _processQueue() async {
// 若正在处理或队列为空,直接返回
if (_isProcessing || _operationQueue.isEmpty) return;
_isProcessing = true;
try {
// 取出队列中的第一个操作并执行
final operation = _operationQueue.removeFirst();
await operation(); // 等待当前操作完成
} catch (e) {
print('操作执行失败:$e');
} finally {
_isProcessing = false;
// 递归处理剩余操作(直到队列为空)
_processQueue();
}
}
/// 保存到缓存
static Future<void> saveFileNameList() async {
await StorageUtil.save(StorageUtil.LRU_IMAGE_LIST, imageNameList);
}
}
初始化
LruImageService.init(); // 图片缓存策略初始化
应用退出时
LruImageService.saveFileNameList();
实际使用
LruImageService.insertData(fileName);