原文為Flutter官方的Cookbook:Parse JSON in the background
通常,Dart app 會在一個執行緒內完成所有工作。許多情況下,這個模型簡化了編碼,速度足夠快而不會導致應用程式效能差或動畫斷斷續續,通常稱為“jank”。
然而,你可能需要執行一些耗時的操作,例如:解析一個非常大的Json
檔案。如果這個過程耗時超過16毫秒,使用者就會感覺卡頓、不流暢。
為了避免這種情況,你需要在後臺執行類似於這種耗時的操作。對於Android
,這就意味著在不同執行緒排程工作。而在Flutter,你可以使用Isolate
。
該方法有以下步驟:
- 新增http依賴包
- 使用
http
包傳送網路請求 - 將結果解析成
List<Photo>
- 移到
isolate
完成
1.新增http
依賴包
在pubspec.yaml
檔案新增依賴包
dependencies:
http: <latest_version>
複製程式碼
2.傳送網路請求
本例中,使用http.get()
方法從JSONPlaceholder REST API中獲取一個包含5000個照片物件列表的Json
大文件。
Future<http.Response> fetchPhotos(http.Client client) async {
return client.get('https://jsonplaceholder.typicode.com/photos');
}
複製程式碼
3.解析Json
建立Photo
類
class Photo {
final int id;
final String title;
final String thumbnailUrl;
Photo({this.id, this.title, this.thumbnailUrl});
factory Photo.fromJson(Map<String, dynamic> json) {
return Photo(
id: json['id'] as int,
title: json['title'] 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 = json.decode(responseBody).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('https://jsonplaceholder.typicode.com/photos');
return parsePhotos(response.body);
}
複製程式碼
4.轉移到isolate
中完成
如果你在一臺很慢的裝置中執行fetchPhotos()
函式,你可能會注意到,當應用在解析和轉換JSON時會有點卡。這就是‘jank’,而且你會想要擺脫它。
你可以通過呼叫Flutter提供的computed()
函式在後臺的isolate
進行解析和轉換來擺脫‘jank’。computed()
會在後臺執行耗時操作並將結果返回。在這個例子中,執行的是parsePhotos()
函式。
Future<List<Photo>> fetchPhotos(http.Client client) async {
final response =
await client.get('https://jsonplaceholder.typicode.com/photos');
// Use the compute function to run parsePhotos in a separate isolate.
return compute(parsePhotos, response.body);
}
複製程式碼
5.注意事項
Isolate
通過來回傳遞資訊進行通訊,這些資訊可以是原始值,例如null
,num
,bool
,double
,或者string
,也可以是簡單的物件就像這個例子中的List<Photo>
。
如果你嘗試在isolate
之間傳遞更復雜的物件,比如Future
或http.Response
,你可能會遇到錯誤。
6.尾記
這是今天在翻官網看到,感覺還不錯。不過現在翻完,感覺簡單了點,但感覺挺實用的,就像Android中的AsynTask
之類的。但都辛苦翻完了,就當一篇小記錄吧。才疏學淺,沒有什麼深度,還請多指教。
還有這個jank
實在不會翻,有些地方還能領悟,有些地方就完全不懂了。
7.完整程式碼
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('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 = json.decode(responseBody).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;
Photo({this.albumId, this.id, this.title, this.url, 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(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final appTitle = 'Isolate Demo';
return MaterialApp(
title: appTitle,
home: MyHomePage(title: appTitle),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
MyHomePage({Key key, this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: FutureBuilder<List<Photo>>(
future: fetchPhotos(http.Client()),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? PhotosList(photos: snapshot.data)
: Center(child: CircularProgressIndicator());
},
),
);
}
}
class PhotosList extends StatelessWidget {
final List<Photo> photos;
PhotosList({Key key, this.photos}) : super(key: key);
@override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemCount: photos.length,
itemBuilder: (context, index) {
return Image.network(photos[index].thumbnailUrl);
},
);
}
}
複製程式碼