Android(安卓)开发者的 Flutter(六) —— Flutter 中的异步 UI
声明:阅读该文章请确保你有 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 isolatestatic 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); }); }}
更多相关文章
- android进行异步更新UI的四种方式
- android Handler,Looper,Message三者关系
- Android(安卓)性能典范:拯救计划
- SurfaceView
- Android开发: 线程间消息通信 Looper 和Handler
- android的surfaceview的用法
- Android面试知识点总结-Android篇
- Android中生成和扫描二维码
- Android(安卓)Handler 异步消息处理机制的妙用 创建强大的图片加