美文网首页
Flutter入门之Async UI

Flutter入门之Async UI

作者: 蜗牛是不是牛 | 来源:发表于2023-01-04 20:40 被阅读0次

Flutter中的runOnUiThread()相当于什么?

Dart具有单线程执行模型,支持Isolate(一种在另一个线程上运行Dart代码的方法)、事件循环和异步线程。除非你生成一个Isolate,否则您的Dart代码将在主UI线程中运行并由事件循环驱动。Flutter的event loop相当于Android的main Looper——依附于主线程的Looper

Dart的单线程模型并不意味着你需要将所有内容作为导致UI冻结的阻塞操作来运行。与Android要求你始终保持主线程空闲不同,在Flutter中,使用Dart语言提供的异步,例如async/await,来执行异步工作。async,如果用过C#、Javascript中使用过await,或者您使用过Kotlin的协程,您可能会熟悉它。

例如,你可以通过使用async/await并让Dart完成繁重的工作来运行网络代码而不会导致UI挂起:

Future<void> loadData() assync {
    var dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
    http.Response response = await http.get(dataURL);
    setState(() {
        widgets = jsonDecode(response.body);
    });
}

await网络调用完成后,通过调用更新UIsetState()这会触发小部件树的重建并更新数据。

以下示例异步加载数据并显示在ListView

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(const SampleApp());
}

class SampleApp extends StatelessWidget {
  const SampleApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  @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: const Text('Sample App'),
      ),
      body: ListView.builder(
        itemCount: widgets.length,
        itemBuilder: (context, position) {
          return getRow(position);
        },
      ),
    );
  }

  Future<void> loadData() async {
    var dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
    http.Response response = await http.get(dataURL);
    setState(() {
      widgets = jsonDecode(response.body);
    });
  }

  Widget getRow(int position) {
    return Padding(
      padding: const EdgeInsets.all(10.0),
      child: Text('Row ${widgets[position]["title"]}'),
    );
  }
}

image.png

你如何将工作转移到后台线程?

在Android中,当你想要访问网络资源时,你通常会移至后台线程并完成工作,以免阻塞主线程并避免ANR。例如,你可能正在使用一个AsyncTask、一个LiveData、一个IntentService、一个JobScheduler作业或一个RxJava管道以及一个在后台线程上运行的调度程序。

由于Flutter是单线程并且运行一个事件循环(如Node.js),您不必担心线程管理或生成后台线程。如果您正在进行I/O密集型工作,例如磁盘访问或网络调用,那么您可以安全地使用async/await并且一切就绪。另一方面,如果您需要执行使CPU忙碌的结算密集型工作,您希望将其移至一个Isolate以避免阻塞事件循环,就像在Android中将任何类型的工作排出在主线程之外一样。

对于I/O密集型工作,将函数声明为async函数,并await在函数内部长时间运行的任务:

Future<void> loadData() async {
    var dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
    http.Response response = await http.get(dataURL);
    setState(() {
        widgets = jsonDecode(response.body);
    });
}

这就是您通常进行网络或数据库调用的方式,它们都是I/O操作。

在Android上,当您继承AsyncTask,通常会重写3个方法onPreExecute(),doInBackground()onPostExecute()。在Flutter中没有等价物,因为你await在一个长时间运行的函数上,Dart的书剑循环会处理剩下的事情。

但是,有时您可能正在处理大量数据并且您的UI挂起。在Flutter中,使用Isonlate可以利用多个CPU内核来执行长时间运行或计算密集型任务。

Isolate是独立的执行线程,不与主执行内存堆共享任何内存。这意味着您无法从主现成访问变量,或通过调用setState()。与Android线程不同,Isolate名副其实,不能共享内存(例如,以静态字段的形式)。

下面的示例在一个简单的Isolate,展示了如何将数据共享会主线程以更新UI。

Future<void> loadData() async {
    ReceivePort receivePort = ReceivePort();
    await Isolate.spawn(dataLoader, receivePort.sendPort);
    
    // The 'echo' issolate 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 fot the isolate
static Future<void> dataLoader(SendPort sendPort) async {
    // Open the ReceivePort for incoming messages
    ReceivePort port = ReceivePort();
    
    // Notify any other isolates what port this isolate listen 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(Uri.parse(dataURL));
        // Lots of JSON to parse
        replyTo.send(jsonDecode(respnse.bogy));
    }
}

Future sendReceive(SendPort port, msg) {
    ReceivePort response = ReceivePort();
    port.send([msg, response.sendPort]);
    return response.first;
}

在这里,dataLoader()Isolate在其自己的独立执行线程中运行的。在isolate中,您可以执行更多CPU密集型处理(例如解析大的JSON),或执行计算密集型数学运算,例如加密或信号处理。

下面完整例子:

import 'dart:convert';
import 'dart:isolate';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(const SampleApp());
}

class SampleApp extends StatelessWidget {
  const SampleApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  const 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: const Text('Sample App'),
      ),
      body: getBody(),
    );
  }

  Widget getBody() {
    bool showLoadingDialog = widgets.isEmpty;
    if (showLoadingDialog) {
      return getProgressDialog();
    } else {
      return getListView();
    }
  }

  Widget getProgressDialog() {
    return const Center(child: CircularProgressIndicator());
  }

  Widget getListView() {
    return ListView.builder(
      itemCount: widgets.length,
      itemBuilder: (context, position) {
        return getRow(position);
      },
    );
  }

  Widget getRow(int i) {
    return Padding(
      padding: const EdgeInsets.all(10.0),
      child: Text('Row ${widgets[i]['title']}'),
    );
  }

  Future<void> loadData() async {
    ReceivePort receivePort = ReceivePort();
    await Isolate.spawn(dataLoader, receivePort.sendPort);

    // The 'echo' isolate  send 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 Future<void> dataLoader(SendPort sendPort) async {
    // Open the ReceivePort for incoming message.
    ReceivePort port = 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(Uri.parse(dataURL));
      // Lots of JSON to parse
      replyTo.send(jsonDecode(response.body));
    }
  }

  Future sendReceive(SendPort port, msg) {
    ReceivePort response = ReceivePort();
    port.send([msg, response.sendPort]);
    return response.first;
  }
}

image.png

Flutter上的OkHttp相当于什么?

http当您使用流行的包时,在 Flutter 中进行网络调用很容易。

虽然http包不具备Okhttp中的所有功能,但它抽象出了您通常自己实现的大部分网络,使其成为进行网络调用的简单方法。

要使用该http包,请将其添加到您的以来项目中pubspec.yaml

dependencies:
    ...
    http: ^0.11.3+16

要进行网络请求,请调用await/async函数http.get()

import 'dart:developer' as developer;
import 'package:http/http.dart' as http;

Future<void> loadData() async {
    var dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
    http.Response response = awaitt http.get(dataURL);
    developer.log(response.body);
}

如何显示长时间运行任务的进度

在Android中,您通常会ProgressBar在后台线程上执行长时间运行的任务时在UI中显示一个视图。

在Flutter中,使用ProgressIndicator小部件。通过控制何时通过布尔标志呈现进度,以编程方式显示进度。告诉Flutter在长时间运行的任务开始之前更新他的状态,并在它结束后隐藏它。

在下面的示例中,构建函数被分成三个不同的函数。如果showLoadingDialogtrue(当widgets.isEmpty),则渲染ProgressIndicator‘。否则通过网络返回的数据显示到ListView


import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(const SampleApp());
}

class SampleApp extends StatelessWidget {
   const SampleApp({Key? key}) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Sample App',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  const SampleAppPage({Key? key}) : super(key: key);

  @override
  _SampleAppPageState createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  List widgets = [];
  
  @override
  void initState() {
    super.initState();
    loadData();
  }
  
  Future<void> loadData() async {
    var dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
    http.Response response = await http.get(dataURL);
    setState(() {
      widgets = jsonDecode(response.body);
    });
  }
  
  Widget getBody() {
    bool showLoadingDialog = widgets.isEmpty;
    if (showLoadingDialog) {
      return getProgressDialog();
    } else {
      return getListView();
    }
  }
  
  Widget getProgressDialog() {
    return const Center(child: CircularProgressIndicator());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Sample App'),
      ),
      body: getBody(),
    );
  }

  ListView getListView() {
    return ListView.builder(
        itemCount: widgets.length,
        itemBuilder: (context, position) {
          return getRow(position);
        });
  }

  Widget getRow(int i) {
    return Padding(
      padding: const EdgeInsets.all(10.0),
      child: Text("Row ${widgets[i]["title"]}"),
    );
  }
}

image.png

本文转自 https://juejin.cn/post/7182563823210430525,如有侵权,请联系删除。

相关文章

网友评论

      本文标题:Flutter入门之Async UI

      本文链接:https://www.haomeiwen.com/subject/jbhdcdtx.html