线程和异步
编写异步代码
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);
},
),
);
}
}
切到后台线程
因为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(),
);
}
}
错误
[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