在后台解析 JSON
默认情况下,Dart 应用的所有工作都在单个线程上进行。在许多情况下,这种模型简化了编码,并且速度足够快,不会导致应用性能不佳或动画卡顿(通常称为“jank”)。
但是,你可能需要执行开销较大的计算,例如解析一个非常大的 JSON 文档。如果此工作耗时超过 16 毫秒,你的用户就会遇到卡顿 (jank)。
为了避免卡顿 (jank),你需要使用单独的 Isolate 在后台执行此类开销较大的计算。本菜谱 (recipe) 包含以下步骤:
- 添加 `http` 包。
- 使用
http
包发起网络请求。 - 将响应转换为照片列表。
- 将此工作移至单独的隔离区 (isolate)。
1. 添加 `http` 包
#首先,将 http
包添加到你的项目中。http
包使执行网络请求(例如从 JSON 端点获取数据)变得更容易。
要将 http
包添加为依赖项,请运行 flutter pub add
。
flutter pub add http
2. 发起网络请求
#本示例涵盖了如何使用 http.get()
方法,从 JSONPlaceholder REST API 获取包含 5000 个照片对象列表的大型 JSON 文档。
Future<http.Response> fetchPhotos(http.Client client) async {
return client.get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));
}
3. 解析 JSON 并将其转换为照片列表
#接下来,遵循 从互联网获取数据 菜谱 (recipe) 中的指导,将 http.Response
转换为 Dart 对象列表。这使得数据更容易处理。
创建一个 Photo
类
#首先,创建一个包含照片数据的 Photo
类。包含一个 fromJson()
工厂方法,以便从 JSON 对象轻松创建 Photo
实例。
class Photo {
final int albumId;
final int id;
final String title;
final String url;
final String thumbnailUrl;
const Photo({
required this.albumId,
required this.id,
required this.title,
required this.url,
required this.thumbnailUrl,
});
factory Photo.fromJson(Map<String, dynamic> json) {
return Photo(
albumId: json['albumId'] as int,
id: json['id'] as int,
title: json['title'] as String,
url: json['url'] as String,
thumbnailUrl: json['thumbnailUrl'] as String,
);
}
}
将响应转换为照片列表
#现在,使用以下说明更新 fetchPhotos()
函数,使其返回一个 Future<List<Photo>>
:
- 创建一个
parsePhotos()
函数,将响应体转换为List<Photo>
。 - 在
fetchPhotos()
函数中使用parsePhotos()
函数。
// A function that converts a response body into a List<Photo>.
List<Photo> parsePhotos(String responseBody) {
final parsed = (jsonDecode(responseBody) as List)
.cast<Map<String, dynamic>>();
return parsed.map<Photo>((json) => Photo.fromJson(json)).toList();
}
Future<List<Photo>> fetchPhotos(http.Client client) async {
final response = await client.get(
Uri.parse('https://jsonplaceholder.typicode.com/photos'),
);
// Synchronously run parsePhotos in the main isolate.
return parsePhotos(response.body);
}
4. 将此工作移至单独的隔离区 (isolate)
#如果你在较慢的设备上运行 fetchPhotos()
函数,你可能会注意到应用在解析和转换 JSON 时会短暂冻结。这就是卡顿 (jank),你需要消除它。
你可以通过使用 Flutter 提供的 compute()
函数,将解析和转换移至后台隔离区 (isolate) 来消除卡顿 (jank)。compute()
函数在后台隔离区 (isolate) 中运行开销较大的函数并返回结果。在本例中,在后台运行 parsePhotos()
函数。
Future<List<Photo>> fetchPhotos(http.Client client) async {
final response = await client.get(
Uri.parse('https://jsonplaceholder.typicode.com/photos'),
);
// Use the compute function to run parsePhotos in a separate isolate.
return compute(parsePhotos, response.body);
}
使用隔离区 (isolate) 的注意事项
#隔离区 (isolate) 通过来回传递消息进行通信。这些消息可以是原始值,如 null
、num
、bool
、double
或 String
,也可以是简单对象,如本例中的 List<Photo>
。
如果你尝试在隔离区 (isolate) 之间传递更复杂的对象,例如 Future
或 http.Response
,可能会遇到错误。
作为替代解决方案,你可以查看 worker_manager
或 workmanager
包,它们用于后台处理。
完整示例
#import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<List<Photo>> fetchPhotos(http.Client client) async {
final response = await client.get(
Uri.parse('https://jsonplaceholder.typicode.com/photos'),
);
// Use the compute function to run parsePhotos in a separate isolate.
return compute(parsePhotos, response.body);
}
// A function that converts a response body into a List<Photo>.
List<Photo> parsePhotos(String responseBody) {
final parsed = (jsonDecode(responseBody) as List)
.cast<Map<String, dynamic>>();
return parsed.map<Photo>((json) => Photo.fromJson(json)).toList();
}
class Photo {
final int albumId;
final int id;
final String title;
final String url;
final String thumbnailUrl;
const Photo({
required this.albumId,
required this.id,
required this.title,
required this.url,
required this.thumbnailUrl,
});
factory Photo.fromJson(Map<String, dynamic> json) {
return Photo(
albumId: json['albumId'] as int,
id: json['id'] as int,
title: json['title'] as String,
url: json['url'] as String,
thumbnailUrl: json['thumbnailUrl'] as String,
);
}
}
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
const appTitle = 'Isolate Demo';
return const MaterialApp(
title: appTitle,
home: MyHomePage(title: appTitle),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late Future<List<Photo>> futurePhotos;
@override
void initState() {
super.initState();
futurePhotos = fetchPhotos(http.Client());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: FutureBuilder<List<Photo>>(
future: futurePhotos,
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Center(child: Text('An error has occurred!'));
} else if (snapshot.hasData) {
return PhotosList(photos: snapshot.data!);
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
);
}
}
class PhotosList extends StatelessWidget {
const PhotosList({super.key, required this.photos});
final List<Photo> photos;
@override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemCount: photos.length,
itemBuilder: (context, index) {
return Image.network(photos[index].thumbnailUrl);
},
);
}
}