声明:阅读该文章请确保你有 Android 开发的相关知识
这是《Android 开发者的 Flutter》系列的第六篇,如果想看上一篇请参考:
Android 开发者的 Flutter(五) —— Flutter 中的 Intent 及处理
Flutter 中的 runOnUiThread()
Dart 有一个单线程执行模型,支持 Isolates 事件循环和异步编程(在另一个线程上运行 Dart 代码)。除非你生成一个 Isolate,否则你的 Dart 代码始终是在 UI 线程中运行,并由事件循环驱动。Flutter 的事件循环相当于 Android 的 main Looper—— 也就是说,Looper 是在主线程上执行。
Dart 的线程模型并不强制您将所有阻塞线程的操作都放入子线程中运行,但是那样就会导致 UI 线程冻结。不同于 Android 要求您始终保持主线程空闲,在Flutter中,您只需使用 Dart 语言提供的异步工具(如 async/ await)来执行异步工作。如果您已经在 C#,Javascript 中使用过它,或者您已经使用过 Kotlin 的协同程序,那么您可能熟悉 async/ await 范例。
例如,您可以通过使用 async/ await 运行网络代码而不会导致 UI 阻塞,让 Dart 完成繁重的工作:
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
一旦 await 网络调用完成,您将更新 UI 调用 setState(),这会触发重构构件树并更新数据。
接下来,这里是一个异步加载数据的例子,并将其显示在 ListView 中:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(new SampleApp());
}
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Sample App',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => new _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Sample App"),
),
body: new ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
}));
}
Widget getRow(int i) {
return new Padding(
padding: new EdgeInsets.all(10.0),
child: new Text("Row ${widgets[i]["title"]}")
);
}
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
}
有关在 Flutter 中进行后台工作的更多信息以及它与 Android 的不同之处,请参阅下一节。
如何将工作移至后台线程
在 Android 中,当您想要访问网络资源时,通常会转到后台线程并执行工作,以免阻塞主线程并避免 ANR。例如,您可能正在使用的 AsyncTask,一个 LiveData,一个 IntentService,一个 JobScheduler 工作,或与在后台线程工作调度的 RxJava。
由于 Flutter 是单线程的并且运行一个事件循环(如 Node.js),因此您不必担心线程管理或后台线程后缀。如果您正在执行 I / O 绑定工作,例如磁盘访问或网络调用,那么您可以安全地使用 async/ await 来运行准备好的代码。另一方面,如果您需要进行计算密集型工作以保持 CPU 繁忙,则您需要将其移至某个本地 Isolate,以避免阻塞事件循环,就像您在 Android 中希望将任何类型的工作保留在主线程之外。
对于 I / O 绑定的工作,您可以将函数声明为一个 async 函数,并 await 在函数中长时间运行任务:
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
经常使用的网络或数据库调用这两种都是 I / O 操作。
在 Android 上,当您继承 AsyncTask 时,通常会重写 3 个方法 onPreExecute(),doInBackground() 和 onPostExecute()。Flutter 中没有类似的方案,因为你只是等待一个长时间运行的函数,而 Dart 的事件循环会处理这些事情。
但是,有时您可能正在处理大量数据,并且您的 UI 可能会挂起。在这种情况下,就像在 Android 上一样,在 Flutter 中,可以利用多个 CPU 内核来执行长时间运行或计算密集型任务。这是通过使用 Isolates 完成的。
Isolates 是一个独立的执行线程,它运行时不会与主执行内存堆共享任何内存。这意味着你不能从主线程访问变量或通过调用来更新你的 UI setState()。Isolates 对他们的名字是真实的;与 Android 线程不同,Isolates 不能共享内存(例如,以静态字段的形式)。
我们来看一个简单的 Isolates 例子,以及如何将数据传递回主线程以更新UI。
loadData() async {
ReceivePort receivePort = new 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 = new 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(json.decode(response.body));
}
}
Future sendReceive(SendPort port, msg) {
ReceivePort response = new ReceivePort();
port.send([msg, response.sendPort]);
return response.first;
}
在这里,dataLoader() 是 Isolate 在它自己的独立执行线程中运行的。在此 Isolates 中,您可以执行更多的 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(new SampleApp());
}
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Sample App',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => new _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 new Center(child: new CircularProgressIndicator());
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Sample App"),
),
body: getBody());
}
ListView getListView() => new ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
});
Widget getRow(int i) {
return new Padding(padding: new EdgeInsets.all(10.0),
child: new Text("Row ${widgets[i]["title"]}"));
}
loadData() async {
ReceivePort receivePort = new 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 = new 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(json.decode(response.body));
}
}
Future sendReceive(SendPort port, msg) {
ReceivePort response = new ReceivePort();
port.send([msg, response.sendPort]);
return response.first;
}
}
Flutter 中的 OkHttp
在使用流行的 http 软件包时,使用 Flutter 进行网络通信非常简单。
虽然 http 包没有 OkHttp 已经实现的所有功能,但它可以抽象出很多通常自己实现的网络,使其成为进行网络调用的一种简单方法。
您可以通过将其添加到您的依赖关系中来使用它 pubspec.yaml:
dependencies:
...
http: '>=0.11.3+16'
然后,进行网络通信,您只需 await 中执行以下 async 功能 http.get():
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
[...]
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
}
如何在 Flutter 中显示长时间任务的进度
在 Android 中,ProgressBar 当您在后台线程上执行长时间运行的任务时,您通常会在 UI 中显示视图。
在 Flutter 中,这可以通过使用 ProgressIndicator 小部件来完成。您可以通过编程方式显示进度 UI,方法是通过布尔型标志在控制呈现到进程 UI,并告诉 Flutter 在长时间运行任务开始之前更新其状态,并在结束之后隐藏它。
在下面的例子中,我们将构造函数分解为三个不同的函数。如果 showLoadingDialog() 是 true(当 widgets.length == 0)然后我们渲染 ProgressIndicator,否则我们在 ListView 中用数据显示。
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(new SampleApp());
}
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Sample App',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => new _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
}
showLoadingDialog() {
return widgets.length == 0;
}
getBody() {
if (showLoadingDialog()) {
return getProgressDialog();
} else {
return getListView();
}
}
getProgressDialog() {
return new Center(child: new CircularProgressIndicator());
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Sample App"),
),
body: getBody());
}
ListView getListView() => new ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
});
Widget getRow(int i) {
return new Padding(padding: new EdgeInsets.all(10.0),
child: new Text("Row ${widgets[i]["title"]}"));
}
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
}