Flutter:如何从资产中异步读取文件而不阻塞UI

时间:2018-07-11 23:50:23

标签: asynchronous file-io dart flutter

在颤抖中,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;
}

3 个答案:

答案 0 :(得分:2)

在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;
}

答案 1 :(得分: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': /*.../*});

答案 2 :(得分:2)

因此,虽然@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;
}