1. 「快速上手Flutter开发系列教程」之线程和异步UI

              Posted by 本的秘密博客 on March 16, 2019

              为大家倾力打造的课程《Flutter从入门到进阶-实战携程网App》上线了,点我Get!!!

              Flutter异步编程

              在这篇文章中,将向大家分享在Flutter中:

              这些Flutter开发的实用技能。

              怎么编写异步的代码?

              Dart有一个单线程执行模型,支持Isolate(一种在另一个线程上运行Dart代码的方法),一个事件循环和异步编程。除非你自己创建一个 Isolate ,否则你的 Dart 代码永远运行在主UI 线程,并由 event loop 驱动。Flutter 的 event loop 和 iOS 中的 main loop 相似:Looper 是附加在主线程上的。

              Dart 的单线程模型,并不意味着你写的代码一定要作为阻塞操作的方式运行,从而卡住 UI。相反,可以使用 Dart 语言提供的异步工具,例如 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);
                });
              }
              

              上面做法等价于Android中的runOnUiThread。 以上代码片段的完整部分可以在课程源码中查找。

              一旦 await 的网络请求完成,通过调用 setState() 来更新 UI,这会触发 widget 子树的重建,并更新相关数据。

              下面的例子展示了异步加载数据,并用 ListView 展示出来:

              loadData-async

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

              怎么把工作放到后台线程执行?

              由于 Flutter 是单线程并且跑着一个 event loop(就像 Node.js),因此你不必担心线程管理或生成后台线程。如果你正在做 I/O 操作,如访问磁盘或网络请求,可以安全地使用 async / await来完成。如果你需要做让 CPU 执行繁忙的计算密集型任务,你需要使用 Isolate 来避免阻塞 event loop。

              在Android中,当你想访问一个网络资源时,你通常会创建一个AsyncTask,当你需要一个耗时的后台任务时,你通常需要IntentService,在Flutter中则不需要这么繁琐。

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

              在Android中,当你继承AsyncTask时,通常会覆盖3个方法,OnPreExecute、doInBackground和onPostExecute。 在Flutter中没有这种模式的等价物,因为你只需await函数执行完成,而Dart的事件循环将负责其余的事情。

              以上就是对诸如网络请求、数据库访问等,I/O 操作的典型做法。

              然而,有时候你需要处理大量的数据,这会导致你的 UI 挂起。在 Flutter 中,使用 Isolate 来发挥多核心 CPU 的优势来处理那些长期运行或是计算密集型的任务。

              Isolate 是分离的运行线程,并且不和主线程的内存堆共享内存。这意味着你不能访问主线程中的变量,或者使用 setState() 来更新 UI。正如它们的名字一样,Isolate 不能共享内存。

              下面的例子展示了一个简单的Isolate是如何把数据返回给主线程来更新 UI 的:

              import 'dart:isolate';
              ...
              loadData() async {
                  // 打开ReceivePort以接收传入的消息
                  ReceivePort receivePort = ReceivePort();
                  //创建并生成与当前Isolate共享相同代码的Isolate
                  await Isolate.spawn(dataLoader, receivePort.sendPort);
              
                  // 流的第一个元素
                  SendPort sendPort = await receivePort.first;
                  // 流的第一个元素被收到后监听会关闭,所以需要新打开一个ReceivePort以接收传入的消息
                  ReceivePort response = ReceivePort();
                  //通过此发送端口向其对应的“ReceivePort”①发送异步[消息],这个“消息”指的是发送的参数②。
                  sendPort.send(
                      ["https://jsonplaceholder.typicode.com/posts", response.sendPort]);
                  // 获取端口发送来的数据③
                  List msg = await response.first;
              
                  setState(() {
                    widgets = msg;
                  });
                }
              
                // isolate的入口函数,该函数会在新的Isolate中调用,Isolate.spawn的message参数会作为调用它时的唯一参数
                static dataLoader(SendPort sendPort) async {
                  // 打开ReceivePort①以接收传入的消息
                  ReceivePort port = ReceivePort();
              
                  // 通知其他的isolates,本isolate 所监听的端口
                  sendPort.send(port.sendPort);
                  // 获取其他端口发送的异步消息 msg② -> ["https://jsonplaceholder.typicode.com/posts", response.sendPort]
                  await for (var msg in port) {
                    //等价于List msg= await port.first;
                    String data = msg[0];
                    SendPort replyTo = msg[1];
              
                    String dataURL = data;
                    http.Response response = await http.get(dataURL);
                    // 其对应的“ReceivePort”发送解析出来的JSON数据③
                    replyTo.send(json.decode(response.body));
                  }
                }
              

              以上代码片段的完整部分可以在课程源码中查找。

              这里,dataLoader() 是一个运行于自己独立执行线程上的 Isolate。在 Isolate 里,你可以执行 CPU 密集型任务(例如解析一个庞大的 json,解析json也是很耗时的哦),或是计算密集型的数学操作,如加密或信号处理等。

              你可以运行下面的完整例子:

              import 'dart:convert';
              import 'dart:isolate';
              
              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<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 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 = ReceivePort();
                  //创建并生成与当前Isolate共享相同代码的Isolate
                  await Isolate.spawn(dataLoader, receivePort.sendPort);
              
                  // 流的第一个元素
                  SendPort sendPort = await receivePort.first;
                  // 流的第一个元素被收到后监听会关闭,所以需要新打开一个ReceivePort以接收传入的消息
                  ReceivePort response = ReceivePort();
                  //通过此发送端口向其对应的“ReceivePort”①发送异步[消息],这个“消息”指的是发送的参数②。
                  sendPort.send(
                      ["https://jsonplaceholder.typicode.com/posts", response.sendPort]);
                  // 获取端口发送来的数据③
                  List msg = await response.first;
              
                  setState(() {
                    widgets = msg;
                  });
                }
              
                // isolate的入口函数,该函数会在新的Isolate中调用,Isolate.spawn的message参数会作为调用它时的唯一参数
                static dataLoader(SendPort sendPort) async {
                  // 打开ReceivePort①以接收传入的消息
                  ReceivePort port = ReceivePort();
              
                  // 通知其他的isolates,本isolate 所监听的端口
                  sendPort.send(port.sendPort);
                  // 获取其他端口发送的异步消息 msg② -> ["https://jsonplaceholder.typicode.com/posts", response.sendPort]
                  await for (var msg in port) {
                    //等价于List msg= await port.first;
                    String data = msg[0];
                    SendPort replyTo = msg[1];
              
                    String dataURL = data;
                    http.Response response = await http.get(dataURL);
                    // 其对应的“ReceivePort”发送解析出来的JSON数据③
                    replyTo.send(json.decode(response.body));
                  }
                }
              }
              

              关于Flutter的更多异步编程知识,可以学习《Flutter从入门到进阶-实战携程网App》

              如何进行网络请求?

              在 Flutter 中,使用流行的 http package 做网络请求非常简单。它把你可能需要自己做的网络请求操作抽象了出来,让发起请求变得简单。

              要使用 http 包,在 pubspec.yaml 中添加如下依赖:

              dependencies:
                ...
                http: ^0.12.0+1
              

              发起网络请求,在 http.get() 这个 async 方法中使用 await

              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更新其状态,setState将使用网络调用的结果更新UI。

              关于网络请求的更多内容和实战技巧可学习《基于Http实现网络操作》部分的课程。

              如何为长时间运行的任务添加一个进度指示器?

              • 在 iOS 中,在后台运行耗时任务时我们通常会使用 UIProgressView。
              • 在 Android 中,在后台运行耗时任务时我们通常会使用 ProgressBar。

              那么,在Flutter也有与之对应的widget叫ProgressIndicator。通过一个布尔 flag 来控制是否展示进度。在任务开始时,告诉 Flutter 更新状态,并在结束后隐藏。

              在下面的例子中,build 函数被拆分成三个函数。如果 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(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<SampleAppPage> {
                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);
                  });
                }
              }
              

              未完待续

              推荐学习资料



              16kou fushipifaw 1sysb 155217 293182 618632 229187 266793 782276 177152 598613 536683 658753 218325 896853 596113 675327 021sh6 278857 0551tt 0752cy 022ct ahcnjt 52lpw