Flutter:我如何 运行 dart 后台线程上的函数
Flutter: How can I run a function on background thread in dart
Future getImage() async {
var image = await ImagePicker.pickImage(source: ImageSource.camera);
setState(() {
_image = image;
print("IMG:" + _image.toString());
});
setPrefs() ;
}
Future setPrefs() async {
_base64 = base64.encode(_image.readAsBytesSync());
print(_base64);
final prefs = await SharedPreferences.getInstance();
prefs.setString(IMAGE_KEY, _base64);
}
readAsBytesSync()
方法在 Android 上运行良好,但在 iOS 上运行太慢。那么如何将此代码移动到新的后台线程?
1.使用未来
您可以使用 readAsBytes
的异步版本。
所以代替:
final imageData = _image.readAsBytesSync();
_base64 = base64.encode(imageData);
你可以:
final imageData = await _image.readAsBytes();
_base64 = base64.encode(imageData);
2。使用 Isolate
在您的代码中,不一定是 readAsBytes
行慢。它可能是图像的 base 64 编码。如果是这种情况,您可以将整个计算放入一个单独的隔离区。您可以使用一种方便的方法compute。
// This needs to be a global function
encodeImage(File imageFile) {
return base64.encodeimageFile.readAsBytesSync());
}
Future setPrefs() async {
_base64 = await compute(encodeImage, _image);
print(_base64);
final prefs = await SharedPreferences.getInstance();
prefs.setString(IMAGE_KEY, _base64);
}
此外,值得一提的是 SharedPreferences
(对于 Android,也就是 iOS 上的 NSUserDefaults
)旨在存储小型用户设置。它不是为存储可能有数兆字节的图像而设计的。最好将图像文件复制到应用程序的文档文件夹中,并且只将文件名存储在 SharedPreferences
.
由于 Flutter 是单线程的并且 运行 是一个事件循环(如 Node.js),您不必担心线程管理或产生后台线程。如果您正在做 I/O-bound 工作,例如磁盘访问或网络调用,那么您可以安全地使用 async/await
并完成。另一方面,如果您需要执行使 CPU 忙碌的计算密集型工作,您希望将其移至 Isolate
以避免阻塞事件循环。
对于 I/O-bound 工作,将函数声明为 async
函数,并且 await
在函数内部的 long-运行ning 任务上:
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = jsonDecode(response.body);
});
}
这是您通常进行网络或数据库调用的方式,它们都是 I/O 操作。
隔离是独立的执行线程,不与主执行内存堆共享任何内存。这意味着您无法从主线程访问变量,或通过调用 setState()
更新您的 UI。 Isolates 名副其实,不能共享内存(例如,以静态字段的形式)。
这里,dataLoader()
是运行在自己单独的执行线程中的Isolate
。在 isolate 中,您可以执行更多 CPU 密集处理(例如解析大 JSON),或执行计算密集型数学运算,例如加密或信号处理。
您可以运行下面的完整示例:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:isolate';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
}
showLoadingDialog() {
if (widgets.length == 0) {
return true;
}
return false;
}
getBody() {
if (showLoadingDialog()) {
return getProgressDialog();
} else {
return getListView();
}
}
getProgressDialog() {
return Center(child: CircularProgressIndicator());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: getBody());
}
ListView getListView() => ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
});
Widget getRow(int i) {
return Padding(
padding: EdgeInsets.all(10.0),
child: Text("Row ${widgets[i]["title"]}"),
);
}
loadData() async {
ReceivePort receivePort = ReceivePort();
await Isolate.spawn(dataLoader, receivePort.sendPort);
// The 'echo' isolate sends its SendPort as the first message
SendPort sendPort = await receivePort.first;
List msg = await sendReceive(
sendPort,
"https://jsonplaceholder.typicode.com/posts",
);
setState(() {
widgets = msg;
});
}
// the entry point for the isolate
static dataLoader(SendPort sendPort) async {
// Open the ReceivePort for incoming messages.
ReceivePort port = ReceivePort();
// Notify any other isolates what port this isolate listens to.
sendPort.send(port.sendPort);
await for (var msg in port) {
String data = msg[0];
SendPort replyTo = msg[1];
String dataURL = data;
http.Response response = await http.get(dataURL);
// Lots of JSON to parse
replyTo.send(jsonDecode(response.body));
}
}
Future sendReceive(SendPort port, msg) {
ReceivePort response = ReceivePort();
port.send([msg, response.sendPort]);
return response.first;
}
}
Future getImage() async {
var image = await ImagePicker.pickImage(source: ImageSource.camera);
setState(() {
_image = image;
print("IMG:" + _image.toString());
});
setPrefs() ;
}
Future setPrefs() async {
_base64 = base64.encode(_image.readAsBytesSync());
print(_base64);
final prefs = await SharedPreferences.getInstance();
prefs.setString(IMAGE_KEY, _base64);
}
readAsBytesSync()
方法在 Android 上运行良好,但在 iOS 上运行太慢。那么如何将此代码移动到新的后台线程?
1.使用未来
您可以使用 readAsBytes
的异步版本。
所以代替:
final imageData = _image.readAsBytesSync();
_base64 = base64.encode(imageData);
你可以:
final imageData = await _image.readAsBytes();
_base64 = base64.encode(imageData);
2。使用 Isolate
在您的代码中,不一定是 readAsBytes
行慢。它可能是图像的 base 64 编码。如果是这种情况,您可以将整个计算放入一个单独的隔离区。您可以使用一种方便的方法compute。
// This needs to be a global function
encodeImage(File imageFile) {
return base64.encodeimageFile.readAsBytesSync());
}
Future setPrefs() async {
_base64 = await compute(encodeImage, _image);
print(_base64);
final prefs = await SharedPreferences.getInstance();
prefs.setString(IMAGE_KEY, _base64);
}
此外,值得一提的是 SharedPreferences
(对于 Android,也就是 iOS 上的 NSUserDefaults
)旨在存储小型用户设置。它不是为存储可能有数兆字节的图像而设计的。最好将图像文件复制到应用程序的文档文件夹中,并且只将文件名存储在 SharedPreferences
.
由于 Flutter 是单线程的并且 运行 是一个事件循环(如 Node.js),您不必担心线程管理或产生后台线程。如果您正在做 I/O-bound 工作,例如磁盘访问或网络调用,那么您可以安全地使用 async/await
并完成。另一方面,如果您需要执行使 CPU 忙碌的计算密集型工作,您希望将其移至 Isolate
以避免阻塞事件循环。
对于 I/O-bound 工作,将函数声明为 async
函数,并且 await
在函数内部的 long-运行ning 任务上:
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = jsonDecode(response.body);
});
}
这是您通常进行网络或数据库调用的方式,它们都是 I/O 操作。
隔离是独立的执行线程,不与主执行内存堆共享任何内存。这意味着您无法从主线程访问变量,或通过调用 setState()
更新您的 UI。 Isolates 名副其实,不能共享内存(例如,以静态字段的形式)。
这里,dataLoader()
是运行在自己单独的执行线程中的Isolate
。在 isolate 中,您可以执行更多 CPU 密集处理(例如解析大 JSON),或执行计算密集型数学运算,例如加密或信号处理。
您可以运行下面的完整示例:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:isolate';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
}
showLoadingDialog() {
if (widgets.length == 0) {
return true;
}
return false;
}
getBody() {
if (showLoadingDialog()) {
return getProgressDialog();
} else {
return getListView();
}
}
getProgressDialog() {
return Center(child: CircularProgressIndicator());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: getBody());
}
ListView getListView() => ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
});
Widget getRow(int i) {
return Padding(
padding: EdgeInsets.all(10.0),
child: Text("Row ${widgets[i]["title"]}"),
);
}
loadData() async {
ReceivePort receivePort = ReceivePort();
await Isolate.spawn(dataLoader, receivePort.sendPort);
// The 'echo' isolate sends its SendPort as the first message
SendPort sendPort = await receivePort.first;
List msg = await sendReceive(
sendPort,
"https://jsonplaceholder.typicode.com/posts",
);
setState(() {
widgets = msg;
});
}
// the entry point for the isolate
static dataLoader(SendPort sendPort) async {
// Open the ReceivePort for incoming messages.
ReceivePort port = ReceivePort();
// Notify any other isolates what port this isolate listens to.
sendPort.send(port.sendPort);
await for (var msg in port) {
String data = msg[0];
SendPort replyTo = msg[1];
String dataURL = data;
http.Response response = await http.get(dataURL);
// Lots of JSON to parse
replyTo.send(jsonDecode(response.body));
}
}
Future sendReceive(SendPort port, msg) {
ReceivePort response = ReceivePort();
port.send([msg, response.sendPort]);
return response.first;
}
}