Flutter - UIKit开发相关指南 - 线程和异步

发布于:2025-05-16 ⋅ 阅读:(13) ⋅ 点赞:(0)

线程和异步

编写异步代码

Dart采用单线程执行模型,支持Isolates(在另一个线程上运行Dart代码)、事件循环和异步编程。除非生成一个Isolates,否则Dart代码将在主UI线程中运行,并由事件循环驱动。Flutter的事件循环相当于iOS的主线程上的RunLoop。

Dart的单线程模型,不代表阻塞型的操作都会导致UI卡顿。实际上可以采用Dart语言提供的异步功能比如async/await来执行异步的操作。

因为要请求网络,所以添加http模块

$ fltter pub add http
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

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

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(home: ThreadSample());
  }
}

class ThreadSample extends StatefulWidget {
  const ThreadSample({super.key});

  
  State<ThreadSample> createState() => _ThreadSampleState();
}

class _ThreadSampleState extends State<ThreadSample> {
  List<Map<String, Object?>> data = [];
  
  /// 1. 初始化_ThreadSampleState Widget的状态
  void initState() {
    super.initState();
    /// 2.加载数据
    loadData();
  }

  Future<void> loadData() async {
    /// 3. 发起异步请求
    final Uri dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
    final http.Response response = await http.get(dataURL);
    /// 4. 等响应结束后调用setState() 更新data 触发build方法
    setState(() {
      data = (jsonDecode(response.body) as List).cast<Map<String, Object?>>();
    });
  }

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('线程与异步示例')),
      // 5. 显示列表,长度为data.length,内容通过getRow方法返回data的子元素
      body: ListView.builder(
        itemCount: data.length,
        itemBuilder: (context, index) {
          return getRow(index);
        },
      ),
    );
  }
}

2025-05-12 16.39.30.png

切到后台线程

因为Flutter是单线程模型,不需要考虑线程管理相关的问题。在执行I/O密集型的操作时,比如访问磁盘或网络,可以使用async/await,但是当在执行CPU计算密集型的操作时,则应该将其移到独立线程(Isolate)以避免阻塞事件循环。

Isolates 是独立的执行线程,它们与主线程内存堆不共享任何内存。这意味着你无法访问主线程中的变量,或通过调用 setState() 来更新用户界面。

import 'dart:async';
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({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Sample App', home: SampleAppPage());
  }
}

class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});

  
  State<SampleAppPage> createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  List<Map<String, Object?>> data = [];

  
  void initState() {
    super.initState();

    /// 主1. 加载数据
    loadData();
  }

  bool get showLoadingDialog => data.isEmpty;

  Future<void> loadData() async {
    /// Opens a long-lived port for receiving messages.
    /// 打开端口用于接收数据
    final ReceivePort receivePort = ReceivePort();

    /// 主2.Isolate开启子线程
    /// The [entryPoint] function must be able to be called with a single
    /// argument, that is, a function which accepts at least one positional
    /// parameter and has at most one required positional parameter.
    ///
    /// The entry-point function is invoked in the new isolate with [message]
    /// as the only argument.
    /// 第一个参数:至少包含一个参数的函数指针,这里关联的是dataLoader,参数是SendPort
    ///
    /// [message] must be sendable between isolates. Objects that cannot be sent
    /// include open files and sockets (see [SendPort.send] for details). Usually
    /// the initial [message] contains a [SendPort] so that the spawner and
    /// spawnee can communicate with each other.
    /// 第二个参数: 不同Isolate之间传递的数据,通常初始化时传的message包含一个SendPort
    ///
    /// receivePort.sendPort
    /// [SendPort]s are created from [ReceivePort]s.
    /// Any message sent through a [SendPort] is delivered to its corresponding [ReceivePort].
    /// There might be many [SendPort]s for the same [ReceivePort].
    /// 通过SendPort发送的消息会传送给关联的ReceivePort
    await Isolate.spawn(dataLoader, receivePort.sendPort);

    /// 主3. first是一个Future,它会在接收到第一个消息时完成
    /// 一旦收到第一个消息,它就会关闭ReceivePort,并且不再监听其它消息
    /// 适用于只接收单个消息的情况
    final SendPort sendPort = await receivePort.first as SendPort;
    try {
      /// 主4. 使用await调用sendReceive
      final List<Map<String, dynamic>> msg = await sendReceive(
        sendPort,
        'https://jsonplaceholder.typicode.com/posts',
      );

      /// 主5.设置数据,通知Flutter刷新UI
      setState(() {
        data = msg;
      });
    } catch (e) {
      print('Error in loadData:$e');
    }
  }

  // 子1. 执行子线程上的函数
  static Future<void> dataLoader(SendPort sendPort) async {
    // 子2.打开端口接收数据
    final ReceivePort port = ReceivePort();

    /// 子3. 发送自己的接收端口
    sendPort.send(port.sendPort);

    /// 子4:等待消息
    await for (final dynamic msg in port) {

      /// 子5: 接收到url + 主线程的接收端口
      final String url = msg[0] as String;
      final SendPort replyTo = msg[1] as SendPort;

      /// 子6: 发起网络请求
      final Uri dataURL = Uri.parse(url);
      final http.Response response = await http.get(dataURL);

      /// 下面这种写法在sendReceive会报
      /// Unhandled
      /// Exception: type 'Future<dynamic>' is not a subtype of type
      /// 'Future<List<Map<String, dynamic>>>'
      ///
      /// replyTo.send(jsonDecode(response.body) as List<Map<String, dynamic>>);
      /// 因为Dart在运行时无法检查Future<T>中的T,直接转换Future的泛型参数会失败
      /// 强制类型转换
      final data = jsonDecode(response.body) as List;
      final typedata = data.cast<Map<String, dynamic>>();

      /// 子7: 将网络请求的结果发送到主线程
      replyTo.send(typedata);
    }
  }

  Future<dynamic> sendReceive(SendPort port, String msg) {
    // 主5.创建接收数据的端口
    final ReceivePort response = ReceivePort();
    // Sends an asynchronous [message] through this send port, to its corresponding [ReceivePort].
    // 主6. 主线程异步发送url + 通知其它线程接收端口
    port.send(<dynamic>[msg, response.sendPort]);
    return response.first;
  }

  Widget getBody() {
    /// 数据为空显示进度条
    bool showLoadingDialog = data.isEmpty;

    if (showLoadingDialog) {
      return getProgressDialog();
    } else {
      return getListView();
    }
  }

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

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

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

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

202505131636-w400

错误

[ERROR:flutter/runtime/dart_vm_initializer.cc(40)] Unhandled Exception: ClientException with SocketException: Failed host lookup: 'jsonplaceholder.typicode.com'

[ERROR:flutter/runtime/dart_vm_initializer.cc(40)] Unhandled Exception: ClientException with SocketException: Failed host lookup: ‘jsonplaceholder.typicode.com’ (OS Error: nodename nor servname provided, or not known, errno = 8), uri=https://jsonplaceholder.typicode.com/posts

首次启动需要同意网络权限,看报错是DNS找不到域名,所以还是网络问题,在手机上授权后再重新用flutter运行工程能恢复

参考

1.给 UIKit 开发者的 Flutter 指南
2.flutter 中 ReceivePort 的 first 和 listen