喜欢我的小伙伴,一定要关注我的微信公众号啊!!!谢谢啦
AllAboutCoding
此文章为翻译Flutter官网的Flutter for Android Developers - Async UI有兴趣的小伙伴可以移步官网查看。
Flutter 中的runOnUiThread()是什么?
Dart具有单线程执行模型,支持隔离(Isolate,一个在其他线程跑Dart代码的方法)、事件循环、异步编程。除非你派生Isolate,你的Dart代码将运行在主线程并且被事件循环驱动。Flutter中的事件循环等价于Android中的主Looper,就是依附于主线程的Looper。
Dart的单线程执行模型并不是意味着你运行任何代码都会导致UI卡顿的阻断操作。Android随时都需要保持主线程处于空闲状态,在Flutter不像Android那样,Flutter使用Dart语言提供的异步工具(例如async/await),来执行异步工作。如果你使用过C#,JavaScript的范式或者使用过Kotlin的协同程序(coroutines)的话,你会熟悉async/await的范式。
例如,你可以通过使用async/await并且让Dart做耗时操作运行请求网络的代码而不导致UI挂起:
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
一旦await网络请求处理完毕,通过调用setState()
更新UI,这将触发重建Widget的子树并且更新数据。下面的例子异步更新数据并展示在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 {
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是单线程的并且有一个事件循环(像Note.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中没有等价的方法,自从你await在一个耗时方法上,Dart事件循环就会负责剩下的部分。
然而,会有几次当你处理大量的数据会导致你的UI挂起。在Flutter中,使用Isolate来发挥多核心CPU的优点去做耗时密集型计算任务。
分离(Isolates)是单独的,不与主线程的执行内存堆共享任何内存的执行线程。这就意味着你不可以在主线程访问变量,或者通过调用setState ()
更新UI。不同于Android的线程,分离是以它们的名字真实隔离,并且不共享内存(例如静态变量的形式)。
下面的代码展示了在一个简单的分离中如何分享数据返回给主线程来更新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 ()
就是分离,运行在它自己单独的执行线程中。在分离中,你可以执行更多的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 {
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 package时,在Flutter的网络请求会很简单。
然而,这个http package并没有OkHttp的所有功能,它抽象出很多你通常会自己实现的网络,让网络请求变得简单。
把它加在你的依赖文件pubspec.yaml中,来使用http package。
dependencies:
...
http: ^0.11.3+16
调用http.get()
带await在async方法中,来做网络请求:
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);
});
}
}
耗时任务时如何显示进度?
在Android中,在后台执行耗时任务时,你通常会显示一个ProgressBar在你的UI上。
在Flutter中,使用ProgressIndicatorWidget。通过控制一个Boolean标志来以编程的方式判断何时显示进度。在耗时任务开始之前告诉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 {
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);
});
}
}