【问题标题】:How to deploy initial Data to Flutter App如何将初始数据部署到 Flutter App
【发布时间】:2021-10-14 18:09:25
【问题描述】:

我遇到了以下问题: 我们正在编写一个库应用程序,其中包含一堆内部文档(大约 900 个)。 所有文档的总大小约为 2,5+ GB。 我必须找到一种方法,如何在首次启动时使用 2.5GB 的 Docs 初始化应用程序。 我的想法是创建一个初始加载的 zip 文件,从服务器下载并在首次启动时在安装过程中解压缩。但是,我没有找到解压缩如此大文件的方法,因为所有解决方案都先(完全)将 zip 读取到内存,然后再将其写入存储。 我也不想在第一次启动时对我们的网络服务器进行 900 多次调用来下载文档。

部署目标是iOS和Android,以后可能是win+mac。

有什么想法吗?

【问题讨论】:

  • 简单方式:使用TarFile(缺点:未压缩的文件很大),困难方式:使用ZipFile(缺点:您必须编写一个基于InputStream的自定义文件-我不知道是否甚至有可能)-所有这些都在 std archive_io 包中
  • 感谢您的回答!我认为存档包将是我的解决方案,但是在解压缩存档时,它总是会将整个内容读取到内存中以构建存档对象。只有这样它才会开始将存档从内存写入磁盘。所以我最终会在内存中使用 2.5GB 存档对象。 来自 extract_archive_to_disk.dart Archive archive; if (archivePath.endsWith('tar')) { final input = InputFileStream(archivePath); archive = TarDecoder().decodeBuffer(input);
  • 使用 tar 文件你可以这样做:final input = InputFileStream('backup.tar'); while(!input.isEOS) { final tf = TarFile.read(input); if (tf.filename.isEmpty) break; print("[${tf.filename}] content.length: ${tf.content.length}"); } 但 tar 没有压缩,所以处理 zip 会好得多

标签: flutter installation zip archive


【解决方案1】:

我在 Flutter linux 上对其进行了测试,其中Uri.base 指向项目的根目录(带有pubspec.yamlREADME.md 等的文件夹)-如果您在android / iOS 上运行它,请检查Uri.base 指向的位置和位置不好就换baseUri

final debug = true;
final Set<Uri> foldersCreated = {};
final baseUri = Uri.base;
final baseUriLength = baseUri.toString().length;
final zipFileUri = baseUri.resolve('inputFolder/backup.zip');

final outputFolderUri = baseUri.resolve('outputFolder/');
print('0. files will be stored in [$outputFolderUri]');
final list = FileList(zipFileUri, debug: debug);
print('1. reading ZipDirectory...');
final directory = ZipDirectory.read(InputStream(list));
print('2. iterating over ZipDirectory file headers...');
for (final zfh in directory.fileHeaders) {
  final zf = zfh.file;
  final content = zf.content;

  // writing file
  final uri = outputFolderUri.resolve(zf.filename);
  final folderUri = uri.resolve('.');
  if (foldersCreated.add(folderUri)) {
    if (debug) print(' #### creating folder [${folderUri.toString().substring(baseUriLength)}] #### ');
    Directory.fromUri(folderUri).createSync(recursive: true);
  }
  File.fromUri(uri).writeAsBytesSync(content);

  print("file: [${zf.filename}], compressed: ${zf.compressedSize}, uncompressed: ${zf.uncompressedSize}, length: ${content.length}");
}
list.close();
print('3. all done!');

这里有一个List,由一个LruMap 支持,它从你的巨大压缩文件中读取数据块:

class FileList with ListMixin<int> {
  RandomAccessFile _file;
  LruMap<int, List<int>> _cache;
  final int maximumPages;
  final int pageSize;
  final bool debug;

  FileList(Uri uri, {
    this.pageSize = 1024, // 1024 is just for tests: make it bigger (1024 * 1024 for example) for normal use
    this.maximumPages = 4, // maybe even 2 is good enough?
    this.debug = false,
  }) {
    _file = File.fromUri(uri).openSync();
    length = _file.lengthSync();
    _cache = LruMap(maximumSize: maximumPages);
  }

  void close() => _file.closeSync();

  @override
  int length;

  int minIndex = -1;
  int maxIndex = -1;
  List<int> page;
  @override
  int operator [](int index) {
    // print(index);

    // 1st cache level
    if (index >= minIndex && index < maxIndex) {
      return page[index - minIndex];
    }

    // 2nd cache level
    int key = index ~/ pageSize;
    final pagePosition = key * pageSize;
    page = _cache.putIfAbsent(key, () {
      if (debug) print(' #### reading page #$key (position $pagePosition) #### ');
      _file.setPositionSync(pagePosition);
      return _file.readSync(pageSize);
    });
    minIndex = pagePosition;
    maxIndex = pagePosition + pageSize;
    return page[index - pagePosition];
  }

  @override
  void operator []=(int index, int value) => null;
}

您可以使用pageSizemaximumPages 来找到最佳解决方案 - 我认为您可以从pageSize: 1024 * 1024maximumPages: 4 开始,但您必须自己检查它

当然,所有这些代码都应该在 Isolate 中运行,因为解压缩几个 GB 需要很长时间,然后您的 UI 会冻结,但首先按原样运行它并查看日志

编辑

似乎ZipFile.content 有一些内存泄漏,因此替代方案可能是基于“tar 文件”的解决方案,它使用tar 包,并且由于它读取Stream 作为输入,您可以使用压缩的*.tar.gz文件(您的 Documents.tar 有 17408 字节,而 Documents.tar.gz 有 993 字节),请注意,您甚至可以直接从套接字的 stream 读取数据,因此不需要任何中间 .tar.gz 文件:

final baseUri = Uri.base;
final tarFileUri = baseUri.resolve('inputFolder/Documents.tar.gz');

final outputFolderUri = baseUri.resolve('outputFolder/');
print('0. files will be stored in [$outputFolderUri]');
final stream = File.fromUri(tarFileUri)
  .openRead()
  .transform(gzip.decoder);
final reader = TarReader(stream);
print('1. iterating over tar stream...');

while (await reader.moveNext()) {
  final entry = reader.current;
  if (entry.type == TypeFlag.dir) {
    print("dir: [${entry.name}]");
    final folderUri = outputFolderUri.resolve(entry.name);
    await Directory.fromUri(folderUri).create(recursive: true);
  }
  if (entry.type == TypeFlag.reg) {
    print("file: [${entry.name}], size: ${entry.size}");
    final uri = outputFolderUri.resolve(entry.name);
    await entry.contents.pipe(File.fromUri(uri).openWrite());
  }
}
print('2. all done!');

【讨论】:

  • 谢谢,这似乎主要工作。但是我看到应用程序的内存占用量在不断增加。我刚刚在我的 iPhone 上尝试过,内存不足。我对 null 安全性做了一些小调整,但我认为它们不会造成泄漏。
  • 我已降到pageSize 512 和maximumPages 1
  • 您的 zip 文件中有多少个文件,您发现内存增加了多少?它或多或少是所有未压缩文件的总和?
  • 大约 1000 个文件,提取大小为 2.6GB。当我停止它时,macOS 上的内存达到了 3.5GB 以上
  • 我觉得设置并不重要:我目前正在尝试这个:this.pageSize = 1048576, this.maximumPages = 4 现在我的内存达到 3GB 左右
猜你喜欢
  • 1970-01-01
  • 2011-03-01
  • 2021-01-06
  • 1970-01-01
  • 2013-03-23
  • 2016-06-30
  • 2020-11-22
  • 2016-09-20
  • 1970-01-01
相关资源
最近更新 更多