文档地址:https://flutter.io/docs/get-started/flutter-for/android-devs
译者:Haocxx
Dart有一个单线程执行模型,支持Isolate(一种在其他线程运行Dart代码的方式),事件循环和异步编程。Dart代码在主线程上运行并由事件循环驱动,除非使用Isolate。Flutter的事件循环相当于是Android的主Looper。换句话说,这个Looper是与主线程绑定的。
Dart的单线程模型并不意味着你要连阻塞UI的耗时操作都在里面同步运行。不像Android要求主线程一直处于空闲状态,在Flutter中,可以使用Dart语言提供的异步执行工具来实现异步操作,比如async/await。如果你曾经在C#/JavaScript使用过它或者使用过Kotlin的协程(Coroutine),就会对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(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 {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: 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 {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
}
下面一小节会告诉你如何完成后台线程执行任务,以及与Android的区别。
在Android中,如果想要获取网络资源的话,通常会将其放到后台线程中运行以防止阻塞UI线程,避免ANR。比如,可以使用AsyncTask,LiveData,IntentService,JobScheduler或者RxJava来实现后台线程执行。
由于Flutter是单线程的,并且由事件循环驱动(像Node.js),你不必担心线程管理和线程的创建。如果你要进行IO操作,比如读取磁盘和网络数据请求,只需要使用async和await就可以了。如果你需要进行密集的运算,就需要将其放到Isolate中避免阻塞事件循环,就像在Android中避免耗时操作阻塞UI线程一样。
对于IO操作,只需要讲操作方法声明为async方法,并将耗时的操作用await修饰:
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
这就是通常进行网络或数据库操作的方法,两者都属于IO操作。
在Android中,当继承AsyncTask的时候,通常要复写三个方法,onPreExecute(),doInBackground()和onPostExecute()。在Flutter中没有这样的用法。你只要用await修饰耗时的操作,Dart的事件循环就会自己处理。
然而,有的时候可能要处理非常巨大的数据。在Flutter中,就可以使用Isolate来利用多核运算的优势来完成密集的耗时运算。
Isolate是独立的执行线程,不会与主执行线程共享内存栈,也就无法获取主线程中的变量,或调用setState()来更新UI。不像Android的线程,Isolate名副其实(Isolate意为“隔离”),无法共享内存。
下面是实现Isolate中获取的数据交给主线程并更新UI的方法:
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(json.decode(response.body));
}
}
Future sendReceive(SendPort port, msg) {
ReceivePort response = ReceivePort();
port.send([msg, response.sendPort]);
return response.first;
}
其中,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 {
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(json.decode(response.body));
}
}
Future sendReceive(SendPort port, msg) {
ReceivePort response = ReceivePort();
port.send([msg, response.sendPort]);
return response.first;
}
}
在Flutter中,如果使用http库,做网络数据请求是非常容易的。
虽然http库没有实现OkHttp中所有的feature功能,但是它封装了很多常用的网络方法,使网络请求变得简单。
要使用http库,需要在pubspec.yaml中添加如下依赖:
dependencies:
...
http: ^0.11.3+16
进行网络数据请求,可以使用await修饰的异步方法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);
});
}
}
在Android中,通常使用ProgressBar来显示后台进程在执行加载。
在Flutter中,可以使用ProgressIndicator。在渲染Widget的时候,通过boolean值来控制ProgressIndicator的显示状态,并在后台线程执行的开始时候通知其显示,结束的时候控制其隐藏。
在下面的例子中,build方法被分为三个方法。如果showLoadingDialog()为true,就显示ProgressIndicator。否则就显示获取到网络数据的ListView。
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
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 {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
}
showLoadingDialog() {
return widgets.length == 0;
}
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 {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
}