Flutter:如何在不阻塞 UI 的情况下异步地从资产中读取文件

Posted

技术标签:

【中文标题】Flutter:如何在不阻塞 UI 的情况下异步地从资产中读取文件【英文标题】:Flutter: How to read file from assets, asynchronously, without blocking the 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;

【问题讨论】:

【参考方案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.

以上是关于Flutter:如何在不阻塞 UI 的情况下异步地从资产中读取文件的主要内容,如果未能解决你的问题,请参考以下文章

如何在不阻塞 UI 的情况下暂停 Java 指定时间? [复制]

如何在不阻塞 UI 的情况下正确使用 MagicalRecord 从 Web 服务导入数据

如何使用 ROOM 库在不阻塞 UI 的情况下将数千条记录从本地数据库加载到 recyclerview

Flutter - 如何在不阻止UI的情况下计算包含未来的繁重任务?

在不阻塞 UI 执行的情况下等待几秒钟

如何在不冻结 GUI 的情况下在单个插槽中实现阻塞进程?