今天我们来学习下flutter中的多线程Isolate的用法。
下面我们会通过如何解析JSON数据来学习isolate的使用,json解析在app中是非常常见的。如果json数据小,在main isolate解析是没有任何问题的,如果数据过大的时候,就会阻塞UI(表现为卡顿和丢帧),所以这时候就会用到Isolate。
这里有两个概念worker isolate和main isolate,相当于多线程了,但不是真正的多线,dart是单线程的。
在本文中,我们将学习通过两种方式解析 JSON,即isolate的两种使用方式:
- 使用
compute()
函数 - 通过spawning an isolate来完成并在完成后调用
Isolate.exit()
通知worker isolate。
首先我们先定一个需要解析的json格式:
代码语言:javascript复制{
"results": [
{
"title": "Flutter Tutorial: Stopwatch App with Custom UI and Animations",
"url": "https://font.happystudy.ink/posts/1",
"date": "Dec 6, 2021",
"contentType": "video"
},
{
"title": "Responsive layouts in Flutter: Split View and Drawer Navigation",
"url": "https://font.happystudy.ink/posts/2",
"date": "Nov 26, 2021",
"contentType": "article"
},
{
"title": "How to create a Flutter GridView with content-sized items",
"url": "https://font.happystudy.ink/posts/3",
"date": "Nov 24, 2021",
"contentType": "article"
}
]
}
这个例子中仅仅展示了3条数据,但实际中可能会有很多数据,假如超过了1M。我们先定义一个解析的类SearchResult.
代码语言:javascript复制class SearchResult {
SearchResult({
required this.title,
required this.url,
required this.date,
});
final String title;
final String url;
final String date;
factory SearchResult.fromJson(Map<String, dynamic> data) {
return SearchResult(
title: data['title'],
url: data['url'],
date: data['date'],
);
}
}
使用 isolates解析数据
我们先定义一个解析用的类,如下
代码语言:javascript复制import 'dart:convert';
class SearchResultsParser {
List<SearchResult> _decodeAndParseJson(String encodedJson) {
final jsonData = jsonDecode(encodedJson);
final resultsJson = jsonData['results'] as List<dynamic>;
return resultsJson.map((json) => SearchResult.fromJson(json)).toList();
}
}
**_decodeAndParseJson()**方法现在不是异步的,但是如果现在数据量很大时,这个解析方法将耗费很长时间。
那么我们现在如何让这个函数在后台运行,不阻塞我们的UI呢?
现在先用我们的第一种方法compute():
代码语言:javascript复制import 'dart:convert';
import 'package:flutter/foundation.dart';
class SearchResultsParser {
Future<List<SearchResult>> parseInBackground(String encodedJson) {
// compute spawns an isolate, runs a callback on that isolate, and returns a Future with the result
return compute(_decodeAndParseJson, encodedJson);
}
List<SearchResult> _decodeAndParseJson(String encodedJson) {
final jsonData = jsonDecode(encodedJson);
final resultsJson = jsonData['results'] as List<dynamic>;
return resultsJson.map((json) => SearchResult.fromJson(json)).toList();
}
}
我们使用了compute()方法,这样就将我们的解析方法放到了worker isolate, 现在再运行我们的程序,就不会出现卡顿的现象了!
compute是dart中为我们封装好的快速使用的方法。下面我们再试试另外一种更加灵活的使用方式。
使用Isolate.exit()快速实现
compute虽然使用简单,但有一些问题,Flutter 2.8以前compute耗时会长一些,所以compute会比实际解析耗时会长那么一点点。
我们现在学习如何自己使用 isolate API:
代码语言:javascript复制class SearchResultsParser {
// 1. pass the encoded json as a constructor argument
SearchResultsParser(this.encodedJson);
final String encodedJson;
// 2. public method that does the parsing in the background
Future<List<SearchResult>> parseInBackground() async {
// create a port
final p = ReceivePort();
// spawn the isolate and wait for it to complete
await Isolate.spawn(_decodeAndParseJson, p.sendPort);
// get and return the result data
return await p.first;
}
// 3. json parsing
Future<void> _decodeAndParseJson(SendPort p) async {
// decode and parse the json
final jsonData = jsonDecode(encodedJson);
final resultsJson = jsonData['results'] as List<dynamic>;
final results = resultsJson.map((json) => SearchResult.fromJson(json)).toList();
// return the result data via Isolate.exit()
Isolate.exit(p, results);
}
}
关键代码第2步和第3步,
- 我们使用
Isolate.spawn()
显式地创建一个新的Isolate,注意是异步的,使用了await
- 在
_decodeAndParseJson()
使用Isolate.exit()
,通知解析完成并返回了结果results
Isolate.spawn()
只能接受一个参数,所以encodedJson只能通过构造函数来传递示例变量。
对比这两种方法,我们可以看出compute
更加简便快捷。
什么情况下我们需要让我们的代码在background
呢
我们可以通过以下方法来测试下:
- 以profile模式在低配置的设备上运行
- 调整数据的大小,看看我们的UI是否卡顿或者丢帧
这样做会耗费很多时间,一般来说如果解析json数据超过10KB,我们就需要使用background了,毕竟1行代码就可以搞定。
代码语言:javascript复制compute(_decodeAndParseJson, encodedJson)
扩展:networking的代码需要 worker isolate吗
到目前为止,我们只是把json解析的代码放到了worker isolate
,那么 networking的代码需要放入吗?
import 'package:http/http.dart' as http;
class APIClient {
Future<List<SearchResult>> downloadAndParseJson() async {
// get the data from the network
final response = await http
.get(Uri.parse('https://codewithandrea.com/search/search.json'));
if (response.statusCode == 200) {
// on success, parse the JSON in the response body
final parser = SearchResultsParser();
return parser.parseInBackground(response.body);
} else {
// on failure, throw an exception
throw Exception('Failed to load json');
}
}
}
其实,nerworking
和file IO
已经是在一个**separate task runner.**当IO操作完成的时候,就会返回结果到main isolate。
也就是说我们能够安全的使用flutter中IO操作相关的API,dart已经都给我们封装好了。
结论
使用worker isolate, 我们使用compute
就能快速实现,在flutter2.8(Dart 2.15)我们compute的速度已经得到优化,简单场景无需我们再自定义使用Isolate了。
相关概念介绍
如果你想学习更多关于Isolate的东西,推荐以下阅读:
- Concurrency in Dart(https://dart.dev/guides/language/concurrency)
也有一些优秀的外国视频可以参考:
- Async vs Isolates | Decoding Flutter https://youtu.be/5AxWC49ZMzs
- Isolates and Event Loops - Flutter in Focus https://youtu.be/vl_AaCgudcY
关于 Flutter 2.8 and Dart 2.15 最新公告:
- What’s New in Flutter 2.8 https://medium.com/flutter/whats-new-in-flutter-2-8-d085b763d181
- Announcing Dart 2.15 https://medium.com/dartlang/dart-2-15-7e7a598e508a
StackOverflow 多线程的解释 how Dart manages IO operations under the hood:
What thread / isolate does flutter run IO operations on? https://stackoverflow.com/questions/56906744/what-thread-isolate-does-flutter-run-io-operations-on
-------------------------少年别走,交个朋友--------------------------