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 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 在它自己的独立执行线程中运行的。在此 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);
    });
  }
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值