flutter_cache_manager本地缓存之CacheStore、WebHelper源码解析(四)

593 阅读8分钟

CacheStore(缓存)

Duration cleanupRunMinInterval = const Duration(seconds: 10);

final _futureCache = <String, Future<CacheObject?>>{};
final _memCache = <String, CacheObject>{};

FileSystem fileSystem;

final Config _config;
String get storeKey => _config.cacheKey;
final Future<CacheInfoRepository> _cacheInfoRepository;
int get _capacity => _config.maxNrOfCacheObjects;
Duration get _maxAge => _config.stalePeriod;

DateTime lastCleanupRun = DateTime.now();
Timer? _scheduledCleanup;

CacheStore(Config config)
    : _config = config,
      fileSystem = config.fileSystem,
      _cacheInfoRepository = config.repo.open().then((value) => config.repo);

CacheStore是一个缓存存储的类,它实现了一个缓存的机制来存储和管理数据。它有一个内存缓存和一个持久化缓存。其中内存缓存用来提高访问速度,持久化缓存用来持久化数据。以下是它的主要属性和构造函数:

  • cleanupRunMinInterval:清理过期缓存的最小时间间隔。默认间隔10s。
  • _futureCache:一个用于存储所有缓存对象的 Future 对象的 Map,其中 Key 是缓存的键,Value 是一个 Future,用于异步获取缓存对象。
  • _memCache:一个用于存储所有缓存对象的 Map,其中 Key 是缓存的键,Value 是缓存对象本身。
  • fileSystem:一个用于文件系统操作的 FileSystem 对象。
  • _config:一个 Config 对象,用于配置缓存的各种属性。
  • storeKey:一个字符串,用于标识该缓存存储。
  • _cacheInfoRepository:一个 Future 对象,用于异步获取一个 CacheInfoRepository 对象,该对象用于管理缓存元信息。
  • _capacity:一个整数,表示缓存的最大容量。
  • _maxAge:一个 Duration 对象,表示缓存的最大存活时间。
  • lastCleanupRun:一个 DateTime 对象,表示上次清理过期缓存的时间。
  • _scheduledCleanup:一个 Timer 对象,用于定期清理过期缓存。
  • CacheStore(Config config):一个构造函数,接受一个 Config 对象作为参数,用于初始化缓存的各种属性。

putFile

Future<void> putFile(CacheObject cacheObject) async {
  _memCache[cacheObject.key] = cacheObject;
  final dynamic out = await _updateCacheDataInDatabase(cacheObject);

  // We update the cache object with the id if returned by the repository
  if (out is CacheObject && out.id != null) {
    _memCache[cacheObject.key] = cacheObject.copyWith(id: out.id);
  }
}

这个方法的作用是将一个缓存对象存储到内存和数据库中。首先,它将缓存对象存储到内存中的 _memCache 字典中。然后,它调用 _updateCacheDataInDatabase 方法,将缓存对象存储到数据库中。如果存储成功,该方法将返回一个包含缓存对象 id 的对象。最后,如果返回的对象是一个 CacheObject 类型并且 id 不为 null,它将使用这个 id 更新缓存对象并存储到内存中。

_updateCacheDataInDatabase

Future<dynamic> _updateCacheDataInDatabase(CacheObject cacheObject) async {
  final provider = await _cacheInfoRepository;
  return provider.updateOrInsert(cacheObject);
}

这段代码是一个私有方法 _updateCacheDataInDatabase,用于将缓存对象存储到数据库中。它接受一个 CacheObject 对象作为参数,并返回一个 Future,用于表示操作的结果。在该方法中,它首先等待 _cacheInfoRepository 对象的初始化,然后调用其 updateOrInsert 方法,将缓存对象存储到数据库中,并返回结果。结果可以是一个整数(表示插入或更新的行数)或一个 CacheObject 对象(表示插入或更新的记录)。如果插入或更新的记录包含一个自增长的 ID,该方法会更新缓存对象的 ID 属性。

getFile

Future<FileInfo?> getFile(String key, {bool ignoreMemCache = false}) async {
  final cacheObject =
      await retrieveCacheData(key, ignoreMemCache: ignoreMemCache);
  if (cacheObject == null) {
    return null;
  }
  final file = await fileSystem.createFile(cacheObject.relativePath);
  cacheLogger.log(
      'CacheManager: Loaded $key from cache', CacheManagerLogLevel.verbose);

  return FileInfo(
    file,
    FileSource.Cache,
    cacheObject.validTill,
    cacheObject.url,
  );
}

这段代码是 CacheStore 类中的 getFile 方法。该方法用于从缓存中获取指定键的文件信息。具体来说,它首先调用 retrieveCacheData 方法获取缓存数据,如果该数据不存在则返回 null,否则它会创建一个文件并返回文件信息对象 FileInfo

FileInfo 对象包含了文件对象、文件来源、缓存数据的有效期和缓存数据的 URL。在这个方法中,文件来源是 FileSource.Cache,表示文件来自缓存。文件的有效期是缓存数据的有效期,URL 则是缓存数据中的 URL。

retrieveCacheData(重要)

Future<CacheObject?> retrieveCacheData(String key,
    {bool ignoreMemCache = false}) async {
  if (!ignoreMemCache && _memCache.containsKey(key)) {
    if (await _fileExists(_memCache[key])) {
      return _memCache[key];
    }
  }
  if (!_futureCache.containsKey(key)) {
    final completer = Completer<CacheObject?>();
    unawaited(_getCacheDataFromDatabase(key).then((cacheObject) async {
    /// 数据库有CacheObject对象,但是CacheObject对象存储的文件路径在本地找不到
      if (cacheObject?.id != null && !await _fileExists(cacheObject)) {
        final provider = await _cacheInfoRepository;
        await provider.delete(cacheObject!.id!);
        cacheObject = null;
      }

      if (cacheObject == null) {
        _memCache.remove(key);
      } else {
        _memCache[key] = cacheObject;
      }
      completer.complete(cacheObject);
      _futureCache.remove(key);
    }));
    _futureCache[key] = completer.future;
  }
  return _futureCache[key];
}

这是一个从缓存中检索 CacheObject 的异步函数,它接受一个键作为参数并返回一个CacheObject对象的Future,如果找不到,则返回null。

它首先检查内存缓存(_memCache)中是否存在指定的键,如果存在且文件存在,则返回该CacheObject。否则,它将从数据库中获取CacheObject,并在完成后将其存储在内存缓存中。

为了避免多个同时请求相同的CacheObject,它使用一个_futureCache Map来缓存尚未完成的Future。如果检索的CacheObject不存在于数据库中,则从内存缓存和未来缓存中删除它,并返回null。如果找到CacheObject并且在数据库中,但文件不存在,则删除该CacheObject,并从内存和未来缓存中删除该键。

_getCacheDataFromDatabase

Future<CacheObject?> _getCacheDataFromDatabase(String key) async {
  final provider = await _cacheInfoRepository;
  final data = await provider.get(key);
  if (await _fileExists(data)) {
    unawaited(_updateCacheDataInDatabase(data!));
  }
  _scheduleCleanup();
  return data;
}

该方法的功能是从数据库中获取给定键值的缓存数据。

    1. 获取缓存信息的存储库,并调用get方法从存储库中获取给定键值的缓存数据。
    1. 检查获取到的数据是否存在并且有效。如果数据存在且未过期,则使用_updateCacheDataInDatabase方法更新缓存数据的元数据。
    1. 调用_scheduleCleanup方法以安排下一次清理操作。
    1. 返回获取到的缓存数据。

_scheduleCleanup

void _scheduleCleanup() {
  if (_scheduledCleanup != null) {
    return;
  }
  _scheduledCleanup = Timer(cleanupRunMinInterval, () {
    _scheduledCleanup = null;
    _cleanupCache();
  });
}

该方法用于在一定时间后自动调用 _cleanupCache() 方法进行缓存清理。具体来说:

  • 如果 _scheduledCleanup 不为空,则说明之前已经设置了定时器,不需要重复设置,直接返回。
  • 否则,创建一个定时器 _scheduledCleanup,定时时长为 cleanupRunMinInterval(默认为 10 秒),定时器触发后调用 _cleanupCache() 方法进行缓存清理。

_cleanupCache

Future<void> _cleanupCache() async {
  final toRemove = <int>[];
  final provider = await _cacheInfoRepository;

  final overCapacity = await provider.getObjectsOverCapacity(_capacity);
  for (final cacheObject in overCapacity) {
    unawaited(_removeCachedFile(cacheObject, toRemove));
  }

  final oldObjects = await provider.getOldObjects(_maxAge);
  for (final cacheObject in oldObjects) {
    unawaited(_removeCachedFile(cacheObject, toRemove));
  }

  await provider.deleteAll(toRemove);
}

该方法用于清理缓存中过期或超过容量限制的对象。具体来说,它执行以下操作:

  1. 创建一个空的 toRemove 列表,用于存储要从缓存中删除的对象的 ID。
  2. _cacheInfoRepository 中获取缓存中超过容量限制的对象,遍历这些对象并调用 _removeCachedFile 方法将其从缓存中删除。同时将这些对象的 ID 存储在 toRemove 列表中。
  3. _cacheInfoRepository 中获取过期的对象,遍历这些对象并调用 _removeCachedFile 方法将其从缓存中删除。同时将这些对象的 ID 存储在 toRemove 列表中。
  4. 最后,调用 _cacheInfoRepositorydeleteAll 方法,将 toRemove 列表中存储的对象从数据库中删除。

_removeCachedFile

Future<void> _removeCachedFile(
    CacheObject cacheObject, List<int> toRemove) async {
  if (toRemove.contains(cacheObject.id)) return;

  toRemove.add(cacheObject.id!);
  if (_memCache.containsKey(cacheObject.key)) {
    _memCache.remove(cacheObject.key);
  }
  if (_futureCache.containsKey(cacheObject.key)) {
    _futureCache.remove(cacheObject.key);
  }
  final file = await fileSystem.createFile(cacheObject.relativePath);
  if (await file.exists()) {
    await file.delete();
  }
}

这段代码定义了一个私有方法 _removeCachedFile,用于删除缓存中的一个文件,并且会将其从内存缓存和未来缓存中移除。方法的输入参数是 CacheObject 类型的 cacheObject 对象和一个 List<int> 类型的 toRemove 列表,用于保存需要从数据库中删除的缓存记录的 ID。如果 toRemove 列表中已经包含了 cacheObject 对象对应的 ID,则不执行删除操作。

removeCachedFile

Future<void> removeCachedFile(CacheObject cacheObject) async {
  final provider = await _cacheInfoRepository;
  final toRemove = <int>[];
  await _removeCachedFile(cacheObject, toRemove);
  await provider.deleteAll(toRemove);
}

该方法的作用是从缓存中删除一个文件并从数据库中删除与其相关的缓存数据。它首先通过异步获取到缓存信息仓库实例 provider,然后创建一个整数类型的列表 toRemove,用于保存将要被删除的缓存数据的 ID。然后调用 _removeCachedFile 方法来从内存和磁盘中删除缓存数据,并将要删除的缓存数据的 ID 添加到 toRemove 中。最后,调用 provider.deleteAll 方法来从数据库中删除与这些缓存数据相关的数据。

getFileFromMemory

Future<FileInfo?> getFileFromMemory(String key) async {
  final cacheObject = _memCache[key];
  if (cacheObject == null) {
    return null;
  }
  final file = await fileSystem.createFile(cacheObject.relativePath);
  return FileInfo(
      file, FileSource.Cache, cacheObject.validTill, cacheObject.url);
}

该方法用于从内存缓存中获取指定key对应的缓存文件信息。它会先检查内存缓存中是否存在该key对应的缓存信息,如果存在,则通过文件系统创建相应的文件,并返回一个包含该文件信息的FileInfo对象,其中包括文件对象、文件来源(这里是缓存)、缓存过期时间和缓存url。如果内存缓存中不存在该key对应的缓存信息,则返回null。

_fileExists

Future<bool> _fileExists(CacheObject? cacheObject) async {
  if (cacheObject == null) {
    return false;
  }
  var file = await fileSystem.createFile(cacheObject.relativePath);
  return file.exists();
}

该函数的作用是判断给定的 CacheObject 对象所代表的文件是否存在于本地缓存中。如果存在,返回 true,否则返回 false。这里首先判断传入的 cacheObject 是否为空,如果为空,则直接返回 false,因为文件显然不存在。如果 cacheObject 不为空,则根据 relativePath 创建对应的文件对象,并调用 exists() 方法判断文件是否存在。最后返回判断结果。

emptyCache

Future<void> emptyCache() async {
  final provider = await _cacheInfoRepository;
  final toRemove = <int>[];
  final allObjects = await provider.getAllObjects();
  for (final cacheObject in allObjects) {
    unawaited(_removeCachedFile(cacheObject, toRemove));
  }
  await provider.deleteAll(toRemove);
}

该函数实现了清空缓存的功能。它首先从 _cacheInfoRepository 获取所有缓存对象,然后遍历每个对象并调用 _removeCachedFile 函数将其从缓存中删除。最后,使用 await provider.deleteAll(toRemove) 从数据库中删除这些对象。

emptyMemoryCache

void emptyMemoryCache() {
  _memCache.clear();
}

该函数用于清空内存缓存,将_memCache清空。

dispose

Future<void> dispose() async {
  final provider = await _cacheInfoRepository;
  await provider.close();
}

这段代码定义了一个异步函数 dispose(),该函数的返回值是一个 Future<void> 对象,用于释放资源。

该函数首先等待 _cacheInfoRepository 的值就绪,然后调用 close() 方法关闭该对象。该方法通常用于释放资源,例如关闭数据库连接、清理缓存等。

WebHelper(网络下载)

class WebHelper {
  WebHelper(this._store, FileService? fileFetcher)
      : _memCache = {},
        fileFetcher = fileFetcher ?? HttpFileService();

  final CacheStore _store;
  @visibleForTesting
  final FileService fileFetcher;
  final Map<String, BehaviorSubject<FileResponse>> _memCache;
  final Queue<QueueItem> _queue = Queue();

定义了一个 WebHelper 类,其包含以下属性:

  • _store:一个 CacheStore 类型的私有属性,用于存储缓存数据。
  • fileFetcher:一个可选的 FileService 类型参数,用于从网络上获取文件。如果没有提供,则默认使用 HttpFileService
  • _memCache:一个以字符串为键,值为 BehaviorSubject<FileResponse> 对象的 Map。它用于在内存中缓存文件响应对象,以便能够更快地获取和使用它们。
  • _queue:一个 Queue<QueueItem> 类型的属性,用于在下载文件时存储待处理的请求。

该类的构造函数接受两个参数,第一个是 _store,第二个是可选的 fileFetcher。构造函数首先初始化了 _memCache_queue,并将 fileFetcher 设置为传入的参数或默认值。

downloadFile

///Download the file from the url
Stream<FileResponse> downloadFile(String url,
    {String? key,
    Map<String, String>? authHeaders,
    bool ignoreMemCache = false}) {
  key ??= url;
  var subject = _memCache[key];
  if (subject == null || ignoreMemCache) {
    subject = BehaviorSubject<FileResponse>();
    _memCache[key] = subject;
    unawaited(_downloadOrAddToQueue(url, key, authHeaders));
  }
  return subject.stream;
}

该方法用于下载文件并返回文件的流。如果请求相同的URL,则在_memCache中使用现有的BehaviorSubject<FileResponse>subject,以避免重复下载相同的文件。如果ignoreMemCachetrue,则会忽略缓存,并从服务器下载新文件。

如果没有现有的BehaviorSubject<FileResponse>,则会创建一个新的,将其添加到_memCache中,并在下载队列中将其添加到队列中,以便逐一处理下载请求。

参数说明:

  • url:文件的URL地址。
  • key:用于标识文件的字符串。如果未提供,则默认使用URL作为键。
  • authHeaders:包含身份验证标头的映射,以进行身份验证请求。默认为null
  • ignoreMemCache:布尔值,如果设置为true,则忽略缓存并从服务器下载新文件。默认为false

返回值:Stream<FileResponse>,一个包含下载文件的流。

_downloadOrAddToQueue

var concurrentCalls = 0;
Future<void> _downloadOrAddToQueue(
  String url,
  String key,
  Map<String, String>? authHeaders,
) async {
  //Add to queue if there are too many calls.
  if (concurrentCalls >= fileFetcher.concurrentFetches) {
    _queue.add(QueueItem(url, key, authHeaders));
    return;
  }
  cacheLogger.log(
      'CacheManager: Downloading $url', CacheManagerLogLevel.verbose);

  concurrentCalls++;
  var subject = _memCache[key]!;
  try {
    await for (var result
        in _updateFile(url, key, authHeaders: authHeaders)) {
      subject.add(result);
    }
  } catch (e, stackTrace) {
    subject.addError(e, stackTrace);
  } finally {
    concurrentCalls--;
    await subject.close();
    _memCache.remove(key);
    _checkQueue();
  }
}

_downloadOrAddToQueue方法实现了下载文件或将下载任务添加到队列的逻辑。它接收文件的URL、文件的键、身份验证标头(可选)作为参数。

该方法首先检查当前正在进行的下载任务数量是否已达到允许的最大并发下载数,如果是,则将下载任务添加到队列中。否则,它会递增正在进行的并发下载任务的数量,然后使用_updateFile方法从给定的URL下载文件。

该方法在下载完成后递减正在进行的并发下载任务的数量,并关闭和移除与文件键相关联的subject流。最后,它调用_checkQueue方法以检查队列中是否有等待的下载任务,并尝试开始下载它们。

_updateFile

///Download the file from the url
Stream<FileResponse> _updateFile(String url, String key,
    {Map<String, String>? authHeaders}) async* {
  var cacheObject = await _store.retrieveCacheData(key);
  cacheObject = cacheObject == null
      ? CacheObject(
          url,
          key: key,
          validTill: clock.now(),
          relativePath: '${const Uuid().v1()}.file',
        )
      : cacheObject.copyWith(url: url);
  final response = await _download(cacheObject, authHeaders);
  yield* _manageResponse(cacheObject, response);
}

_updateFile方法是WebHelper类中的一个私有方法,用于更新文件并返回文件响应的流。具体来说,该方法将检查缓存存储中是否存在与提供的密钥对应的缓存对象。如果存在,则更新缓存对象的URL字段。否则,创建一个新的缓存对象,并为其设置URL和相对路径等属性。

接下来,该方法会使用_download方法来下载文件。如果下载成功,则调用_manageResponse方法来处理文件响应并返回相应的流。最终,流中将包含文件的所有状态,包括开始下载、下载进度和下载完成等。

_download

Future<FileServiceResponse> _download(
    CacheObject cacheObject, Map<String, String>? authHeaders) {
  final headers = <String, String>{};
  if (authHeaders != null) {
    headers.addAll(authHeaders);
  }

  final etag = cacheObject.eTag;

  // Adding `if-none-match` header on web causes a CORS error.
  if (etag != null && !kIsWeb) {
    headers[HttpHeaders.ifNoneMatchHeader] = etag;
  }

  return fileFetcher.get(cacheObject.url, headers: headers);
}

这是 WebHelper 中的一个私有方法 _download,用于下载给定的 CacheObject。该方法的返回值是 Future<FileServiceResponse>

该方法首先创建一个空的字符串字典 headers,然后将 authHeaders 中的内容添加到其中。接下来,如果 cacheObject 中包含 ETag,就将其添加到 if-none-match 请求头中,以便服务器可以检查该文件是否已更新。需要注意的是,如果正在 Web 平台上运行代码,则不会添加 if-none-match 请求头,因为这会引发跨域错误。

最后,该方法调用了 fileFetcherget 方法,该方法执行实际的 HTTP GET 请求,以获取给定 URL 的文件,并返回一个 FileServiceResponse 对象作为结果。

_manageResponse

Stream<FileResponse> _manageResponse(
    CacheObject cacheObject, FileServiceResponse response) async* {
  final hasNewFile = statusCodesNewFile.contains(response.statusCode);
  final keepOldFile = statusCodesFileNotChanged.contains(response.statusCode);
  if (!hasNewFile && !keepOldFile) {
    throw HttpExceptionWithStatus(
      response.statusCode,
      'Invalid statusCode: ${response.statusCode}',
      uri: Uri.parse(cacheObject.url),
    );
  }

  final oldCacheObject = cacheObject;
  var newCacheObject = _setDataFromHeaders(cacheObject, response);
  if (statusCodesNewFile.contains(response.statusCode)) {
    var savedBytes = 0;
    await for (var progress in _saveFile(newCacheObject, response)) {
      savedBytes = progress;
      yield DownloadProgress(
          cacheObject.url, response.contentLength, progress);
    }
    newCacheObject = newCacheObject.copyWith(length: savedBytes);
  }

  unawaited(_store.putFile(newCacheObject).then((_) {
    if (newCacheObject.relativePath != oldCacheObject.relativePath) {
      _removeOldFile(oldCacheObject.relativePath);
    }
  }));

  final file = await _store.fileSystem.createFile(
    newCacheObject.relativePath,
  );
  yield FileInfo(
    file,
    FileSource.Online,
    newCacheObject.validTill,
    newCacheObject.url,
  );
}

该函数用于处理文件下载的响应,返回一个Stream。具体而言,它接受两个参数,一个CacheObject对象和一个FileServiceResponse对象,分别表示缓存对象和文件服务的响应。函数会根据响应中的状态码来决定如何处理文件。

如果响应状态码为新文件的状态码,则说明服务器返回了一个新的文件,函数会根据响应中的头部信息和文件服务的响应,将文件数据保存到缓存,并将FileInfo对象作为流的第一个值返回。

如果响应状态码表示文件未更改,则说明缓存中已存在该文件,函数会更新CacheObject对象中的头部信息,并将FileInfo对象作为流的第一个值返回。

如果响应状态码既不表示新文件,也不表示文件未更改,则会抛出一个HttpExceptionWithStatus异常,表示响应状态码无效。

最后,函数会异步将新的CacheObject对象保存到缓存中,并根据新的文件路径和旧的文件路径之间是否不同,决定是否删除旧文件。

_setDataFromHeaders

CacheObject _setDataFromHeaders(
    CacheObject cacheObject, FileServiceResponse response) {
  final fileExtension = response.fileExtension;
  var filePath = cacheObject.relativePath;

  if (!statusCodesFileNotChanged.contains(response.statusCode)) {
    if (!filePath.endsWith(fileExtension)) {
      //Delete old file directly when file extension changed
      unawaited(_removeOldFile(filePath));
    }
    // Store new file on different path
    filePath = '${const Uuid().v1()}$fileExtension';
  }
  return cacheObject.copyWith(
    relativePath: filePath,
    validTill: response.validTill,
    eTag: response.eTag,
  );
}

_setDataFromHeaders 方法会将从网络下载的文件的元数据信息更新到 CacheObject 对象中,并返回更新后的 CacheObject 对象。具体来说,该方法首先获取网络响应中的文件扩展名,并通过比较文件扩展名是否改变来决定是否删除旧文件并将新文件存储在不同的路径中。如果文件扩展名未改变,则直接使用旧的文件路径。然后,方法将 CacheObject 对象的路径、有效期和标签等信息更新为网络响应中的值,并返回更新后的 CacheObject 对象。

_saveFile

Stream<int> _saveFile(CacheObject cacheObject, FileServiceResponse response) {
  var receivedBytesResultController = StreamController<int>();
  unawaited(_saveFileAndPostUpdates(
    receivedBytesResultController,
    cacheObject,
    response,
  ));
  return receivedBytesResultController.stream;
}

该方法用于保存从服务器下载的文件,并返回进度更新的流(Stream)。它接收两个参数:缓存对象 cacheObject 和文件服务响应对象 response

该方法首先创建一个 StreamController 对象,用于向流中添加进度更新。然后,它异步地调用 _saveFileAndPostUpdates 方法,该方法在下载文件的同时将接收到的字节数添加到 receivedBytesResultController 流中。

最后,该方法返回 receivedBytesResultController 流。

_saveFileAndPostUpdates

Future _saveFileAndPostUpdates(
    StreamController<int> receivedBytesResultController,
    CacheObject cacheObject,
    FileServiceResponse response) async {
  final file = await _store.fileSystem.createFile(cacheObject.relativePath);

  try {
    var receivedBytes = 0;
    final sink = file.openWrite();
    await response.content.map((s) {
      receivedBytes += s.length;
      receivedBytesResultController.add(receivedBytes);
      return s;
    }).pipe(sink);
  } catch (e, stacktrace) {
    receivedBytesResultController.addError(e, stacktrace);
  }
  await receivedBytesResultController.close();
}

该函数是一个私有方法,它的作用是将从网络上下载的数据保存到本地文件系统中。函数的参数包括一个 StreamController 对象,它用于记录文件下载的进度;一个 CacheObject 对象,用于表示缓存对象;一个 FileServiceResponse 对象,它封装了下载的响应数据。

函数首先创建一个文件对象,然后通过 response.content 属性获取到一个 Stream 对象,这个对象包含了从网络上下载的数据。接着,函数通过调用 pipe() 方法,将 response.content 中的数据写入到文件对象中,同时还会累加已经写入的字节数,然后将这个值通过 receivedBytesResultController 对象发送出去,表示文件下载的进度。如果在下载的过程中发生了错误,函数将会通过 receivedBytesResultController 发送一个错误事件,并且关闭这个流。

_removeOldFile

Future<void> _removeOldFile(String? relativePath) async {
  if (relativePath == null) return;
  final file = await _store.fileSystem.createFile(relativePath);
  if (await file.exists()) {
    await file.delete();
  }
}

这是一个私有方法 _removeOldFile,它的作用是删除给定路径的文件。方法接受一个字符串类型的相对路径作为参数,如果路径为空则直接返回,否则会使用该路径在文件系统中创建一个文件对象,然后判断该文件是否存在,如果存在则删除它。最终方法返回一个 Future<void> 对象,表示删除操作的异步执行结果。

_checkQueue

void _checkQueue() {
  if (_queue.isEmpty) return;
  var next = _queue.removeFirst();
  _downloadOrAddToQueue(next.url, next.key, next.headers);
}

_checkQueue() 方法用于检查下载队列是否为空,如果队列不为空,则从队列中取出第一个缓存对象并调用 _downloadOrAddToQueue() 方法进行下载或重新添加到队列中。如果队列为空,则不进行任何操作。

具体来说,该方法实现了以下功能:

    1. 检查下载队列是否为空。
    1. 如果下载队列不为空,则获取队列中的第一个缓存对象。
    1. 调用 _downloadOrAddToQueue() 方法进行下载或重新添加到队列中。
    1. 如果下载队列为空,则不进行任何操作。

这个方法在下载完成后会被调用以检查是否有其他的文件需要下载,并根据情况将它们添加到队列中。

QueueItem

class QueueItem {
  final String url;
  final String key;
  final Map<String, String>? headers;

  QueueItem(this.url, this.key, this.headers);
}

队列对象

辅助方法

unawaited

unawaited 是一个函数,用于表示我们不需要等待一个异步操作的结果,这个异步操作可能会返回 Future<void> 或者 Future<dynamic> 类型,但我们并不关心它的结果。在 Dart 中,我们通常使用 await 关键字来等待一个异步操作的结果,但是在某些情况下,我们可能不希望等待异步操作的结果,而是直接执行下一步操作。这时就可以使用 unawaited 函数来避免等待。例如,在以下代码中,我们使用 unawaited 来避免等待 Future.delayed 的结果:

void main() { print('start'); unawaited(Future.delayed(Duration(seconds: 1)).then((_) => print('done'))); print('end'); }

在上面的代码中,Future.delayed 会在 1 秒后打印出 "done",但是我们并没有使用 await 等待它的结果,而是直接打印出了 "end"。