【问题标题】:Flutter: How to read file from assets, asynchronously, without blocking the UIFlutter:如何在不阻塞 UI 的情况下异步地从资产中读取文件
【发布时间】:2018-12-20 02:10:25
【问题描述】:

在颤动中,rootBundle.load() 给了我一个ByteData 对象。

dart 中的 ByteData 对象到底是什么?可以用来异步读取文件吗?

我真的不明白这背后的动机。

为什么不给我一个好的File 对象,或者更好的是资产的完整路径?

就我而言,我想从资产文件中异步读取字节,逐字节地写入新文件。 (构建一个不会挂断 UI 的 XOR 解密)

这是我能做的最好的事情了,但它可悲地挂断了 UI。

loadEncryptedPdf(fileName, secretKey, cacheDir) async {
  final lenSecretKey = secretKey.length;

  final encryptedByteData = await rootBundle.load('assets/$fileName');

  final outputFilePath = cacheDir + '/' + fileName;
  final outputFile = File(outputFilePath);

  if (!await outputFile.exists()) {
    Stream decrypter() async* {
      // read bits from encryptedByteData, and stream the xor inverted bits

      for (var index = 0; index < encryptedByteData.lengthInBytes; index++)
        yield encryptedByteData.getUint8(index) ^
        secretKey.codeUnitAt(index % lenSecretKey);
      print('done!');
    }

    print('decrypting $fileName using $secretKey ..');
    await outputFile.openWrite(encoding: AsciiCodec()).addStream(decrypter());
    print('finished');
  }

  return outputFilePath;
}

【问题讨论】:

    标签: asynchronous file-io dart flutter


    【解决方案1】:

    因此,虽然 @Jonah Williams 和 @Richard Heap 发布的答案仅靠他们自己还不够,但我尝试了两者的结合,对我来说效果很好。

    这是一个完整的解决方案-

    使用path_provider包获取缓存目录

    import 'package:flutter/foundation.dart';
    import 'package:path_provider/path_provider.dart';
    
    // Holds a Future to a temporary cache directory
    final cacheDirFuture =
        (() async => (await (await getTemporaryDirectory()).createTemp()).path)();
    
    _xorDecryptIsolate(args) {
      Uint8List pdfBytes = args[0].buffer.asUint8List();
      String secretKey = args[1];
      File outputFile = args[2];
    
      int lenSecretKey = secretKey.length;
    
      for (int i = 0; i < pdfBytes.length; i++)
        pdfBytes[i] ^= secretKey.codeUnitAt(i % lenSecretKey);
    
      outputFile.writeAsBytesSync(pdfBytes, flush: true);
    
    }
    
    
    /// decrypt a file from assets using XOR,
    /// and return the path to a cached-temporary decrypted file.
    xorDecryptFromAssets(String assetFileName, String secretKey) async {
      final pdfBytesData = await rootBundle.load('assets/$assetFileName');
      final outputFilePath = (await pdfCacheDirFuture) + '/' + assetFileName;
      final outputFile = File(outputFilePath);
    
      if ((await outputFile.stat()).size > 0) {
        print('decrypting $assetFileName using $secretKey ..');
        await compute(_xorDecryptIsolate, [pdfBytesData, secretKey, outputFile]);
        print('done!');
      }
    
      return outputFilePath;
    }
    

    【讨论】:

      【解决方案2】:

      如果您正在做昂贵的工作并且不想阻塞 UI,请使用 package:flutter/foundation.dart 中的 compute 方法。这将在单独的隔离中运行提供的函数并将结果异步返回给您。 loadEncryptedPdf 必须是顶级或静态函数才能在此处使用,并且您只能传递一个参数(但您可以将它们放在 Map 中)。

      import 'package:flutter/foundation.dart';
      
      Future<String> loadEncryptedPdf(Map<String, String> arguments) async {
        // this runs on another isolate
        ...
      } 
      
      final String result = await compute(loadEncryptedPdf, {'fileName': /*.../*});
      

      【讨论】:

      • 这会导致 Exception: Platform messages can only be sent from the main isolate 无法使用 Isolates 从资产中读取文件:(
      【解决方案3】:

      在 Dart 中,ByteData 类似于 Java 中的 ByteBuffer。它包装了一个字节数组,为 1、2 和 4 字节整数(两种字节序)提供 getter 和 setter 函数。

      由于您要操作字节,因此最简单的方法是处理底层字节数组(Dart Uint8List)。 RootBundle.load() 已经将整个资产读入内存,所以在内存中更改并写出。

      Future<String> loadEncryptedPdf(
          String fileName, String secretKey, String cacheDir) async {
        final lenSecretKey = secretKey.length;
      
        final encryptedByteData = await rootBundle.load('assets/$fileName');
      
        String path = cacheDir + '/' + fileName;
        final outputFile = File(path);
      
        if (!await outputFile.exists()) {
          print('decrypting $fileName using $secretKey ..');
      
          Uint8List bytes = encryptedByteData.buffer.asUint8List();
          for (int i = 0; i < bytes.length; i++) {
            bytes[i] ^= secretKey.codeUnitAt(i % lenSecretKey);
          }
      
          await outputFile.writeAsBytes(bytes);
          print('finished');
        }
      
        return path;
      }
      

      【讨论】:

      • 我怎样才能获得资产中文件的文件路径?我尝试查看getApplicationDocumentsDirectory(),但它不存在。
      • 甚至尝试将其放入android/app/src/main/assets/,并使用File 对象和Android file:///android_asset/ url 进行访问,但这只是说文件不存在.. 看起来我唯一的选择是现在编写本机代码..
      • 请不要使用 outputFile.exists()。更多信息:dart-lang.github.io/linter/lints/avoid_slow_async_io.html.
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-27
      • 1970-01-01
      • 2021-06-10
      • 2018-12-13
      • 1970-01-01
      相关资源
      最近更新 更多