DefaultCacheManager
/// The DefaultCacheManager that can be easily used directly. The code of
/// this implementation can be used as inspiration for more complex cache
/// managers.
class DefaultCacheManager extends CacheManager with ImageCacheManager {
static const key = 'libCachedImageData';
static final DefaultCacheManager _instance = DefaultCacheManager._();
factory DefaultCacheManager() {
return _instance;
}
DefaultCacheManager._() : super(Config(key));
}
DefaultCacheManager 是继承自 CacheManager 和 ImageCacheManager 的默认缓存管理器实现,旨在提供一个方便的缓存解决方案。具体来说,它是一个单例对象,使用 Config(key) 配置来初始化 CacheManager,并添加了图片缓存的能力。
CacheManager 是一个抽象类,它定义了一组管理缓存的 API,例如 getFileFromCache、putFile 和 removeFile 等方法。通过继承该类并实现其中的方法,可以创建一个自定义的缓存管理器。
ImageCacheManager 是一个 mixin,它提供了一些特定于图片缓存的方法,例如 getImageFile、putImageFile 和 removeImageFile 等。通过继承该类和实现其方法,可以创建一个支持图片缓存的自定义缓存管理器。
在 DefaultCacheManager 的实现中,它继承了 CacheManager 和 ImageCacheManager,并使用 Config(key) 来初始化 CacheManager,其中 key 是该缓存管理器的名称。通过这种方式,它可以使用 CacheManager 的所有方法,同时还添加了对图片缓存的支持。
此外,由于它是一个单例对象,因此在整个应用程序生命周期中只存在一个 DefaultCacheManager 实例,可以确保所有的文件和图片缓存操作都是针对同一实例进行的。
ImageCacheManager
const supportedFileNames = ['jpg', 'jpeg', 'png', 'tga', 'cur', 'ico'];
mixin ImageCacheManager on BaseCacheManager {}
ImageCacheManager 是一个 Dart mixin(混入)类,通过 mixin 的方式混合到 DefaultCacheManager 中。
getImageFile
final Map<String, Stream<FileResponse>> _runningResizes = {};
/// Returns a resized image file to fit within maxHeight and maxWidth. It
/// tries to keep the aspect ratio. It stores the resized image by adding
/// the size to the key or url. For example when resizing
/// https://via.placeholder.com/150 to max width 100 and height 75 it will
/// store it with cacheKey resized_w100_h75_https://via.placeholder.com/150.
///
/// When the resized file is not found in the cache the original is fetched
/// from the cache or online and stored in the cache. Then it is resized
/// and returned to the caller.
Stream<FileResponse> getImageFile(
String url, {
String? key,
Map<String, String>? headers,
bool withProgress = false,
int? maxHeight,
int? maxWidth,
}) async* {
if (maxHeight == null && maxWidth == null) {
yield* getFileStream(url,
key: key, headers: headers, withProgress: withProgress);
return;
}
key ??= url;
var resizedKey = 'resized';
if (maxWidth != null) resizedKey += '_w$maxWidth';
if (maxHeight != null) resizedKey += '_h$maxHeight';
resizedKey += '_$key';
var fromCache = await getFileFromCache(resizedKey);
if (fromCache != null) {
yield fromCache;
if (fromCache.validTill.isAfter(DateTime.now())) {
return;
}
withProgress = false;
}
var runningResize = _runningResizes[resizedKey];
if (runningResize == null) {
runningResize = _fetchedResizedFile(
url,
key,
resizedKey,
headers,
withProgress,
maxWidth: maxWidth,
maxHeight: maxHeight,
).asBroadcastStream();
_runningResizes[resizedKey] = runningResize;
}
yield* runningResize;
_runningResizes.remove(resizedKey);
}
该方法用于获取图片文件并可以按指定的最大高度和宽度进行缩放,保持原始图片的长宽比例。如果缓存中已经存在所需尺寸的缩放图片,则直接返回缓存中的图片,否则从缓存或者网络中获取原始图片,进行缩放后返回给调用者。方法参数包括图片的 URL,缓存键值,HTTP 请求头,是否需要进度报告,最大高度和最大宽度。如果不指定最大高度和宽度,则直接获取原始图片文件。如果指定了最大高度和宽度,则按照最大高度和宽度对图片进行缩放。对于缩放后的图片,缓存键值会自动加上resized前缀和指定的尺寸后缀,例如resized_w100_h75_https://via.placeholder.com/150,以避免缓存键值冲突。
该方法返回一个 Stream<FileResponse>,可以通过监听这个 Stream 来获得异步的文件响应结果。在获取响应结果前,该方法会先检查缓存是否已经存在所需的图片,如果存在,则直接返回缓存中的图片,并且如果缓存中的图片没有过期,则不会进行后续操作。如果缓存中不存在所需尺寸的图片,则会从网络或者缓存中获取原始图片,并且在获取原始图片后进行缩放。
如果需要缩放,就通过 _fetchedResizedFile 方法开始异步处理图片文件的下载和缩放,并将返回的 Stream 对象保存到 _runningResizes 字典中,以便后续可以直接使用。
如果之前已经在下载和缩放该图片文件,则直接使用之前保存的 _runningResizes 中的 Stream 对象进行后续操作,避免了重复下载和缩放同一张图片。当图片下载和缩放完成之后,将对应的 _runningResizes 中的 Stream 对象从字典中移除。
_fetchedResizedFile
Stream<FileResponse> _fetchedResizedFile(
String url,
String originalKey,
String resizedKey,
Map<String, String>? headers,
bool withProgress, {
int? maxWidth,
int? maxHeight,
}) async* {
await for (var response in getFileStream(
url,
key: originalKey,
headers: headers,
withProgress: withProgress,
)) {
if (response is DownloadProgress) {
yield response;
}
if (response is FileInfo) {
yield await _resizeImageFile(
response,
resizedKey,
maxWidth,
maxHeight,
);
}
}
}
这是一个私有方法 _fetchedResizedFile,用于实际获取和调整大小图片文件的操作。它接受原始图片的URL、缓存的原始文件键、调整大小后缓存文件的键、可选的头信息、一个布尔值,表示是否需要包含下载进度,以及调整大小的最大宽度和最大高度。
在方法中,它使用 getFileStream 从URL获取文件流,并使用 yield* 返回其元素。如果元素是 DownloadProgress,则直接返回它以更新下载进度。如果元素是 FileInfo,则使用 _resizeImageFile 调整大小,然后返回调整大小后的文件信息。
_resizeImageFile
Future<FileInfo> _resizeImageFile(
FileInfo originalFile,
String key,
int? maxWidth,
int? maxHeight,
) async {
var originalFileName = originalFile.file.path;
var fileExtension = originalFileName.split('.').last;
if (!supportedFileNames.contains(fileExtension)) {
return originalFile;
}
var image = await _decodeImage(originalFile.file);
var shouldResize = maxWidth != null
? image.width > maxWidth
: false || maxHeight != null
? image.height > maxHeight
: false;
if (!shouldResize) return originalFile;
if (maxWidth != null && maxHeight != null) {
var resizeFactorWidth = image.width / maxWidth;
var resizeFactorHeight = image.height / maxHeight;
var resizeFactor = max(resizeFactorHeight, resizeFactorWidth);
maxWidth = (image.width / resizeFactor).round();
maxHeight = (image.height / resizeFactor).round();
}
var resized = await _decodeImage(originalFile.file,
width: maxWidth, height: maxHeight, allowUpscaling: false);
var resizedFile =
(await resized.toByteData(format: ui.ImageByteFormat.png))!
.buffer
.asUint8List();
var maxAge = originalFile.validTill.difference(DateTime.now());
var file = await putFile(
originalFile.originalUrl,
resizedFile,
key: key,
maxAge: maxAge,
fileExtension: fileExtension,
);
return FileInfo(
file,
originalFile.source,
originalFile.validTill,
originalFile.originalUrl,
);
}
该方法用于对原始图片进行缩放操作,并将缩放后的图片保存在缓存中。
具体来说,该方法接受四个参数:
- originalFile: 原始文件信息,包含了原始图片的File对象、缓存的来源以及过期时间等信息。
- key: 缓存的键,即存储缩放后图片的文件名。
- maxWidth: 缩放后图片的最大宽度。
- maxHeight: 缩放后图片的最大高度。
方法内部首先会根据原始文件的路径获取文件扩展名,如果扩展名不在支持的类型列表内,则直接返回原始文件信息。
接着,方法会使用decodeImage方法将原始图片解码成一个Image对象,并检查是否需要对其进行缩放操作。如果不需要缩放,则直接返回原始文件信息。
如果需要缩放,则根据maxWidth和maxHeight计算缩放比例,并使用decodeImage方法对原始图片进行缩放。缩放完成后,将缩放后的图片保存为png格式的字节数组,并调用putFile方法将其存储到缓存中。
最后,方法返回一个FileInfo对象,其中包含了缩放后的图片File对象、缓存的来源、过期时间以及原始图片的URL。
_decodeImage
Future<ui.Image> _decodeImage(File file,
{int? width, int? height, bool allowUpscaling = false}) {
var shouldResize = width != null || height != null;
var fileImage = FileImage(file);
final image = shouldResize
? ResizeImage(fileImage,
width: width, height: height, allowUpscaling: allowUpscaling)
: fileImage as ImageProvider;
final completer = Completer<ui.Image>();
image
.resolve(const ImageConfiguration())
.addListener(ImageStreamListener((info, _) {
completer.complete(info.image);
image.evict();
}));
return completer.future;
}
该方法用于将文件解码成UI图像对象。它接受一个文件对象,以及可选的宽度和高度参数。如果提供了宽度或高度,则会尝试调整图像大小以适应给定的宽度和/或高度,并且allowUpscaling参数可以控制是否允许图像放大。如果没有提供宽度或高度参数,则不会调整图像大小。该方法返回一个Future,该Future最终将返回UI图像对象。
解释说明
maxWidth 、maxHeight
maxWidth和maxHeight是指调整后的图片最大宽度和高度。在对图片进行调整大小的时候,该方法会检查图片的实际宽度和高度是否超过了给定的maxWidth和maxHeight,如果超过了就进行缩小操作,如果没有超过就不做任何操作,返回原图。如果同时给定了maxWidth和maxHeight,那么调整后的图片宽度和高度将按照宽高比例缩小,但是最终的宽度和高度都不会超过给定的maxWidth和maxHeight。
缓存到内存、磁盘的图像,是缩放后的图像。
精妙之处
flutter_cache_manager 是一个 Flutter 缓存管理器,它允许 Flutter 应用在网络请求和本地缓存之间进行平滑的切换,同时提供了一些有用的特性和 API,设计的精妙之处主要有以下几点:
- 多种缓存策略可选:
flutter_cache_manager提供了多种缓存策略,包括内存缓存、文件系统缓存、自定义缓存等,用户可以根据自己的需求选择合适的缓存策略。 - 自动缓存文件:
flutter_cache_manager会自动缓存网络请求的文件,并根据 HTTP 响应头中的缓存控制信息(如 Cache-Control、ETag 等)来决定文件是否需要重新请求和缓存,从而避免了重复的网络请求。 - 异步处理:
flutter_cache_manager中的大部分 API 都是异步的,这意味着它们不会阻塞主线程,可以提升应用的性能和流畅度。 - 简单易用:
flutter_cache_manager的 API 设计简单明了,易于上手,同时还提供了详细的文档和示例,方便开发者使用。
综上所述,flutter_cache_manager 是一个功能强大、设计精妙、易于使用的 Flutter 缓存管理库,它可以帮助开发者更好地管理应用中的缓存,并提升应用的性能和用户体验。