[译]Flutter for Android Developers - Async UI

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);
});
}
}

界面展示的部分与FFAD-Views中介绍的StatefulWidget的展示没有太大区别,我们声明的loadData异步方法在_SampleAppPageState的initState方法中调用,于是触发异步加载数据,await表达式挂起整个异步操作,直到http.get(dataURL)返回时通过setState更新widgets成员变量,进而触发build方法重新调用以更新ListView中的item。

通过协程实现的异步方法通常能够帮助我们在main isolate去执行一些耗时操作并且不会阻塞界面更新。但是有时候我们需要处理大量的数据,就算我们将该操作声明为异步方法依然可能会导致阻塞界面更新,因为通过协程来实现的异步方法说到底还是运行于一个线程之上,在一个线程上去调度执行毕竟算力有限。

这时候我们可以利用多核CPU的优势去完成这些耗时的或CPU密集型的操作。这正是通过前面介绍的isolate来实现。下面的例子展示了如何创建一个isolate,并且如何在创建的isolate和main isolate之间通信来将数据传递回main isolate进而更新界面:

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;
}

简单解释一下,这段代码主要声明了三个方法。

loadData被声明为一个异步方法,其内部的代码运行于main isolate中。该方法首先声明了一个用于main isolate从其他isolate接受消息的ReceivePort。接着通过spawn命名构造方法生成了一个isolate,为了后续描述简单这里姑且叫它x isolate。该isolate将会以构造时传入的第一个参数dataLoader方法作为运行的入口函数。即生成x isolate后,在x isolate中会开始执行dataLoader方法。构造x isolate时传入的第二个参数是通过main isolate中的ReceivePort获得的一个SendPort,这个SendPort会在dataLoader被执行时传递给它。在x isolate中可以用该SendPort向main isolate发送消息进行通信。 接下来通过receivePort.first获取x isolate发送过来的消息,这里获取到的其实是一个x isolate的SendPort对象,在main isolate中可以利用这个SendPort对象向x isolate中发送消息。 接下来调用sendReceive方法并传入刚刚获得的x isolate的SendPort对象和一个字符串作为参数。 最后调用setState方法触发界面更新。

dataLoader也被声明为一个异步方法,其内部的代码运行于x isolate中。在构建了x isolate后该方法开始在x isolate中执行,要注意的是dataLoader方法的参数是一个SendPort类型的对象,这正是前面构造x isolate时传入的第二个参数,也就是说,前面通过Isolate.spawn命名构造方法构造一个isolate时,传入的第二个参数的用途就是将其传递给第一个参数所表示的入口函数。在这里该参数表示的是main isolate对应的SendPort,通过它就可以在x isolate中向main isolate发送消息。 在dataLoader方法中首先生成了一个x isolate的ReceivePort对象,然后就用main isolate对应的SendPort向main isolate发送了一个消息,该消息其实就是x isolate对应的SendPort对象,所以回过头去看loadData方法中通过receivePort.first获取到的一个SendPort就是这里发送出去的。在main isolate中接收到这个SendPort后,就可以利用该SendPort向x isolate发送消息了。 接下来dataLoader方法则挂起等待x isolate的ReceivePort接受到消息。

sendReceive被声明为一个普通方法,该方法运行于main isolate中,它是在loadData中被调用的。调用sendReceive时传入的第一个参数就是在main isolate中从x isolate接收到的其对应的SendPort对象,所以在sendReceive方法中利用x isolate对应的这个SendPort对象就可以在main isolate中向x isolate发送消息。在这里发送的消息是一个数组[msg, response.sendPort]。消息发送后在dataLoader方法中await挂起的代码就会开始唤醒继续执行,取出传递过来的参数,于是在x isolate中开始执行网络请求的逻辑。 接着将请求结果再通过main isolate对应的SendPort传递给main isolate。于是在sendReceive方法中通过response.first获取到x isolate传递过来的网络请求结果。 最终在setState方法中使用网络请求回来的结果更新数据集触发界面更新。

完整的例子代码:

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 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;
}

}

小结:

  1. 在Flutter中一般情况下不需要runOnUiThread,AsyncTask,IntentService等类似的概念,因为Dart是基于单线程模型的。异步方法的执行也是通过协程实现的,其实际也还是运行于main isolate中。
  2. Dart中的代码都是运行在isolate中的,各个isolate之间的内存是没法直接共享的。但是可以通过ReceivePort和SendPort来实现isolate之间的通信。每个isolate都有自己对应的ReceivePort和SendPort,ReceivePort用于接受其他isolate发送过来的消息,SendPort则用于向其他isolate发送消息。关于ReceivePort和SendPort更多详情可以参阅官方文档

在Flutter中OkHttp等价于什么

  • in Android

  • 我们有很多类似OkHttp之类的网络库使用。

  • in Flutter

  • 我们使用http package来简单的完成一个网络请求调用。

虽然http package没有实现OkHttp已经实现的所有功能,但是它实现了很多常用的网络请求功能,帮助我们更简单的完成一个网络请求调用。关于http package的更多信息可以参阅官方文档。 在使用http package之前我们需要先在pubspec.yaml文件中配置依赖:

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);
});
}
}

代码很简单,其实前面的分析中我们就已经看到了http package的身影。在这里是直接调用http.get(dataURL)方法发起一个get请求,参数是一个url,该方法返回的是一个Future<http.Response>类型,所以最终整个await表达式返回的就是一个http.Response类型。一旦请求完成获取到了数据我们就可以调用setState方法来触发系统更新界面。

小结:

在Flutter中我们使用http package来帮助我们更简单的实现网络请求调用。

在Flutter中怎样在一个任务正在运行时显示一个Loading Dialog

  • in Android

  • 我们可以在运行一个耗时任务时展示一个Loading Dialog,可以使用Dialog或者其他自定义的View来实现。

  • in Flutter

  • 我们可以用一个Progress Indicator Widget来实现一个Loading Dialog。

下面我们可以看一个例子:

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);

总结

本文讲解了我对Android开发现状的一些看法,也许有些人会觉得我的观点不对,但我认为没有绝对的对与错,一切交给时间去证明吧!愿与各位坚守的同胞们互相学习,共同进步!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 29
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值