Flutter与android的对比---UI 中的异步
本文是在GitHub上一个flutter项目的资料中看到的,由于原文过于太长,因此对其进行了章节拆分方便阅读,此篇为原文的部分内容,后续内容会在今后陆续更新完;如果想查看该项目请跳转GitHub查看。
注:GitHub的这篇资料是转载的flutter中文官网的文章,这里是原文地址。
UI 中的异步
- runOnUiThread 在 Flutter 中对应什么方法?
- Android 中的 AsyncTask 或 IntentService 在 Flutter 对应什么?
- Android 中的 OkHttp 在 Flutter 中对应什么?
- 在 Flutter 中,当有任务在执行时,如何显示进度?
runOnUiThread 在 Flutter 中对应什么方法?
Dart 是单线程执行模型、支持 Isolate(一种多线程模型)、事件循环和异步编程的。除非使用 Isolate,不然你的 Dart 代码都是在 UI 线程中进行并由事件循环器进行驱动。
例如你可以在 UI 线程执行网络请求而不会导致 UI 线程的阻塞:
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = JSON.decode(response.body);
});
}
通过调用 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);
});
}
}
Android 中的 AsyncTask
或 IntentService
在 Flutter 对应什么?
在 Android 中进行网络操作时通常会使用 AsyncTask,以避免主线程遭到阻塞。与此同时 AsyncTask 会有一个线程池专门为你管理线程。
由于 Flutter 是单线程并由事件驱动
(类似 Node.js),因此你不必为线程管理或需要类似的 AsyncTask 和 IntentService 而感到担忧。
需要异步执行的时候只要将方法声明为异步方法
并在方法中使用 await
来等待即可。
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = JSON.decode(response.body);
});
}
以上就是你通常进行网络或数据库操作的方式。
在 Android 中,当继承 AsyncTask 的时候,通常要重载它的 3 个方法,OnPreExecute, doInBackground 和 onPostExecute
。而在 Flutter 中没有这种麻烦事,你要做的仅仅是 await
一个长时间的操作,剩下的事 Dart 的事件循环机制
会帮你搞定。
但是,有时你可能会处理一些数据量较大较密集的操作,Flutter 的 UI 还是可能会受到影响。
在这种情况下,Flutter 中还是有和 AsyncTask 类似的解决方案。在 Flutter 中可以利用 CPU 多核的性质来并行处理事务
,而这一工作则是由 Isolate
完成。
Isolate 是独立的执行线程,和主线程不共享任何内存。这意味着你不能在 Isolate 中给主线程的变量赋值或者调用 setState 方法来更新 UI。
让我们看一个 Isolate
的简单例子,学习下 Isolate 如何与主线程交流并共享数据来更新 UI
:
loadData() async {
ReceivePort receivePort = new ReceivePort();
await Isolate.spawn(dataLoader, receivePort.sendPort);
// The 'echo' isolate sends it's 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
中运行,你可以在其中执行更多的 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 it's 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;
}
}
Android 中的 OkHttp 在 Flutter 中对应什么?
在 Flutter 中使用http
扩展库将使网络通信变得异常简单。
虽然 http 扩展库没有实现 OkHttp 的所有功能,但其抽象出了很多常用的功能,使得原本你要自己实现的网络调用变成一些极为简单的方法。
可以去pub上面下载,链接:https://pub.dartlang.org/packages/http
你也可以在 pubspec.yaml 添加 http 包的依赖来使用它:
dependencies:
...
http: '>=0.11.3+12'
接着就可以进行网络请求了,如下:
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);
});
}
}
一旦得到了需要的数据,就可以通过调用 setState
方法通知 Flutter 将网络调用的结果更新到 UI 上。
在 Flutter 中,当有任务在执行时,如何显示进度?
在 Android 中,当你执行长时间的任务时,会在界面上显示一个进度指示器表明当前有任务在执行。
在 Flutter 中可以通过进度指示器Widget
来实现。你可以通过一个 boolean 值来告诉 Flutter 是否需要显示进度指示器。
在下面这个例子中,我们将 build 方法分解为三个不同的方法。如果 showLoadingDialog 为 true 时显示进度指示器,否则将显示带有数据的 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() {
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 {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = JSON.decode(response.body);
});
}
}