在背景中解析 JSON
預設情況下,Dart 應用程式的所有工作都在單一執行緒上進行。在許多情況下,此模型簡化了程式碼編寫,而且速度夠快,不會導致應用程式效能不佳或動畫卡頓,通常稱為「jank」。
但是,您可能需要執行昂貴的計算,例如解析非常大的 JSON 文件。如果這項工作花費超過 16 毫秒,您的使用者就會體驗到 jank。
為了避免 jank,您需要在背景執行像這樣的昂貴計算。在 Android 上,這意味著在不同的執行緒上排程工作。在 Flutter 中,您可以使用單獨的 Isolate。此食譜使用以下步驟
- 新增
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 轉換為照片列表
#接下來,按照「從網際網路擷取資料」食譜中的指南,將 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);
}
關於使用 Isolates 的注意事項
#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);
},
);
}
}
除非另有說明,否則本網站上的文件會反映 Flutter 的最新穩定版本。頁面上次更新於 2024-05-08。 檢視原始碼 或 回報問題。