flutter_cache_manager
本地缓存是常用的辅助功能。接下来,我们一起探究下经典的本地缓存三方库flutter_cache_manager
CacheManager 用于下载和缓存应用缓存目录中的文件。 缓存多长时间可通过各种设置来修改。 它使用 Cache-Control HTTP 头部来高效提取文件。
flutter_cache_manager | Flutter Package (flutter-io.cn)
BaseCacheManager抽象类
BaseCacheManager是flutter_cache_manager库的一个抽象类,定义了缓存管理器的基本结构。它提供了一些方法来检查、获取、清除缓存以及缓存文件的路径。在flutter_cache_manager中,缓存管理器是为了帮助开发人员在应用中管理和处理缓存的类。
getSingleFile
/// Get the file from the cache and/or online, depending on availability and age.
/// Downloaded form [url], [headers] can be used for example for authentication.
/// When a file is cached it is return directly, when it is too old the file is
/// downloaded in the background. When a cached file is not available the
/// newly downloaded file is returned.
Future<File> getSingleFile(
String url, {
String key,
Map<String, String> headers,
});
getSingleFile是BaseCacheManager中的一个方法,它接受一个URL以及可选的key和headers参数,返回一个Future<File>对象。
该方法的主要功能是从缓存和/或在线获取文件,具体取决于文件的可用性和年龄。如果文件已被缓存,则直接返回缓存中的文件,否则会在后台下载文件。如果缓存文件不可用,则会返回新下载的文件。
如果指定了key,则使用它作为文件名进行缓存。如果未指定key,则使用URL的哈希值作为文件名。headers参数可以用于添加HTTP请求头,例如用于身份验证。
总体来说,getSingleFile方法提供了一个方便的方式来获取文件,同时最大限度地减少对网络资源的依赖。如果文件已被缓存,则从缓存获取文件可以加快加载速度,并减少对网络的请求。
CacheManager 可用多种方式获取一个文件。获取单个文件最简单的方式是调用 .getSingleFile 。
getFileStream
/// Get the file from the cache and/or online, depending on availability and age.
/// Downloaded form [url], [headers] can be used for example for authentication.
/// The files are returned as stream. First the cached file if available, when the
/// cached file is too old the newly downloaded file is returned afterwards.
///
/// The [FileResponse] is either a [FileInfo] object for fully downloaded files
/// or a [DownloadProgress] object for when a file is being downloaded.
/// The [DownloadProgress] objects are only dispatched when [withProgress] is
/// set on true and the file is not available in the cache. When the file is
/// returned from the cache there will be no progress given, although the file
/// might be outdated and a new file is being downloaded in the background.
Stream<FileResponse> getFileStream(String url,
{String? key, Map<String, String>? headers, bool withProgress});
BaseCacheManager 类的 getFileStream 方法用于获取一个文件的流,这个文件可以从缓存或者网络获取。它接受以下参数:
url: 要获取的文件的 URL。key: 用于唯一标识此文件的键。如果未提供,则会使用 URL 作为键。headers: 要包含在下载请求中的标头。withProgress: 是否在下载期间发布下载进度更新。如果设置为 true 并且文件不在缓存中,则会发出DownloadProgress对象,否则将不提供进度更新。
这个方法返回一个 Stream<FileResponse> 流,其中 FileResponse 是一个抽象类,它有两个实现类:
FileInfo: 完全下载的文件信息。DownloadProgress: 正在下载的文件进度信息。
当文件被缓存时,它会直接返回,当文件过期时,它会在后台下载。当缓存中没有可用的文件时,将返回新下载的文件。
值得注意的是,如果要使用此方法,请确保您已经导入了 dart:async 库,因为它需要返回一个流。
downloadFile
///Download the file and add to cache
Future<FileInfo> downloadFile(String url,
{String? key, Map<String, String>? authHeaders, bool force = false});
downloadFile是BaseCacheManager的方法,用于下载文件并将其添加到缓存中。它的参数包括url表示要下载的文件的URL,key表示缓存中存储文件的键值,authHeaders用于身份验证的头部信息,force表示是否强制下载。该方法返回一个Future<FileInfo>对象,该对象表示下载完成的文件信息。
在下载文件时,downloadFile首先会尝试从缓存中查找文件。如果文件存在且未过期,则返回缓存文件。否则,它将下载文件并将其添加到缓存中。如果force参数设置为true,则无论缓存中是否存在文件,都将下载文件。
下载文件时,BaseCacheManager使用HttpClient类来发出HTTP请求。默认情况下,HttpClient会尝试重用现有的TCP连接以提高性能。下载的文件被保存在应用程序的缓存目录中。
getFileFromCache
/// Get the file from the cache.
/// Specify [ignoreMemCache] to force a re-read from the database
Future<FileInfo?> getFileFromCache(String key, {bool ignoreMemCache = false});
该方法用于从缓存中获取文件,可指定是否忽略内存缓存。它接受一个 key 参数,用于查找缓存中的文件。当 ignoreMemCache 参数设置为 false 时,方法首先尝试从内存缓存中获取文件。如果文件不在内存缓存中,它将尝试从磁盘缓存中获取文件。如果文件不在磁盘缓存中,则返回 null。
如果 ignoreMemCache 参数设置为 true,则该方法将直接从磁盘缓存中获取文件,而不检查内存缓存。
getFileFromMemory
///Returns the file from memory if it has already been fetched
Future<FileInfo?> getFileFromMemory(String key);
这个方法用于从内存中获取指定key对应的文件信息(FileInfo),如果该文件信息已经被获取并保存在内存中,可以直接返回;否则返回null。
putFile
/// Put a file in the cache. It is recommended to specify the [eTag] and the
/// [maxAge]. When [maxAge] is passed and the eTag is not set the file will
/// always be downloaded again. The [fileExtension] should be without a dot,
/// for example "jpg". When cache info is available for the url that path
/// is re-used.
/// The returned [File] is saved on disk.
Future<File> putFile(
String url,
Uint8List fileBytes, {
String? key,
String? eTag,
Duration maxAge = const Duration(days: 30),
String fileExtension = 'file',
});
这个方法用于将文件保存到缓存中,其中包括文件的URL、文件内容、缓存的键(可选)、ETag(可选)、最大缓存时间(默认为30天)和文件扩展名(默认为'file')等参数。在保存文件时,如果缓存中已存在相同键的文件,则先删除旧文件。方法返回一个Future,返回保存的文件。
putFileStream
/// Put a byte stream in the cache. When using an existing file you can use
/// file.openRead(). It is recommended to specify the [eTag] and the
/// [maxAge]. When [maxAge] is passed and the eTag is not set the file will
/// always be downloaded again. The [fileExtension] should be without a dot,
/// for example "jpg". When cache info is available for the url that path
/// is re-used.
/// The returned [File] is saved on disk.
Future<File> putFileStream(
String url,
Stream<List<int>> source, {
String? key,
String? eTag,
Duration maxAge = const Duration(days: 30),
String fileExtension = 'file',
});
putFileStream方法与putFile方法类似,不同之处在于它可以将字节流缓存到磁盘中。传入的参数source是一个Stream<List<int>>类型的字节流,可以是从文件中读取的字节流,也可以是通过网络下载的字节流。
与putFile方法一样,也可以通过传入key参数来自定义缓存文件的路径,传入eTag参数来指定缓存文件的标识符,传入maxAge参数来指定缓存文件的最大存储时间。如果可用,则会重用与URL对应的缓存文件路径。最后,返回一个Future<File>对象,表示已保存在磁盘上的缓存文件。
removeFile
/// Remove a file from the cache
Future<void> removeFile(String key);
该方法用于从缓存中删除指定键的文件。
参数:
key:要删除的文件的键。
返回值:Future<void>。
emptyCache
/// Removes all files from the cache
Future<void> emptyCache();
该方法用于清空缓存,将所有文件从缓存中删除。调用该方法将返回一个Future对象,该对象在清空缓存完成时将被解析。可以在emptyCache()方法后使用getFileFromCache()来检查文件是否被成功删除。
dispose
/// Closes the cache database
Future<void> dispose();
该方法用于关闭缓存数据库。当你使用BaseCacheManager类创建一个缓存管理器对象时,它会打开一个数据库连接以用于读取和写入缓存信息。当你完成缓存操作时,应该使用该方法来关闭数据库连接,释放资源并确保所有文件已保存在磁盘上。
例如,当你的Flutter应用程序关闭时,你可以在dispose方法中调用该方法以关闭缓存数据库连接。
CacheManager实现类
class CacheManager implements BaseCacheManager {
static CacheManagerLogLevel logLevel = CacheManagerLogLevel.none;
}
该类是BaseCacheManager的基本实现,应该作为单个实例使用。它提供了对缓存的基本管理和操作,包括获取、存储、删除和清空缓存等功能。另外,该类还实现了dispose()方法,用于关闭缓存数据库。
logLevel是一个静态变量,用于设置缓存管理器的日志输出级别。可以设置为CacheManagerLogLevel.none、CacheManagerLogLevel.errors、CacheManagerLogLevel.debug或CacheManagerLogLevel.all。日志输出级别越高,输出的信息就越详细。
CacheManager(Config config)、CacheManager.custom(Config config, {CacheStore?cacheStore,WebHelper? webHelper,})
/// Creates a new instance of a cache manager. This can be used to retrieve
/// files from the cache or download them online. The http headers are used
/// for the maximum age of the files. The BaseCacheManager should only be
/// used in singleton patterns.
///
/// The [_cacheKey] is used for the sqlite database file and should be unique.
/// Files are removed when they haven't been used for longer than [stalePeriod]
/// or when this cache has grown too big. When the cache is larger than [maxNrOfCacheObjects]
/// files the files that haven't been used longest will be removed.
/// The [fileService] can be used to customize how files are downloaded. For example
/// to edit the urls, add headers or use a proxy. You can also choose to supply
/// a CacheStore or WebHelper directly if you want more customization.
CacheManager(Config config)
: _config = config,
_store = CacheStore(config) {
_webHelper = WebHelper(_store, config.fileService);
}
@visibleForTesting
CacheManager.custom(
Config config, {
CacheStore? cacheStore,
WebHelper? webHelper,
}) : _config = config,
_store = cacheStore ?? CacheStore(config) {
_webHelper = webHelper ?? WebHelper(_store, config.fileService);
}
创建一个新的 CacheManager 实例,它可以用于从缓存中检索文件或在线下载文件。
http headers 用于设置文件的最大存储时间。BaseCacheManager 应该只在单例模式下使用。
[_cacheKey] 用于 sqlite 数据库文件,应该是唯一的。当文件未使用的时间超过 [stalePeriod] 或者缓存文件数量超过 [maxNrOfCacheObjects] 时,文件将被删除。
如果缓存超过 [maxNrOfCacheObjects],则将删除最久未使用的文件。
[fileService] 可用于自定义文件下载方式。例如,编辑 URL、添加 headers 或使用代理。
如果需要更多的自定义选项,可以选择直接提供 CacheStore 或 WebHelper。
辅助类
final Config _config;
/// Store helper for cached files
final CacheStore _store;
/// Get the underlying store helper
CacheStore get store => _store;
/// WebHelper to download and store files
late final WebHelper _webHelper;
/// Get the underlying web helper
WebHelper get webHelper => _webHelper;
getSingleFile
/// Get the file from the cache and/or online, depending on availability and age.
/// Downloaded form [url], [headers] can be used for example for authentication.
/// When a file is cached and up to date it is return directly, when the cached
/// file is too old the file is downloaded and returned after download.
/// When a cached file is not available the newly downloaded file is returned.
@override
Future<File> getSingleFile(
String url, {
String? key,
Map<String, String>? headers,
}) async {
key ??= url;
final cacheFile = await getFileFromCache(key);
if (cacheFile != null && cacheFile.validTill.isAfter(DateTime.now())) {
return cacheFile.file;
}
return (await downloadFile(url, key: key, authHeaders: headers)).file;
}
getFileStream
/// Get the file from the cache and/or online, depending on availability and age.
/// Downloaded form [url], [headers] can be used for example for authentication.
/// The files are returned as stream. First the cached file if available, when the
/// cached file is too old the newly downloaded file is returned afterwards.
///
/// The [FileResponse] is either a [FileInfo] object for fully downloaded files
/// or a [DownloadProgress] object for when a file is being downloaded.
/// The [DownloadProgress] objects are only dispatched when [withProgress] is
/// set on true and the file is not available in the cache. When the file is
/// returned from the cache there will be no progress given, although the file
/// might be outdated and a new file is being downloaded in the background.
@override
Stream<FileResponse> getFileStream(String url,
{String? key, Map<String, String>? headers, bool withProgress = false}) {
key ??= url;
final streamController = StreamController<FileResponse>();
_pushFileToStream(streamController, url, key, headers, withProgress);
return streamController.stream;
}
Future<void> _pushFileToStream(StreamController streamController, String url,
String? key, Map<String, String>? headers, bool withProgress) async {
key ??= url;
FileInfo? cacheFile;
try {
cacheFile = await getFileFromCache(key);
if (cacheFile != null) {
streamController.add(cacheFile);
withProgress = false;
}
} catch (e) {
cacheLogger.log(
'CacheManager: Failed to load cached file for $url with error:\n$e',
CacheManagerLogLevel.debug);
}
if (cacheFile == null || cacheFile.validTill.isBefore(DateTime.now())) {
try {
await for (var response
in _webHelper.downloadFile(url, key: key, authHeaders: headers)) {
if (response is DownloadProgress && withProgress) {
streamController.add(response);
}
if (response is FileInfo) {
streamController.add(response);
}
}
} catch (e) {
cacheLogger.log(
'CacheManager: Failed to download file from $url with error:\n$e',
CacheManagerLogLevel.debug);
if (cacheFile == null && streamController.hasListener) {
streamController.addError(e);
}
}
}
unawaited(streamController.close());
}
该方法是私有方法,用于将文件推送到流中以便处理。它接收一个StreamController,它将在下载过程中输出FileInfo和DownloadProgress对象。如果可用,它首先从缓存中获取文件,如果缓存文件未过期,则直接将其推送到流中。如果缓存文件不可用或已过期,则使用WebHelper下载文件,并将其推送到流中。如果下载失败,它会将错误推送到流中,以便处理它。最后,它关闭了流。这是getSingleFile和getFileStream中的一个私有方法。
downloadFile
///Download the file and add to cache
@override
Future<FileInfo> downloadFile(String url,
{String? key,
Map<String, String>? authHeaders,
bool force = false}) async {
key ??= url;
var fileResponse = await _webHelper
.downloadFile(
url,
key: key,
authHeaders: authHeaders,
ignoreMemCache: force,
)
.firstWhere((r) => r is FileInfo);
return fileResponse as FileInfo;
}
getFileFromCache
/// Get the file from the cache.
/// Specify [ignoreMemCache] to force a re-read from the database
@override
Future<FileInfo?> getFileFromCache(String key,
{bool ignoreMemCache = false}) =>
_store.getFile(key, ignoreMemCache: ignoreMemCache);
getFileFromMemory
///Returns the file from memory if it has already been fetched
@override
Future<FileInfo?> getFileFromMemory(String key) =>
_store.getFileFromMemory(key);
putFile
/// Put a file in the cache. It is recommended to specify the [eTag] and the
/// [maxAge]. When [maxAge] is passed and the eTag is not set the file will
/// always be downloaded again. The [fileExtension] should be without a dot,
/// for example "jpg". When cache info is available for the url that path
/// is re-used.
/// The returned [File] is saved on disk.
@override
Future<File> putFile(
String url,
Uint8List fileBytes, {
String? key,
String? eTag,
Duration maxAge = const Duration(days: 30),
String fileExtension = 'file',
}) async {
key ??= url;
var cacheObject = await _store.retrieveCacheData(key);
cacheObject ??= CacheObject(
url,
key: key,
relativePath: '${const Uuid().v1()}.$fileExtension',
validTill: DateTime.now().add(maxAge),
);
cacheObject = cacheObject.copyWith(
validTill: DateTime.now().add(maxAge),
eTag: eTag,
);
final file = await _config.fileSystem.createFile(cacheObject.relativePath);
await file.writeAsBytes(fileBytes);
unawaited(_store.putFile(cacheObject));
return file;
}
这个方法用于将文件保存到缓存中,其中包括文件的URL、文件内容、缓存的键(可选)、ETag(可选)、最大缓存时间(默认为30天)和文件扩展名(默认为'file')等参数。在保存文件时,如果缓存中已存在相同键的文件,则先删除旧文件。方法返回一个Future,返回保存的文件。
putFileStream
/// Put a byte stream in the cache. When using an existing file you can use
/// file.openRead(). It is recommended to specify the [eTag] and the
/// [maxAge]. When [maxAge] is passed and the eTag is not set the file will
/// always be downloaded again. The [fileExtension] should be without a dot,
/// for example "jpg". When cache info is available for the url that path
/// is re-used.
/// The returned [File] is saved on disk.
@override
Future<File> putFileStream(
String url,
Stream<List<int>> source, {
String? key,
String? eTag,
Duration maxAge = const Duration(days: 30),
String fileExtension = 'file',
}) async {
key ??= url;
var cacheObject = await _store.retrieveCacheData(key);
cacheObject ??= CacheObject(url,
key: key,
relativePath: '${const Uuid().v1()}'
'.$fileExtension',
validTill: DateTime.now().add(maxAge));
cacheObject = cacheObject.copyWith(
validTill: DateTime.now().add(maxAge),
eTag: eTag,
);
var file = await _config.fileSystem.createFile(cacheObject.relativePath);
// Always copy file
var sink = file.openWrite();
await source
// this map is need to map UInt8List to List<int>
.map((event) => event)
.pipe(sink);
unawaited(_store.putFile(cacheObject));
return file;
}
removeFile
/// Remove a file from the cache
@override
Future<void> removeFile(String key) async {
final cacheObject = await _store.retrieveCacheData(key);
if (cacheObject?.id != null) {
await _store.removeCachedFile(cacheObject!);
}
}
emptyCache
/// Removes all files from the cache
@override
Future<void> emptyCache() => _store.emptyCache();
dispose
/// Closes the cache database
@override
Future<void> dispose() async {
await _config.repo.close();
}