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